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 commit_tooltip;
20pub mod display_map;
21mod editor_settings;
22mod editor_settings_controls;
23mod element;
24mod git;
25mod highlight_matching_bracket;
26mod hover_links;
27mod hover_popover;
28mod indent_guides;
29mod inlay_hint_cache;
30pub mod items;
31mod jsx_tag_auto_close;
32mod linked_editing_ranges;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod editor_tests;
45#[cfg(test)]
46mod inline_completion_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
53use aho_corasick::AhoCorasick;
54use anyhow::{anyhow, Context as _, Result};
55use blink_manager::BlinkManager;
56use buffer_diff::DiffHunkStatus;
57use client::{Collaborator, ParticipantIndex};
58use clock::ReplicaId;
59use collections::{BTreeMap, HashMap, HashSet, VecDeque};
60use convert_case::{Case, Casing};
61use display_map::*;
62pub use display_map::{DisplayPoint, FoldPlaceholder};
63use editor_settings::GoToDefinitionFallback;
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68pub use editor_settings_controls::*;
69use element::{layout_line, AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
70pub use element::{
71 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
72};
73use feature_flags::{Debugger, FeatureFlagAppExt};
74use futures::{
75 future::{self, join, Shared},
76 FutureExt,
77};
78use fuzzy::StringMatchCandidate;
79
80use ::git::Restore;
81use code_context_menus::{
82 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
83 CompletionsMenu, ContextMenuOrigin,
84};
85use git::blame::GitBlame;
86use gpui::{
87 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
88 AnimationExt, AnyElement, App, AppContext, AsyncWindowContext, AvailableSpace, Background,
89 Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity,
90 EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight,
91 Global, HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
92 ParentElement, Pixels, Render, SharedString, Size, Stateful, Styled, StyledText, Subscription,
93 Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
94 WeakEntity, WeakFocusHandle, Window,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{find_file, HoverLink, HoveredLinkState, InlayHighlight};
98use hover_popover::{hide_hover, HoverState};
99use indent_guides::ActiveIndentGuidesState;
100use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
101pub use inline_completion::Direction;
102use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
103pub use items::MAX_TAB_TITLE_LEN;
104use itertools::Itertools;
105use language::{
106 language_settings::{
107 self, all_language_settings, language_settings, InlayHintSettings, RewrapBehavior,
108 WordsCompletionMode,
109 },
110 point_from_lsp, text_diff_with_options, AutoindentMode, BracketMatch, BracketPair, Buffer,
111 Capability, CharKind, CodeLabel, CursorShape, Diagnostic, DiffOptions, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114};
115use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
116use linked_editing_ranges::refresh_linked_ranges;
117use mouse_context_menu::MouseContextMenu;
118use persistence::DB;
119use project::{
120 debugger::breakpoint_store::{
121 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
122 },
123 ProjectPath,
124};
125
126pub use proposed_changes_editor::{
127 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
128};
129use smallvec::smallvec;
130use std::{cell::OnceCell, iter::Peekable};
131use task::{ResolvedTask, TaskTemplate, TaskVariables};
132
133pub use lsp::CompletionContext;
134use lsp::{
135 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
136 InsertTextFormat, LanguageServerId, LanguageServerName,
137};
138
139use language::BufferSnapshot;
140use movement::TextLayoutDetails;
141pub use multi_buffer::{
142 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
143 ToOffset, ToPoint,
144};
145use multi_buffer::{
146 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
147 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
148};
149use parking_lot::Mutex;
150use project::{
151 debugger::breakpoint_store::Breakpoint,
152 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
153 project_settings::{GitGutterSetting, ProjectSettings},
154 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
155 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
156 TaskSourceKind,
157};
158use rand::prelude::*;
159use rpc::{proto::*, ErrorExt};
160use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
161use selections_collection::{
162 resolve_selections, MutableSelectionsCollection, SelectionsCollection,
163};
164use serde::{Deserialize, Serialize};
165use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
166use smallvec::SmallVec;
167use snippet::Snippet;
168use std::sync::Arc;
169use std::{
170 any::TypeId,
171 borrow::Cow,
172 cell::RefCell,
173 cmp::{self, Ordering, Reverse},
174 mem,
175 num::NonZeroU32,
176 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
177 path::{Path, PathBuf},
178 rc::Rc,
179 time::{Duration, Instant},
180};
181pub use sum_tree::Bias;
182use sum_tree::TreeMap;
183use text::{BufferId, OffsetUtf16, Rope};
184use theme::{
185 observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme,
186 ThemeColors, ThemeSettings,
187};
188use ui::{
189 h_flex, prelude::*, ButtonSize, ButtonStyle, Disclosure, IconButton, IconButtonShape, IconName,
190 IconSize, Key, Tooltip,
191};
192use util::{maybe, post_inc, RangeExt, ResultExt, TryFutureExt};
193use workspace::{
194 item::{ItemHandle, PreviewTabsSettings},
195 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
196 searchable::SearchEvent,
197 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
198 RestoreOnStartupBehavior, SplitDirection, TabBarSettings, Toast, ViewId, Workspace,
199 WorkspaceId, WorkspaceSettings, SERIALIZATION_THROTTLE_TIME,
200};
201
202use crate::hover_links::{find_url, find_url_from_range};
203use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
204
205pub const FILE_HEADER_HEIGHT: u32 = 2;
206pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
207pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
208const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
209const MAX_LINE_LEN: usize = 1024;
210const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
211const MAX_SELECTION_HISTORY_LEN: usize = 1024;
212pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
213#[doc(hidden)]
214pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
215
216pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
217pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
218pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
219
220pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
221pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
222pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
223
224pub type RenderDiffHunkControlsFn = Arc<
225 dyn Fn(
226 u32,
227 &DiffHunkStatus,
228 Range<Anchor>,
229 bool,
230 Pixels,
231 &Entity<Editor>,
232 &mut Window,
233 &mut App,
234 ) -> AnyElement,
235>;
236
237const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
238 alt: true,
239 shift: true,
240 control: false,
241 platform: false,
242 function: false,
243};
244
245#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
246pub enum InlayId {
247 InlineCompletion(usize),
248 Hint(usize),
249}
250
251impl InlayId {
252 fn id(&self) -> usize {
253 match self {
254 Self::InlineCompletion(id) => *id,
255 Self::Hint(id) => *id,
256 }
257 }
258}
259
260pub enum DebugCurrentRowHighlight {}
261enum DocumentHighlightRead {}
262enum DocumentHighlightWrite {}
263enum InputComposition {}
264enum SelectedTextHighlight {}
265
266#[derive(Debug, Copy, Clone, PartialEq, Eq)]
267pub enum Navigated {
268 Yes,
269 No,
270}
271
272impl Navigated {
273 pub fn from_bool(yes: bool) -> Navigated {
274 if yes {
275 Navigated::Yes
276 } else {
277 Navigated::No
278 }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283enum DisplayDiffHunk {
284 Folded {
285 display_row: DisplayRow,
286 },
287 Unfolded {
288 is_created_file: bool,
289 diff_base_byte_range: Range<usize>,
290 display_row_range: Range<DisplayRow>,
291 multi_buffer_range: Range<Anchor>,
292 status: DiffHunkStatus,
293 },
294}
295
296pub enum HideMouseCursorOrigin {
297 TypingAction,
298 MovementAction,
299}
300
301pub fn init_settings(cx: &mut App) {
302 EditorSettings::register(cx);
303}
304
305pub fn init(cx: &mut App) {
306 init_settings(cx);
307
308 workspace::register_project_item::<Editor>(cx);
309 workspace::FollowableViewRegistry::register::<Editor>(cx);
310 workspace::register_serializable_item::<Editor>(cx);
311
312 cx.observe_new(
313 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
314 workspace.register_action(Editor::new_file);
315 workspace.register_action(Editor::new_file_vertical);
316 workspace.register_action(Editor::new_file_horizontal);
317 workspace.register_action(Editor::cancel_language_server_work);
318 },
319 )
320 .detach();
321
322 cx.on_action(move |_: &workspace::NewFile, cx| {
323 let app_state = workspace::AppState::global(cx);
324 if let Some(app_state) = app_state.upgrade() {
325 workspace::open_new(
326 Default::default(),
327 app_state,
328 cx,
329 |workspace, window, cx| {
330 Editor::new_file(workspace, &Default::default(), window, cx)
331 },
332 )
333 .detach();
334 }
335 });
336 cx.on_action(move |_: &workspace::NewWindow, cx| {
337 let app_state = workspace::AppState::global(cx);
338 if let Some(app_state) = app_state.upgrade() {
339 workspace::open_new(
340 Default::default(),
341 app_state,
342 cx,
343 |workspace, window, cx| {
344 cx.activate(true);
345 Editor::new_file(workspace, &Default::default(), window, cx)
346 },
347 )
348 .detach();
349 }
350 });
351}
352
353pub struct SearchWithinRange;
354
355trait InvalidationRegion {
356 fn ranges(&self) -> &[Range<Anchor>];
357}
358
359#[derive(Clone, Debug, PartialEq)]
360pub enum SelectPhase {
361 Begin {
362 position: DisplayPoint,
363 add: bool,
364 click_count: usize,
365 },
366 BeginColumnar {
367 position: DisplayPoint,
368 reset: bool,
369 goal_column: u32,
370 },
371 Extend {
372 position: DisplayPoint,
373 click_count: usize,
374 },
375 Update {
376 position: DisplayPoint,
377 goal_column: u32,
378 scroll_delta: gpui::Point<f32>,
379 },
380 End,
381}
382
383#[derive(Clone, Debug)]
384pub enum SelectMode {
385 Character,
386 Word(Range<Anchor>),
387 Line(Range<Anchor>),
388 All,
389}
390
391#[derive(Copy, Clone, PartialEq, Eq, Debug)]
392pub enum EditorMode {
393 SingleLine { auto_width: bool },
394 AutoHeight { max_lines: usize },
395 Full,
396}
397
398#[derive(Copy, Clone, Debug)]
399pub enum SoftWrap {
400 /// Prefer not to wrap at all.
401 ///
402 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
403 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
404 GitDiff,
405 /// Prefer a single line generally, unless an overly long line is encountered.
406 None,
407 /// Soft wrap lines that exceed the editor width.
408 EditorWidth,
409 /// Soft wrap lines at the preferred line length.
410 Column(u32),
411 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
412 Bounded(u32),
413}
414
415#[derive(Clone)]
416pub struct EditorStyle {
417 pub background: Hsla,
418 pub local_player: PlayerColor,
419 pub text: TextStyle,
420 pub scrollbar_width: Pixels,
421 pub syntax: Arc<SyntaxTheme>,
422 pub status: StatusColors,
423 pub inlay_hints_style: HighlightStyle,
424 pub inline_completion_styles: InlineCompletionStyles,
425 pub unnecessary_code_fade: f32,
426}
427
428impl Default for EditorStyle {
429 fn default() -> Self {
430 Self {
431 background: Hsla::default(),
432 local_player: PlayerColor::default(),
433 text: TextStyle::default(),
434 scrollbar_width: Pixels::default(),
435 syntax: Default::default(),
436 // HACK: Status colors don't have a real default.
437 // We should look into removing the status colors from the editor
438 // style and retrieve them directly from the theme.
439 status: StatusColors::dark(),
440 inlay_hints_style: HighlightStyle::default(),
441 inline_completion_styles: InlineCompletionStyles {
442 insertion: HighlightStyle::default(),
443 whitespace: HighlightStyle::default(),
444 },
445 unnecessary_code_fade: Default::default(),
446 }
447 }
448}
449
450pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
451 let show_background = language_settings::language_settings(None, None, cx)
452 .inlay_hints
453 .show_background;
454
455 HighlightStyle {
456 color: Some(cx.theme().status().hint),
457 background_color: show_background.then(|| cx.theme().status().hint_background),
458 ..HighlightStyle::default()
459 }
460}
461
462pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
463 InlineCompletionStyles {
464 insertion: HighlightStyle {
465 color: Some(cx.theme().status().predictive),
466 ..HighlightStyle::default()
467 },
468 whitespace: HighlightStyle {
469 background_color: Some(cx.theme().status().created_background),
470 ..HighlightStyle::default()
471 },
472 }
473}
474
475type CompletionId = usize;
476
477pub(crate) enum EditDisplayMode {
478 TabAccept,
479 DiffPopover,
480 Inline,
481}
482
483enum InlineCompletion {
484 Edit {
485 edits: Vec<(Range<Anchor>, String)>,
486 edit_preview: Option<EditPreview>,
487 display_mode: EditDisplayMode,
488 snapshot: BufferSnapshot,
489 },
490 Move {
491 target: Anchor,
492 snapshot: BufferSnapshot,
493 },
494}
495
496struct InlineCompletionState {
497 inlay_ids: Vec<InlayId>,
498 completion: InlineCompletion,
499 completion_id: Option<SharedString>,
500 invalidation_range: Range<Anchor>,
501}
502
503enum EditPredictionSettings {
504 Disabled,
505 Enabled {
506 show_in_menu: bool,
507 preview_requires_modifier: bool,
508 },
509}
510
511enum InlineCompletionHighlight {}
512
513#[derive(Debug, Clone)]
514struct InlineDiagnostic {
515 message: SharedString,
516 group_id: usize,
517 is_primary: bool,
518 start: Point,
519 severity: DiagnosticSeverity,
520}
521
522pub enum MenuInlineCompletionsPolicy {
523 Never,
524 ByProvider,
525}
526
527pub enum EditPredictionPreview {
528 /// Modifier is not pressed
529 Inactive { released_too_fast: bool },
530 /// Modifier pressed
531 Active {
532 since: Instant,
533 previous_scroll_position: Option<ScrollAnchor>,
534 },
535}
536
537impl EditPredictionPreview {
538 pub fn released_too_fast(&self) -> bool {
539 match self {
540 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
541 EditPredictionPreview::Active { .. } => false,
542 }
543 }
544
545 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
546 if let EditPredictionPreview::Active {
547 previous_scroll_position,
548 ..
549 } = self
550 {
551 *previous_scroll_position = scroll_position;
552 }
553 }
554}
555
556pub struct ContextMenuOptions {
557 pub min_entries_visible: usize,
558 pub max_entries_visible: usize,
559 pub placement: Option<ContextMenuPlacement>,
560}
561
562#[derive(Debug, Clone, PartialEq, Eq)]
563pub enum ContextMenuPlacement {
564 Above,
565 Below,
566}
567
568#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
569struct EditorActionId(usize);
570
571impl EditorActionId {
572 pub fn post_inc(&mut self) -> Self {
573 let answer = self.0;
574
575 *self = Self(answer + 1);
576
577 Self(answer)
578 }
579}
580
581// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
582// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
583
584type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
585type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
586
587#[derive(Default)]
588struct ScrollbarMarkerState {
589 scrollbar_size: Size<Pixels>,
590 dirty: bool,
591 markers: Arc<[PaintQuad]>,
592 pending_refresh: Option<Task<Result<()>>>,
593}
594
595impl ScrollbarMarkerState {
596 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
597 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
598 }
599}
600
601#[derive(Clone, Debug)]
602struct RunnableTasks {
603 templates: Vec<(TaskSourceKind, TaskTemplate)>,
604 offset: multi_buffer::Anchor,
605 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
606 column: u32,
607 // Values of all named captures, including those starting with '_'
608 extra_variables: HashMap<String, String>,
609 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
610 context_range: Range<BufferOffset>,
611}
612
613impl RunnableTasks {
614 fn resolve<'a>(
615 &'a self,
616 cx: &'a task::TaskContext,
617 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
618 self.templates.iter().filter_map(|(kind, template)| {
619 template
620 .resolve_task(&kind.to_id_base(), cx)
621 .map(|task| (kind.clone(), task))
622 })
623 }
624}
625
626#[derive(Clone)]
627struct ResolvedTasks {
628 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
629 position: Anchor,
630}
631
632#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
633struct BufferOffset(usize);
634
635// Addons allow storing per-editor state in other crates (e.g. Vim)
636pub trait Addon: 'static {
637 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
638
639 fn render_buffer_header_controls(
640 &self,
641 _: &ExcerptInfo,
642 _: &Window,
643 _: &App,
644 ) -> Option<AnyElement> {
645 None
646 }
647
648 fn to_any(&self) -> &dyn std::any::Any;
649}
650
651/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
652///
653/// See the [module level documentation](self) for more information.
654pub struct Editor {
655 focus_handle: FocusHandle,
656 last_focused_descendant: Option<WeakFocusHandle>,
657 /// The text buffer being edited
658 buffer: Entity<MultiBuffer>,
659 /// Map of how text in the buffer should be displayed.
660 /// Handles soft wraps, folds, fake inlay text insertions, etc.
661 pub display_map: Entity<DisplayMap>,
662 pub selections: SelectionsCollection,
663 pub scroll_manager: ScrollManager,
664 /// When inline assist editors are linked, they all render cursors because
665 /// typing enters text into each of them, even the ones that aren't focused.
666 pub(crate) show_cursor_when_unfocused: bool,
667 columnar_selection_tail: Option<Anchor>,
668 add_selections_state: Option<AddSelectionsState>,
669 select_next_state: Option<SelectNextState>,
670 select_prev_state: Option<SelectNextState>,
671 selection_history: SelectionHistory,
672 autoclose_regions: Vec<AutocloseRegion>,
673 snippet_stack: InvalidationStack<SnippetState>,
674 select_syntax_node_history: SelectSyntaxNodeHistory,
675 ime_transaction: Option<TransactionId>,
676 active_diagnostics: Option<ActiveDiagnosticGroup>,
677 show_inline_diagnostics: bool,
678 inline_diagnostics_update: Task<()>,
679 inline_diagnostics_enabled: bool,
680 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
681 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
682 hard_wrap: Option<usize>,
683
684 // TODO: make this a access method
685 pub project: Option<Entity<Project>>,
686 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
687 completion_provider: Option<Box<dyn CompletionProvider>>,
688 collaboration_hub: Option<Box<dyn CollaborationHub>>,
689 blink_manager: Entity<BlinkManager>,
690 show_cursor_names: bool,
691 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
692 pub show_local_selections: bool,
693 mode: EditorMode,
694 show_breadcrumbs: bool,
695 show_gutter: bool,
696 show_scrollbars: bool,
697 show_line_numbers: Option<bool>,
698 use_relative_line_numbers: Option<bool>,
699 show_git_diff_gutter: Option<bool>,
700 show_code_actions: Option<bool>,
701 show_runnables: Option<bool>,
702 show_breakpoints: Option<bool>,
703 show_wrap_guides: Option<bool>,
704 show_indent_guides: Option<bool>,
705 placeholder_text: Option<Arc<str>>,
706 highlight_order: usize,
707 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
708 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
709 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
710 scrollbar_marker_state: ScrollbarMarkerState,
711 active_indent_guides_state: ActiveIndentGuidesState,
712 nav_history: Option<ItemNavHistory>,
713 context_menu: RefCell<Option<CodeContextMenu>>,
714 context_menu_options: Option<ContextMenuOptions>,
715 mouse_context_menu: Option<MouseContextMenu>,
716 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
717 signature_help_state: SignatureHelpState,
718 auto_signature_help: Option<bool>,
719 find_all_references_task_sources: Vec<Anchor>,
720 next_completion_id: CompletionId,
721 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
722 code_actions_task: Option<Task<Result<()>>>,
723 selection_highlight_task: Option<Task<()>>,
724 document_highlights_task: Option<Task<()>>,
725 linked_editing_range_task: Option<Task<Option<()>>>,
726 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
727 pending_rename: Option<RenameState>,
728 searchable: bool,
729 cursor_shape: CursorShape,
730 current_line_highlight: Option<CurrentLineHighlight>,
731 collapse_matches: bool,
732 autoindent_mode: Option<AutoindentMode>,
733 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
734 input_enabled: bool,
735 use_modal_editing: bool,
736 read_only: bool,
737 leader_peer_id: Option<PeerId>,
738 remote_id: Option<ViewId>,
739 hover_state: HoverState,
740 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
741 gutter_hovered: bool,
742 hovered_link_state: Option<HoveredLinkState>,
743 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
744 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
745 active_inline_completion: Option<InlineCompletionState>,
746 /// Used to prevent flickering as the user types while the menu is open
747 stale_inline_completion_in_menu: Option<InlineCompletionState>,
748 edit_prediction_settings: EditPredictionSettings,
749 inline_completions_hidden_for_vim_mode: bool,
750 show_inline_completions_override: Option<bool>,
751 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
752 edit_prediction_preview: EditPredictionPreview,
753 edit_prediction_indent_conflict: bool,
754 edit_prediction_requires_modifier_in_indent_conflict: bool,
755 inlay_hint_cache: InlayHintCache,
756 next_inlay_id: usize,
757 _subscriptions: Vec<Subscription>,
758 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
759 gutter_dimensions: GutterDimensions,
760 style: Option<EditorStyle>,
761 text_style_refinement: Option<TextStyleRefinement>,
762 next_editor_action_id: EditorActionId,
763 editor_actions:
764 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
765 use_autoclose: bool,
766 use_auto_surround: bool,
767 auto_replace_emoji_shortcode: bool,
768 jsx_tag_auto_close_enabled_in_any_buffer: bool,
769 show_git_blame_gutter: bool,
770 show_git_blame_inline: bool,
771 show_git_blame_inline_delay_task: Option<Task<()>>,
772 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
773 git_blame_inline_enabled: bool,
774 render_diff_hunk_controls: RenderDiffHunkControlsFn,
775 serialize_dirty_buffers: bool,
776 show_selection_menu: Option<bool>,
777 blame: Option<Entity<GitBlame>>,
778 blame_subscription: Option<Subscription>,
779 custom_context_menu: Option<
780 Box<
781 dyn 'static
782 + Fn(
783 &mut Self,
784 DisplayPoint,
785 &mut Window,
786 &mut Context<Self>,
787 ) -> Option<Entity<ui::ContextMenu>>,
788 >,
789 >,
790 last_bounds: Option<Bounds<Pixels>>,
791 last_position_map: Option<Rc<PositionMap>>,
792 expect_bounds_change: Option<Bounds<Pixels>>,
793 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
794 tasks_update_task: Option<Task<()>>,
795 breakpoint_store: Option<Entity<BreakpointStore>>,
796 /// Allow's a user to create a breakpoint by selecting this indicator
797 /// It should be None while a user is not hovering over the gutter
798 /// Otherwise it represents the point that the breakpoint will be shown
799 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
800 in_project_search: bool,
801 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
802 breadcrumb_header: Option<String>,
803 focused_block: Option<FocusedBlock>,
804 next_scroll_position: NextScrollCursorCenterTopBottom,
805 addons: HashMap<TypeId, Box<dyn Addon>>,
806 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
807 load_diff_task: Option<Shared<Task<()>>>,
808 selection_mark_mode: bool,
809 toggle_fold_multiple_buffers: Task<()>,
810 _scroll_cursor_center_top_bottom_task: Task<()>,
811 serialize_selections: Task<()>,
812 serialize_folds: Task<()>,
813 mouse_cursor_hidden: bool,
814 hide_mouse_mode: HideMouseMode,
815}
816
817#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
818enum NextScrollCursorCenterTopBottom {
819 #[default]
820 Center,
821 Top,
822 Bottom,
823}
824
825impl NextScrollCursorCenterTopBottom {
826 fn next(&self) -> Self {
827 match self {
828 Self::Center => Self::Top,
829 Self::Top => Self::Bottom,
830 Self::Bottom => Self::Center,
831 }
832 }
833}
834
835#[derive(Clone)]
836pub struct EditorSnapshot {
837 pub mode: EditorMode,
838 show_gutter: bool,
839 show_line_numbers: Option<bool>,
840 show_git_diff_gutter: Option<bool>,
841 show_code_actions: Option<bool>,
842 show_runnables: Option<bool>,
843 show_breakpoints: Option<bool>,
844 git_blame_gutter_max_author_length: Option<usize>,
845 pub display_snapshot: DisplaySnapshot,
846 pub placeholder_text: Option<Arc<str>>,
847 is_focused: bool,
848 scroll_anchor: ScrollAnchor,
849 ongoing_scroll: OngoingScroll,
850 current_line_highlight: CurrentLineHighlight,
851 gutter_hovered: bool,
852}
853
854const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
855
856#[derive(Default, Debug, Clone, Copy)]
857pub struct GutterDimensions {
858 pub left_padding: Pixels,
859 pub right_padding: Pixels,
860 pub width: Pixels,
861 pub margin: Pixels,
862 pub git_blame_entries_width: Option<Pixels>,
863}
864
865impl GutterDimensions {
866 /// The full width of the space taken up by the gutter.
867 pub fn full_width(&self) -> Pixels {
868 self.margin + self.width
869 }
870
871 /// The width of the space reserved for the fold indicators,
872 /// use alongside 'justify_end' and `gutter_width` to
873 /// right align content with the line numbers
874 pub fn fold_area_width(&self) -> Pixels {
875 self.margin + self.right_padding
876 }
877}
878
879#[derive(Debug)]
880pub struct RemoteSelection {
881 pub replica_id: ReplicaId,
882 pub selection: Selection<Anchor>,
883 pub cursor_shape: CursorShape,
884 pub peer_id: PeerId,
885 pub line_mode: bool,
886 pub participant_index: Option<ParticipantIndex>,
887 pub user_name: Option<SharedString>,
888}
889
890#[derive(Clone, Debug)]
891struct SelectionHistoryEntry {
892 selections: Arc<[Selection<Anchor>]>,
893 select_next_state: Option<SelectNextState>,
894 select_prev_state: Option<SelectNextState>,
895 add_selections_state: Option<AddSelectionsState>,
896}
897
898enum SelectionHistoryMode {
899 Normal,
900 Undoing,
901 Redoing,
902}
903
904#[derive(Clone, PartialEq, Eq, Hash)]
905struct HoveredCursor {
906 replica_id: u16,
907 selection_id: usize,
908}
909
910impl Default for SelectionHistoryMode {
911 fn default() -> Self {
912 Self::Normal
913 }
914}
915
916#[derive(Default)]
917struct SelectionHistory {
918 #[allow(clippy::type_complexity)]
919 selections_by_transaction:
920 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
921 mode: SelectionHistoryMode,
922 undo_stack: VecDeque<SelectionHistoryEntry>,
923 redo_stack: VecDeque<SelectionHistoryEntry>,
924}
925
926impl SelectionHistory {
927 fn insert_transaction(
928 &mut self,
929 transaction_id: TransactionId,
930 selections: Arc<[Selection<Anchor>]>,
931 ) {
932 self.selections_by_transaction
933 .insert(transaction_id, (selections, None));
934 }
935
936 #[allow(clippy::type_complexity)]
937 fn transaction(
938 &self,
939 transaction_id: TransactionId,
940 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
941 self.selections_by_transaction.get(&transaction_id)
942 }
943
944 #[allow(clippy::type_complexity)]
945 fn transaction_mut(
946 &mut self,
947 transaction_id: TransactionId,
948 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
949 self.selections_by_transaction.get_mut(&transaction_id)
950 }
951
952 fn push(&mut self, entry: SelectionHistoryEntry) {
953 if !entry.selections.is_empty() {
954 match self.mode {
955 SelectionHistoryMode::Normal => {
956 self.push_undo(entry);
957 self.redo_stack.clear();
958 }
959 SelectionHistoryMode::Undoing => self.push_redo(entry),
960 SelectionHistoryMode::Redoing => self.push_undo(entry),
961 }
962 }
963 }
964
965 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
966 if self
967 .undo_stack
968 .back()
969 .map_or(true, |e| e.selections != entry.selections)
970 {
971 self.undo_stack.push_back(entry);
972 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
973 self.undo_stack.pop_front();
974 }
975 }
976 }
977
978 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
979 if self
980 .redo_stack
981 .back()
982 .map_or(true, |e| e.selections != entry.selections)
983 {
984 self.redo_stack.push_back(entry);
985 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
986 self.redo_stack.pop_front();
987 }
988 }
989 }
990}
991
992struct RowHighlight {
993 index: usize,
994 range: Range<Anchor>,
995 color: Hsla,
996 should_autoscroll: bool,
997}
998
999#[derive(Clone, Debug)]
1000struct AddSelectionsState {
1001 above: bool,
1002 stack: Vec<usize>,
1003}
1004
1005#[derive(Clone)]
1006struct SelectNextState {
1007 query: AhoCorasick,
1008 wordwise: bool,
1009 done: bool,
1010}
1011
1012impl std::fmt::Debug for SelectNextState {
1013 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1014 f.debug_struct(std::any::type_name::<Self>())
1015 .field("wordwise", &self.wordwise)
1016 .field("done", &self.done)
1017 .finish()
1018 }
1019}
1020
1021#[derive(Debug)]
1022struct AutocloseRegion {
1023 selection_id: usize,
1024 range: Range<Anchor>,
1025 pair: BracketPair,
1026}
1027
1028#[derive(Debug)]
1029struct SnippetState {
1030 ranges: Vec<Vec<Range<Anchor>>>,
1031 active_index: usize,
1032 choices: Vec<Option<Vec<String>>>,
1033}
1034
1035#[doc(hidden)]
1036pub struct RenameState {
1037 pub range: Range<Anchor>,
1038 pub old_name: Arc<str>,
1039 pub editor: Entity<Editor>,
1040 block_id: CustomBlockId,
1041}
1042
1043struct InvalidationStack<T>(Vec<T>);
1044
1045struct RegisteredInlineCompletionProvider {
1046 provider: Arc<dyn InlineCompletionProviderHandle>,
1047 _subscription: Subscription,
1048}
1049
1050#[derive(Debug, PartialEq, Eq)]
1051struct ActiveDiagnosticGroup {
1052 primary_range: Range<Anchor>,
1053 primary_message: String,
1054 group_id: usize,
1055 blocks: HashMap<CustomBlockId, Diagnostic>,
1056 is_valid: bool,
1057}
1058
1059#[derive(Serialize, Deserialize, Clone, Debug)]
1060pub struct ClipboardSelection {
1061 /// The number of bytes in this selection.
1062 pub len: usize,
1063 /// Whether this was a full-line selection.
1064 pub is_entire_line: bool,
1065 /// The indentation of the first line when this content was originally copied.
1066 pub first_line_indent: u32,
1067}
1068
1069// selections, scroll behavior, was newest selection reversed
1070type SelectSyntaxNodeHistoryState = (
1071 Box<[Selection<usize>]>,
1072 SelectSyntaxNodeScrollBehavior,
1073 bool,
1074);
1075
1076#[derive(Default)]
1077struct SelectSyntaxNodeHistory {
1078 stack: Vec<SelectSyntaxNodeHistoryState>,
1079 // disable temporarily to allow changing selections without losing the stack
1080 pub disable_clearing: bool,
1081}
1082
1083impl SelectSyntaxNodeHistory {
1084 pub fn try_clear(&mut self) {
1085 if !self.disable_clearing {
1086 self.stack.clear();
1087 }
1088 }
1089
1090 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1091 self.stack.push(selection);
1092 }
1093
1094 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1095 self.stack.pop()
1096 }
1097}
1098
1099enum SelectSyntaxNodeScrollBehavior {
1100 CursorTop,
1101 CenterSelection,
1102 CursorBottom,
1103}
1104
1105#[derive(Debug)]
1106pub(crate) struct NavigationData {
1107 cursor_anchor: Anchor,
1108 cursor_position: Point,
1109 scroll_anchor: ScrollAnchor,
1110 scroll_top_row: u32,
1111}
1112
1113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1114pub enum GotoDefinitionKind {
1115 Symbol,
1116 Declaration,
1117 Type,
1118 Implementation,
1119}
1120
1121#[derive(Debug, Clone)]
1122enum InlayHintRefreshReason {
1123 ModifiersChanged(bool),
1124 Toggle(bool),
1125 SettingsChange(InlayHintSettings),
1126 NewLinesShown,
1127 BufferEdited(HashSet<Arc<Language>>),
1128 RefreshRequested,
1129 ExcerptsRemoved(Vec<ExcerptId>),
1130}
1131
1132impl InlayHintRefreshReason {
1133 fn description(&self) -> &'static str {
1134 match self {
1135 Self::ModifiersChanged(_) => "modifiers changed",
1136 Self::Toggle(_) => "toggle",
1137 Self::SettingsChange(_) => "settings change",
1138 Self::NewLinesShown => "new lines shown",
1139 Self::BufferEdited(_) => "buffer edited",
1140 Self::RefreshRequested => "refresh requested",
1141 Self::ExcerptsRemoved(_) => "excerpts removed",
1142 }
1143 }
1144}
1145
1146pub enum FormatTarget {
1147 Buffers,
1148 Ranges(Vec<Range<MultiBufferPoint>>),
1149}
1150
1151pub(crate) struct FocusedBlock {
1152 id: BlockId,
1153 focus_handle: WeakFocusHandle,
1154}
1155
1156#[derive(Clone)]
1157enum JumpData {
1158 MultiBufferRow {
1159 row: MultiBufferRow,
1160 line_offset_from_top: u32,
1161 },
1162 MultiBufferPoint {
1163 excerpt_id: ExcerptId,
1164 position: Point,
1165 anchor: text::Anchor,
1166 line_offset_from_top: u32,
1167 },
1168}
1169
1170pub enum MultibufferSelectionMode {
1171 First,
1172 All,
1173}
1174
1175#[derive(Clone, Copy, Debug, Default)]
1176pub struct RewrapOptions {
1177 pub override_language_settings: bool,
1178 pub preserve_existing_whitespace: bool,
1179}
1180
1181impl Editor {
1182 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1183 let buffer = cx.new(|cx| Buffer::local("", cx));
1184 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1185 Self::new(
1186 EditorMode::SingleLine { auto_width: false },
1187 buffer,
1188 None,
1189 window,
1190 cx,
1191 )
1192 }
1193
1194 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1195 let buffer = cx.new(|cx| Buffer::local("", cx));
1196 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1197 Self::new(EditorMode::Full, buffer, None, window, cx)
1198 }
1199
1200 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1201 let buffer = cx.new(|cx| Buffer::local("", cx));
1202 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1203 Self::new(
1204 EditorMode::SingleLine { auto_width: true },
1205 buffer,
1206 None,
1207 window,
1208 cx,
1209 )
1210 }
1211
1212 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1213 let buffer = cx.new(|cx| Buffer::local("", cx));
1214 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1215 Self::new(
1216 EditorMode::AutoHeight { max_lines },
1217 buffer,
1218 None,
1219 window,
1220 cx,
1221 )
1222 }
1223
1224 pub fn for_buffer(
1225 buffer: Entity<Buffer>,
1226 project: Option<Entity<Project>>,
1227 window: &mut Window,
1228 cx: &mut Context<Self>,
1229 ) -> Self {
1230 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1231 Self::new(EditorMode::Full, buffer, project, window, cx)
1232 }
1233
1234 pub fn for_multibuffer(
1235 buffer: Entity<MultiBuffer>,
1236 project: Option<Entity<Project>>,
1237 window: &mut Window,
1238 cx: &mut Context<Self>,
1239 ) -> Self {
1240 Self::new(EditorMode::Full, buffer, project, window, cx)
1241 }
1242
1243 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1244 let mut clone = Self::new(
1245 self.mode,
1246 self.buffer.clone(),
1247 self.project.clone(),
1248 window,
1249 cx,
1250 );
1251 self.display_map.update(cx, |display_map, cx| {
1252 let snapshot = display_map.snapshot(cx);
1253 clone.display_map.update(cx, |display_map, cx| {
1254 display_map.set_state(&snapshot, cx);
1255 });
1256 });
1257 clone.folds_did_change(cx);
1258 clone.selections.clone_state(&self.selections);
1259 clone.scroll_manager.clone_state(&self.scroll_manager);
1260 clone.searchable = self.searchable;
1261 clone
1262 }
1263
1264 pub fn new(
1265 mode: EditorMode,
1266 buffer: Entity<MultiBuffer>,
1267 project: Option<Entity<Project>>,
1268 window: &mut Window,
1269 cx: &mut Context<Self>,
1270 ) -> Self {
1271 let style = window.text_style();
1272 let font_size = style.font_size.to_pixels(window.rem_size());
1273 let editor = cx.entity().downgrade();
1274 let fold_placeholder = FoldPlaceholder {
1275 constrain_width: true,
1276 render: Arc::new(move |fold_id, fold_range, cx| {
1277 let editor = editor.clone();
1278 div()
1279 .id(fold_id)
1280 .bg(cx.theme().colors().ghost_element_background)
1281 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1282 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1283 .rounded_xs()
1284 .size_full()
1285 .cursor_pointer()
1286 .child("⋯")
1287 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1288 .on_click(move |_, _window, cx| {
1289 editor
1290 .update(cx, |editor, cx| {
1291 editor.unfold_ranges(
1292 &[fold_range.start..fold_range.end],
1293 true,
1294 false,
1295 cx,
1296 );
1297 cx.stop_propagation();
1298 })
1299 .ok();
1300 })
1301 .into_any()
1302 }),
1303 merge_adjacent: true,
1304 ..Default::default()
1305 };
1306 let display_map = cx.new(|cx| {
1307 DisplayMap::new(
1308 buffer.clone(),
1309 style.font(),
1310 font_size,
1311 None,
1312 FILE_HEADER_HEIGHT,
1313 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1314 fold_placeholder,
1315 cx,
1316 )
1317 });
1318
1319 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1320
1321 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1322
1323 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1324 .then(|| language_settings::SoftWrap::None);
1325
1326 let mut project_subscriptions = Vec::new();
1327 if mode == EditorMode::Full {
1328 if let Some(project) = project.as_ref() {
1329 project_subscriptions.push(cx.subscribe_in(
1330 project,
1331 window,
1332 |editor, _, event, window, cx| match event {
1333 project::Event::RefreshCodeLens => {
1334 // we always query lens with actions, without storing them, always refreshing them
1335 }
1336 project::Event::RefreshInlayHints => {
1337 editor
1338 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1339 }
1340 project::Event::SnippetEdit(id, snippet_edits) => {
1341 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1342 let focus_handle = editor.focus_handle(cx);
1343 if focus_handle.is_focused(window) {
1344 let snapshot = buffer.read(cx).snapshot();
1345 for (range, snippet) in snippet_edits {
1346 let editor_range =
1347 language::range_from_lsp(*range).to_offset(&snapshot);
1348 editor
1349 .insert_snippet(
1350 &[editor_range],
1351 snippet.clone(),
1352 window,
1353 cx,
1354 )
1355 .ok();
1356 }
1357 }
1358 }
1359 }
1360 _ => {}
1361 },
1362 ));
1363 if let Some(task_inventory) = project
1364 .read(cx)
1365 .task_store()
1366 .read(cx)
1367 .task_inventory()
1368 .cloned()
1369 {
1370 project_subscriptions.push(cx.observe_in(
1371 &task_inventory,
1372 window,
1373 |editor, _, window, cx| {
1374 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1375 },
1376 ));
1377 };
1378
1379 project_subscriptions.push(cx.subscribe_in(
1380 &project.read(cx).breakpoint_store(),
1381 window,
1382 |editor, _, event, window, cx| match event {
1383 BreakpointStoreEvent::ActiveDebugLineChanged => {
1384 editor.go_to_active_debug_line(window, cx);
1385 }
1386 _ => {}
1387 },
1388 ));
1389 }
1390 }
1391
1392 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1393
1394 let inlay_hint_settings =
1395 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1396 let focus_handle = cx.focus_handle();
1397 cx.on_focus(&focus_handle, window, Self::handle_focus)
1398 .detach();
1399 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1400 .detach();
1401 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1402 .detach();
1403 cx.on_blur(&focus_handle, window, Self::handle_blur)
1404 .detach();
1405
1406 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1407 Some(false)
1408 } else {
1409 None
1410 };
1411
1412 let breakpoint_store = match (mode, project.as_ref()) {
1413 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1414 _ => None,
1415 };
1416
1417 let mut code_action_providers = Vec::new();
1418 let mut load_uncommitted_diff = None;
1419 if let Some(project) = project.clone() {
1420 load_uncommitted_diff = Some(
1421 get_uncommitted_diff_for_buffer(
1422 &project,
1423 buffer.read(cx).all_buffers(),
1424 buffer.clone(),
1425 cx,
1426 )
1427 .shared(),
1428 );
1429 code_action_providers.push(Rc::new(project) as Rc<_>);
1430 }
1431
1432 let mut this = Self {
1433 focus_handle,
1434 show_cursor_when_unfocused: false,
1435 last_focused_descendant: None,
1436 buffer: buffer.clone(),
1437 display_map: display_map.clone(),
1438 selections,
1439 scroll_manager: ScrollManager::new(cx),
1440 columnar_selection_tail: None,
1441 add_selections_state: None,
1442 select_next_state: None,
1443 select_prev_state: None,
1444 selection_history: Default::default(),
1445 autoclose_regions: Default::default(),
1446 snippet_stack: Default::default(),
1447 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1448 ime_transaction: Default::default(),
1449 active_diagnostics: None,
1450 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1451 inline_diagnostics_update: Task::ready(()),
1452 inline_diagnostics: Vec::new(),
1453 soft_wrap_mode_override,
1454 hard_wrap: None,
1455 completion_provider: project.clone().map(|project| Box::new(project) as _),
1456 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1457 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1458 project,
1459 blink_manager: blink_manager.clone(),
1460 show_local_selections: true,
1461 show_scrollbars: true,
1462 mode,
1463 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1464 show_gutter: mode == EditorMode::Full,
1465 show_line_numbers: None,
1466 use_relative_line_numbers: None,
1467 show_git_diff_gutter: None,
1468 show_code_actions: None,
1469 show_runnables: None,
1470 show_breakpoints: None,
1471 show_wrap_guides: None,
1472 show_indent_guides,
1473 placeholder_text: None,
1474 highlight_order: 0,
1475 highlighted_rows: HashMap::default(),
1476 background_highlights: Default::default(),
1477 gutter_highlights: TreeMap::default(),
1478 scrollbar_marker_state: ScrollbarMarkerState::default(),
1479 active_indent_guides_state: ActiveIndentGuidesState::default(),
1480 nav_history: None,
1481 context_menu: RefCell::new(None),
1482 context_menu_options: None,
1483 mouse_context_menu: None,
1484 completion_tasks: Default::default(),
1485 signature_help_state: SignatureHelpState::default(),
1486 auto_signature_help: None,
1487 find_all_references_task_sources: Vec::new(),
1488 next_completion_id: 0,
1489 next_inlay_id: 0,
1490 code_action_providers,
1491 available_code_actions: Default::default(),
1492 code_actions_task: Default::default(),
1493 selection_highlight_task: Default::default(),
1494 document_highlights_task: Default::default(),
1495 linked_editing_range_task: Default::default(),
1496 pending_rename: Default::default(),
1497 searchable: true,
1498 cursor_shape: EditorSettings::get_global(cx)
1499 .cursor_shape
1500 .unwrap_or_default(),
1501 current_line_highlight: None,
1502 autoindent_mode: Some(AutoindentMode::EachLine),
1503 collapse_matches: false,
1504 workspace: None,
1505 input_enabled: true,
1506 use_modal_editing: mode == EditorMode::Full,
1507 read_only: false,
1508 use_autoclose: true,
1509 use_auto_surround: true,
1510 auto_replace_emoji_shortcode: false,
1511 jsx_tag_auto_close_enabled_in_any_buffer: false,
1512 leader_peer_id: None,
1513 remote_id: None,
1514 hover_state: Default::default(),
1515 pending_mouse_down: None,
1516 hovered_link_state: Default::default(),
1517 edit_prediction_provider: None,
1518 active_inline_completion: None,
1519 stale_inline_completion_in_menu: None,
1520 edit_prediction_preview: EditPredictionPreview::Inactive {
1521 released_too_fast: false,
1522 },
1523 inline_diagnostics_enabled: mode == EditorMode::Full,
1524 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1525
1526 gutter_hovered: false,
1527 pixel_position_of_newest_cursor: None,
1528 last_bounds: None,
1529 last_position_map: None,
1530 expect_bounds_change: None,
1531 gutter_dimensions: GutterDimensions::default(),
1532 style: None,
1533 show_cursor_names: false,
1534 hovered_cursors: Default::default(),
1535 next_editor_action_id: EditorActionId::default(),
1536 editor_actions: Rc::default(),
1537 inline_completions_hidden_for_vim_mode: false,
1538 show_inline_completions_override: None,
1539 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1540 edit_prediction_settings: EditPredictionSettings::Disabled,
1541 edit_prediction_indent_conflict: false,
1542 edit_prediction_requires_modifier_in_indent_conflict: true,
1543 custom_context_menu: None,
1544 show_git_blame_gutter: false,
1545 show_git_blame_inline: false,
1546 show_selection_menu: None,
1547 show_git_blame_inline_delay_task: None,
1548 git_blame_inline_tooltip: None,
1549 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1550 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1551 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1552 .session
1553 .restore_unsaved_buffers,
1554 blame: None,
1555 blame_subscription: None,
1556 tasks: Default::default(),
1557
1558 breakpoint_store,
1559 gutter_breakpoint_indicator: (None, None),
1560 _subscriptions: vec![
1561 cx.observe(&buffer, Self::on_buffer_changed),
1562 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1563 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1564 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1565 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1566 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1567 cx.observe_window_activation(window, |editor, window, cx| {
1568 let active = window.is_window_active();
1569 editor.blink_manager.update(cx, |blink_manager, cx| {
1570 if active {
1571 blink_manager.enable(cx);
1572 } else {
1573 blink_manager.disable(cx);
1574 }
1575 });
1576 }),
1577 ],
1578 tasks_update_task: None,
1579 linked_edit_ranges: Default::default(),
1580 in_project_search: false,
1581 previous_search_ranges: None,
1582 breadcrumb_header: None,
1583 focused_block: None,
1584 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1585 addons: HashMap::default(),
1586 registered_buffers: HashMap::default(),
1587 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1588 selection_mark_mode: false,
1589 toggle_fold_multiple_buffers: Task::ready(()),
1590 serialize_selections: Task::ready(()),
1591 serialize_folds: Task::ready(()),
1592 text_style_refinement: None,
1593 load_diff_task: load_uncommitted_diff,
1594 mouse_cursor_hidden: false,
1595 hide_mouse_mode: EditorSettings::get_global(cx)
1596 .hide_mouse
1597 .unwrap_or_default(),
1598 };
1599 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1600 this._subscriptions
1601 .push(cx.observe(breakpoints, |_, _, cx| {
1602 cx.notify();
1603 }));
1604 }
1605 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1606 this._subscriptions.extend(project_subscriptions);
1607 this._subscriptions
1608 .push(cx.subscribe_self(|editor, e: &EditorEvent, cx| {
1609 if let EditorEvent::SelectionsChanged { local } = e {
1610 if *local {
1611 let new_anchor = editor.scroll_manager.anchor();
1612 editor.update_restoration_data(cx, move |data| {
1613 data.scroll_anchor = new_anchor;
1614 });
1615 }
1616 }
1617 }));
1618
1619 this.end_selection(window, cx);
1620 this.scroll_manager.show_scrollbars(window, cx);
1621 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1622
1623 if mode == EditorMode::Full {
1624 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1625 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1626
1627 if this.git_blame_inline_enabled {
1628 this.git_blame_inline_enabled = true;
1629 this.start_git_blame_inline(false, window, cx);
1630 }
1631
1632 this.go_to_active_debug_line(window, cx);
1633
1634 if let Some(buffer) = buffer.read(cx).as_singleton() {
1635 if let Some(project) = this.project.as_ref() {
1636 let handle = project.update(cx, |project, cx| {
1637 project.register_buffer_with_language_servers(&buffer, cx)
1638 });
1639 this.registered_buffers
1640 .insert(buffer.read(cx).remote_id(), handle);
1641 }
1642 }
1643 }
1644
1645 this.report_editor_event("Editor Opened", None, cx);
1646 this
1647 }
1648
1649 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1650 self.mouse_context_menu
1651 .as_ref()
1652 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1653 }
1654
1655 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1656 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1657 }
1658
1659 fn key_context_internal(
1660 &self,
1661 has_active_edit_prediction: bool,
1662 window: &Window,
1663 cx: &App,
1664 ) -> KeyContext {
1665 let mut key_context = KeyContext::new_with_defaults();
1666 key_context.add("Editor");
1667 let mode = match self.mode {
1668 EditorMode::SingleLine { .. } => "single_line",
1669 EditorMode::AutoHeight { .. } => "auto_height",
1670 EditorMode::Full => "full",
1671 };
1672
1673 if EditorSettings::jupyter_enabled(cx) {
1674 key_context.add("jupyter");
1675 }
1676
1677 key_context.set("mode", mode);
1678 if self.pending_rename.is_some() {
1679 key_context.add("renaming");
1680 }
1681
1682 match self.context_menu.borrow().as_ref() {
1683 Some(CodeContextMenu::Completions(_)) => {
1684 key_context.add("menu");
1685 key_context.add("showing_completions");
1686 }
1687 Some(CodeContextMenu::CodeActions(_)) => {
1688 key_context.add("menu");
1689 key_context.add("showing_code_actions")
1690 }
1691 None => {}
1692 }
1693
1694 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1695 if !self.focus_handle(cx).contains_focused(window, cx)
1696 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1697 {
1698 for addon in self.addons.values() {
1699 addon.extend_key_context(&mut key_context, cx)
1700 }
1701 }
1702
1703 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1704 if let Some(extension) = singleton_buffer
1705 .read(cx)
1706 .file()
1707 .and_then(|file| file.path().extension()?.to_str())
1708 {
1709 key_context.set("extension", extension.to_string());
1710 }
1711 } else {
1712 key_context.add("multibuffer");
1713 }
1714
1715 if has_active_edit_prediction {
1716 if self.edit_prediction_in_conflict() {
1717 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1718 } else {
1719 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1720 key_context.add("copilot_suggestion");
1721 }
1722 }
1723
1724 if self.selection_mark_mode {
1725 key_context.add("selection_mode");
1726 }
1727
1728 key_context
1729 }
1730
1731 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1732 self.mouse_cursor_hidden = match origin {
1733 HideMouseCursorOrigin::TypingAction => {
1734 matches!(
1735 self.hide_mouse_mode,
1736 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1737 )
1738 }
1739 HideMouseCursorOrigin::MovementAction => {
1740 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1741 }
1742 };
1743 }
1744
1745 pub fn edit_prediction_in_conflict(&self) -> bool {
1746 if !self.show_edit_predictions_in_menu() {
1747 return false;
1748 }
1749
1750 let showing_completions = self
1751 .context_menu
1752 .borrow()
1753 .as_ref()
1754 .map_or(false, |context| {
1755 matches!(context, CodeContextMenu::Completions(_))
1756 });
1757
1758 showing_completions
1759 || self.edit_prediction_requires_modifier()
1760 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1761 // bindings to insert tab characters.
1762 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1763 }
1764
1765 pub fn accept_edit_prediction_keybind(
1766 &self,
1767 window: &Window,
1768 cx: &App,
1769 ) -> AcceptEditPredictionBinding {
1770 let key_context = self.key_context_internal(true, window, cx);
1771 let in_conflict = self.edit_prediction_in_conflict();
1772
1773 AcceptEditPredictionBinding(
1774 window
1775 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1776 .into_iter()
1777 .filter(|binding| {
1778 !in_conflict
1779 || binding
1780 .keystrokes()
1781 .first()
1782 .map_or(false, |keystroke| keystroke.modifiers.modified())
1783 })
1784 .rev()
1785 .min_by_key(|binding| {
1786 binding
1787 .keystrokes()
1788 .first()
1789 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1790 }),
1791 )
1792 }
1793
1794 pub fn new_file(
1795 workspace: &mut Workspace,
1796 _: &workspace::NewFile,
1797 window: &mut Window,
1798 cx: &mut Context<Workspace>,
1799 ) {
1800 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1801 "Failed to create buffer",
1802 window,
1803 cx,
1804 |e, _, _| match e.error_code() {
1805 ErrorCode::RemoteUpgradeRequired => Some(format!(
1806 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1807 e.error_tag("required").unwrap_or("the latest version")
1808 )),
1809 _ => None,
1810 },
1811 );
1812 }
1813
1814 pub fn new_in_workspace(
1815 workspace: &mut Workspace,
1816 window: &mut Window,
1817 cx: &mut Context<Workspace>,
1818 ) -> Task<Result<Entity<Editor>>> {
1819 let project = workspace.project().clone();
1820 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1821
1822 cx.spawn_in(window, async move |workspace, cx| {
1823 let buffer = create.await?;
1824 workspace.update_in(cx, |workspace, window, cx| {
1825 let editor =
1826 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1827 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1828 editor
1829 })
1830 })
1831 }
1832
1833 fn new_file_vertical(
1834 workspace: &mut Workspace,
1835 _: &workspace::NewFileSplitVertical,
1836 window: &mut Window,
1837 cx: &mut Context<Workspace>,
1838 ) {
1839 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1840 }
1841
1842 fn new_file_horizontal(
1843 workspace: &mut Workspace,
1844 _: &workspace::NewFileSplitHorizontal,
1845 window: &mut Window,
1846 cx: &mut Context<Workspace>,
1847 ) {
1848 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1849 }
1850
1851 fn new_file_in_direction(
1852 workspace: &mut Workspace,
1853 direction: SplitDirection,
1854 window: &mut Window,
1855 cx: &mut Context<Workspace>,
1856 ) {
1857 let project = workspace.project().clone();
1858 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1859
1860 cx.spawn_in(window, async move |workspace, cx| {
1861 let buffer = create.await?;
1862 workspace.update_in(cx, move |workspace, window, cx| {
1863 workspace.split_item(
1864 direction,
1865 Box::new(
1866 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1867 ),
1868 window,
1869 cx,
1870 )
1871 })?;
1872 anyhow::Ok(())
1873 })
1874 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1875 match e.error_code() {
1876 ErrorCode::RemoteUpgradeRequired => Some(format!(
1877 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1878 e.error_tag("required").unwrap_or("the latest version")
1879 )),
1880 _ => None,
1881 }
1882 });
1883 }
1884
1885 pub fn leader_peer_id(&self) -> Option<PeerId> {
1886 self.leader_peer_id
1887 }
1888
1889 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1890 &self.buffer
1891 }
1892
1893 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1894 self.workspace.as_ref()?.0.upgrade()
1895 }
1896
1897 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1898 self.buffer().read(cx).title(cx)
1899 }
1900
1901 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1902 let git_blame_gutter_max_author_length = self
1903 .render_git_blame_gutter(cx)
1904 .then(|| {
1905 if let Some(blame) = self.blame.as_ref() {
1906 let max_author_length =
1907 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1908 Some(max_author_length)
1909 } else {
1910 None
1911 }
1912 })
1913 .flatten();
1914
1915 EditorSnapshot {
1916 mode: self.mode,
1917 show_gutter: self.show_gutter,
1918 show_line_numbers: self.show_line_numbers,
1919 show_git_diff_gutter: self.show_git_diff_gutter,
1920 show_code_actions: self.show_code_actions,
1921 show_runnables: self.show_runnables,
1922 show_breakpoints: self.show_breakpoints,
1923 git_blame_gutter_max_author_length,
1924 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1925 scroll_anchor: self.scroll_manager.anchor(),
1926 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1927 placeholder_text: self.placeholder_text.clone(),
1928 is_focused: self.focus_handle.is_focused(window),
1929 current_line_highlight: self
1930 .current_line_highlight
1931 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1932 gutter_hovered: self.gutter_hovered,
1933 }
1934 }
1935
1936 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1937 self.buffer.read(cx).language_at(point, cx)
1938 }
1939
1940 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1941 self.buffer.read(cx).read(cx).file_at(point).cloned()
1942 }
1943
1944 pub fn active_excerpt(
1945 &self,
1946 cx: &App,
1947 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1948 self.buffer
1949 .read(cx)
1950 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1951 }
1952
1953 pub fn mode(&self) -> EditorMode {
1954 self.mode
1955 }
1956
1957 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1958 self.collaboration_hub.as_deref()
1959 }
1960
1961 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1962 self.collaboration_hub = Some(hub);
1963 }
1964
1965 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1966 self.in_project_search = in_project_search;
1967 }
1968
1969 pub fn set_custom_context_menu(
1970 &mut self,
1971 f: impl 'static
1972 + Fn(
1973 &mut Self,
1974 DisplayPoint,
1975 &mut Window,
1976 &mut Context<Self>,
1977 ) -> Option<Entity<ui::ContextMenu>>,
1978 ) {
1979 self.custom_context_menu = Some(Box::new(f))
1980 }
1981
1982 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1983 self.completion_provider = provider;
1984 }
1985
1986 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1987 self.semantics_provider.clone()
1988 }
1989
1990 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1991 self.semantics_provider = provider;
1992 }
1993
1994 pub fn set_edit_prediction_provider<T>(
1995 &mut self,
1996 provider: Option<Entity<T>>,
1997 window: &mut Window,
1998 cx: &mut Context<Self>,
1999 ) where
2000 T: EditPredictionProvider,
2001 {
2002 self.edit_prediction_provider =
2003 provider.map(|provider| RegisteredInlineCompletionProvider {
2004 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2005 if this.focus_handle.is_focused(window) {
2006 this.update_visible_inline_completion(window, cx);
2007 }
2008 }),
2009 provider: Arc::new(provider),
2010 });
2011 self.update_edit_prediction_settings(cx);
2012 self.refresh_inline_completion(false, false, window, cx);
2013 }
2014
2015 pub fn placeholder_text(&self) -> Option<&str> {
2016 self.placeholder_text.as_deref()
2017 }
2018
2019 pub fn set_placeholder_text(
2020 &mut self,
2021 placeholder_text: impl Into<Arc<str>>,
2022 cx: &mut Context<Self>,
2023 ) {
2024 let placeholder_text = Some(placeholder_text.into());
2025 if self.placeholder_text != placeholder_text {
2026 self.placeholder_text = placeholder_text;
2027 cx.notify();
2028 }
2029 }
2030
2031 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2032 self.cursor_shape = cursor_shape;
2033
2034 // Disrupt blink for immediate user feedback that the cursor shape has changed
2035 self.blink_manager.update(cx, BlinkManager::show_cursor);
2036
2037 cx.notify();
2038 }
2039
2040 pub fn set_current_line_highlight(
2041 &mut self,
2042 current_line_highlight: Option<CurrentLineHighlight>,
2043 ) {
2044 self.current_line_highlight = current_line_highlight;
2045 }
2046
2047 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2048 self.collapse_matches = collapse_matches;
2049 }
2050
2051 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2052 let buffers = self.buffer.read(cx).all_buffers();
2053 let Some(project) = self.project.as_ref() else {
2054 return;
2055 };
2056 project.update(cx, |project, cx| {
2057 for buffer in buffers {
2058 self.registered_buffers
2059 .entry(buffer.read(cx).remote_id())
2060 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2061 }
2062 })
2063 }
2064
2065 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2066 if self.collapse_matches {
2067 return range.start..range.start;
2068 }
2069 range.clone()
2070 }
2071
2072 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2073 if self.display_map.read(cx).clip_at_line_ends != clip {
2074 self.display_map
2075 .update(cx, |map, _| map.clip_at_line_ends = clip);
2076 }
2077 }
2078
2079 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2080 self.input_enabled = input_enabled;
2081 }
2082
2083 pub fn set_inline_completions_hidden_for_vim_mode(
2084 &mut self,
2085 hidden: bool,
2086 window: &mut Window,
2087 cx: &mut Context<Self>,
2088 ) {
2089 if hidden != self.inline_completions_hidden_for_vim_mode {
2090 self.inline_completions_hidden_for_vim_mode = hidden;
2091 if hidden {
2092 self.update_visible_inline_completion(window, cx);
2093 } else {
2094 self.refresh_inline_completion(true, false, window, cx);
2095 }
2096 }
2097 }
2098
2099 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2100 self.menu_inline_completions_policy = value;
2101 }
2102
2103 pub fn set_autoindent(&mut self, autoindent: bool) {
2104 if autoindent {
2105 self.autoindent_mode = Some(AutoindentMode::EachLine);
2106 } else {
2107 self.autoindent_mode = None;
2108 }
2109 }
2110
2111 pub fn read_only(&self, cx: &App) -> bool {
2112 self.read_only || self.buffer.read(cx).read_only()
2113 }
2114
2115 pub fn set_read_only(&mut self, read_only: bool) {
2116 self.read_only = read_only;
2117 }
2118
2119 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2120 self.use_autoclose = autoclose;
2121 }
2122
2123 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2124 self.use_auto_surround = auto_surround;
2125 }
2126
2127 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2128 self.auto_replace_emoji_shortcode = auto_replace;
2129 }
2130
2131 pub fn toggle_edit_predictions(
2132 &mut self,
2133 _: &ToggleEditPrediction,
2134 window: &mut Window,
2135 cx: &mut Context<Self>,
2136 ) {
2137 if self.show_inline_completions_override.is_some() {
2138 self.set_show_edit_predictions(None, window, cx);
2139 } else {
2140 let show_edit_predictions = !self.edit_predictions_enabled();
2141 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2142 }
2143 }
2144
2145 pub fn set_show_edit_predictions(
2146 &mut self,
2147 show_edit_predictions: Option<bool>,
2148 window: &mut Window,
2149 cx: &mut Context<Self>,
2150 ) {
2151 self.show_inline_completions_override = show_edit_predictions;
2152 self.update_edit_prediction_settings(cx);
2153
2154 if let Some(false) = show_edit_predictions {
2155 self.discard_inline_completion(false, cx);
2156 } else {
2157 self.refresh_inline_completion(false, true, window, cx);
2158 }
2159 }
2160
2161 fn inline_completions_disabled_in_scope(
2162 &self,
2163 buffer: &Entity<Buffer>,
2164 buffer_position: language::Anchor,
2165 cx: &App,
2166 ) -> bool {
2167 let snapshot = buffer.read(cx).snapshot();
2168 let settings = snapshot.settings_at(buffer_position, cx);
2169
2170 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2171 return false;
2172 };
2173
2174 scope.override_name().map_or(false, |scope_name| {
2175 settings
2176 .edit_predictions_disabled_in
2177 .iter()
2178 .any(|s| s == scope_name)
2179 })
2180 }
2181
2182 pub fn set_use_modal_editing(&mut self, to: bool) {
2183 self.use_modal_editing = to;
2184 }
2185
2186 pub fn use_modal_editing(&self) -> bool {
2187 self.use_modal_editing
2188 }
2189
2190 fn selections_did_change(
2191 &mut self,
2192 local: bool,
2193 old_cursor_position: &Anchor,
2194 show_completions: bool,
2195 window: &mut Window,
2196 cx: &mut Context<Self>,
2197 ) {
2198 window.invalidate_character_coordinates();
2199
2200 // Copy selections to primary selection buffer
2201 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2202 if local {
2203 let selections = self.selections.all::<usize>(cx);
2204 let buffer_handle = self.buffer.read(cx).read(cx);
2205
2206 let mut text = String::new();
2207 for (index, selection) in selections.iter().enumerate() {
2208 let text_for_selection = buffer_handle
2209 .text_for_range(selection.start..selection.end)
2210 .collect::<String>();
2211
2212 text.push_str(&text_for_selection);
2213 if index != selections.len() - 1 {
2214 text.push('\n');
2215 }
2216 }
2217
2218 if !text.is_empty() {
2219 cx.write_to_primary(ClipboardItem::new_string(text));
2220 }
2221 }
2222
2223 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2224 self.buffer.update(cx, |buffer, cx| {
2225 buffer.set_active_selections(
2226 &self.selections.disjoint_anchors(),
2227 self.selections.line_mode,
2228 self.cursor_shape,
2229 cx,
2230 )
2231 });
2232 }
2233 let display_map = self
2234 .display_map
2235 .update(cx, |display_map, cx| display_map.snapshot(cx));
2236 let buffer = &display_map.buffer_snapshot;
2237 self.add_selections_state = None;
2238 self.select_next_state = None;
2239 self.select_prev_state = None;
2240 self.select_syntax_node_history.try_clear();
2241 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2242 self.snippet_stack
2243 .invalidate(&self.selections.disjoint_anchors(), buffer);
2244 self.take_rename(false, window, cx);
2245
2246 let new_cursor_position = self.selections.newest_anchor().head();
2247
2248 self.push_to_nav_history(
2249 *old_cursor_position,
2250 Some(new_cursor_position.to_point(buffer)),
2251 false,
2252 cx,
2253 );
2254
2255 if local {
2256 let new_cursor_position = self.selections.newest_anchor().head();
2257 let mut context_menu = self.context_menu.borrow_mut();
2258 let completion_menu = match context_menu.as_ref() {
2259 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2260 _ => {
2261 *context_menu = None;
2262 None
2263 }
2264 };
2265 if let Some(buffer_id) = new_cursor_position.buffer_id {
2266 if !self.registered_buffers.contains_key(&buffer_id) {
2267 if let Some(project) = self.project.as_ref() {
2268 project.update(cx, |project, cx| {
2269 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2270 return;
2271 };
2272 self.registered_buffers.insert(
2273 buffer_id,
2274 project.register_buffer_with_language_servers(&buffer, cx),
2275 );
2276 })
2277 }
2278 }
2279 }
2280
2281 if let Some(completion_menu) = completion_menu {
2282 let cursor_position = new_cursor_position.to_offset(buffer);
2283 let (word_range, kind) =
2284 buffer.surrounding_word(completion_menu.initial_position, true);
2285 if kind == Some(CharKind::Word)
2286 && word_range.to_inclusive().contains(&cursor_position)
2287 {
2288 let mut completion_menu = completion_menu.clone();
2289 drop(context_menu);
2290
2291 let query = Self::completion_query(buffer, cursor_position);
2292 cx.spawn(async move |this, cx| {
2293 completion_menu
2294 .filter(query.as_deref(), cx.background_executor().clone())
2295 .await;
2296
2297 this.update(cx, |this, cx| {
2298 let mut context_menu = this.context_menu.borrow_mut();
2299 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2300 else {
2301 return;
2302 };
2303
2304 if menu.id > completion_menu.id {
2305 return;
2306 }
2307
2308 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2309 drop(context_menu);
2310 cx.notify();
2311 })
2312 })
2313 .detach();
2314
2315 if show_completions {
2316 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2317 }
2318 } else {
2319 drop(context_menu);
2320 self.hide_context_menu(window, cx);
2321 }
2322 } else {
2323 drop(context_menu);
2324 }
2325
2326 hide_hover(self, cx);
2327
2328 if old_cursor_position.to_display_point(&display_map).row()
2329 != new_cursor_position.to_display_point(&display_map).row()
2330 {
2331 self.available_code_actions.take();
2332 }
2333 self.refresh_code_actions(window, cx);
2334 self.refresh_document_highlights(cx);
2335 self.refresh_selected_text_highlights(window, cx);
2336 refresh_matching_bracket_highlights(self, window, cx);
2337 self.update_visible_inline_completion(window, cx);
2338 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2339 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2340 if self.git_blame_inline_enabled {
2341 self.start_inline_blame_timer(window, cx);
2342 }
2343 }
2344
2345 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2346 cx.emit(EditorEvent::SelectionsChanged { local });
2347
2348 let selections = &self.selections.disjoint;
2349 if selections.len() == 1 {
2350 cx.emit(SearchEvent::ActiveMatchChanged)
2351 }
2352 if local && self.is_singleton(cx) {
2353 let inmemory_selections = selections.iter().map(|s| s.range()).collect();
2354 self.update_restoration_data(cx, |data| {
2355 data.selections = inmemory_selections;
2356 });
2357
2358 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2359 {
2360 if let Some(workspace_id) =
2361 self.workspace.as_ref().and_then(|workspace| workspace.1)
2362 {
2363 let snapshot = self.buffer().read(cx).snapshot(cx);
2364 let selections = selections.clone();
2365 let background_executor = cx.background_executor().clone();
2366 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2367 self.serialize_selections = cx.background_spawn(async move {
2368 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2369 let db_selections = selections
2370 .iter()
2371 .map(|selection| {
2372 (
2373 selection.start.to_offset(&snapshot),
2374 selection.end.to_offset(&snapshot),
2375 )
2376 })
2377 .collect();
2378
2379 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2380 .await
2381 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2382 .log_err();
2383 });
2384 }
2385 }
2386 }
2387
2388 cx.notify();
2389 }
2390
2391 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2392 if !self.is_singleton(cx)
2393 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2394 {
2395 return;
2396 }
2397
2398 let snapshot = self.buffer().read(cx).snapshot(cx);
2399 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2400 display_map
2401 .snapshot(cx)
2402 .folds_in_range(0..snapshot.len())
2403 .map(|fold| fold.range.deref().clone())
2404 .collect()
2405 });
2406 self.update_restoration_data(cx, |data| {
2407 data.folds = inmemory_folds;
2408 });
2409
2410 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2411 return;
2412 };
2413 let background_executor = cx.background_executor().clone();
2414 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2415 let db_folds = self.display_map.update(cx, |display_map, cx| {
2416 display_map
2417 .snapshot(cx)
2418 .folds_in_range(0..snapshot.len())
2419 .map(|fold| {
2420 (
2421 fold.range.start.to_offset(&snapshot),
2422 fold.range.end.to_offset(&snapshot),
2423 )
2424 })
2425 .collect()
2426 });
2427 self.serialize_folds = cx.background_spawn(async move {
2428 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2429 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2430 .await
2431 .with_context(|| format!("persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"))
2432 .log_err();
2433 });
2434 }
2435
2436 pub fn sync_selections(
2437 &mut self,
2438 other: Entity<Editor>,
2439 cx: &mut Context<Self>,
2440 ) -> gpui::Subscription {
2441 let other_selections = other.read(cx).selections.disjoint.to_vec();
2442 self.selections.change_with(cx, |selections| {
2443 selections.select_anchors(other_selections);
2444 });
2445
2446 let other_subscription =
2447 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2448 EditorEvent::SelectionsChanged { local: true } => {
2449 let other_selections = other.read(cx).selections.disjoint.to_vec();
2450 if other_selections.is_empty() {
2451 return;
2452 }
2453 this.selections.change_with(cx, |selections| {
2454 selections.select_anchors(other_selections);
2455 });
2456 }
2457 _ => {}
2458 });
2459
2460 let this_subscription =
2461 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2462 EditorEvent::SelectionsChanged { local: true } => {
2463 let these_selections = this.selections.disjoint.to_vec();
2464 if these_selections.is_empty() {
2465 return;
2466 }
2467 other.update(cx, |other_editor, cx| {
2468 other_editor.selections.change_with(cx, |selections| {
2469 selections.select_anchors(these_selections);
2470 })
2471 });
2472 }
2473 _ => {}
2474 });
2475
2476 Subscription::join(other_subscription, this_subscription)
2477 }
2478
2479 pub fn change_selections<R>(
2480 &mut self,
2481 autoscroll: Option<Autoscroll>,
2482 window: &mut Window,
2483 cx: &mut Context<Self>,
2484 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2485 ) -> R {
2486 self.change_selections_inner(autoscroll, true, window, cx, change)
2487 }
2488
2489 fn change_selections_inner<R>(
2490 &mut self,
2491 autoscroll: Option<Autoscroll>,
2492 request_completions: bool,
2493 window: &mut Window,
2494 cx: &mut Context<Self>,
2495 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2496 ) -> R {
2497 let old_cursor_position = self.selections.newest_anchor().head();
2498 self.push_to_selection_history();
2499
2500 let (changed, result) = self.selections.change_with(cx, change);
2501
2502 if changed {
2503 if let Some(autoscroll) = autoscroll {
2504 self.request_autoscroll(autoscroll, cx);
2505 }
2506 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2507
2508 if self.should_open_signature_help_automatically(
2509 &old_cursor_position,
2510 self.signature_help_state.backspace_pressed(),
2511 cx,
2512 ) {
2513 self.show_signature_help(&ShowSignatureHelp, window, cx);
2514 }
2515 self.signature_help_state.set_backspace_pressed(false);
2516 }
2517
2518 result
2519 }
2520
2521 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2522 where
2523 I: IntoIterator<Item = (Range<S>, T)>,
2524 S: ToOffset,
2525 T: Into<Arc<str>>,
2526 {
2527 if self.read_only(cx) {
2528 return;
2529 }
2530
2531 self.buffer
2532 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2533 }
2534
2535 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2536 where
2537 I: IntoIterator<Item = (Range<S>, T)>,
2538 S: ToOffset,
2539 T: Into<Arc<str>>,
2540 {
2541 if self.read_only(cx) {
2542 return;
2543 }
2544
2545 self.buffer.update(cx, |buffer, cx| {
2546 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2547 });
2548 }
2549
2550 pub fn edit_with_block_indent<I, S, T>(
2551 &mut self,
2552 edits: I,
2553 original_indent_columns: Vec<Option<u32>>,
2554 cx: &mut Context<Self>,
2555 ) where
2556 I: IntoIterator<Item = (Range<S>, T)>,
2557 S: ToOffset,
2558 T: Into<Arc<str>>,
2559 {
2560 if self.read_only(cx) {
2561 return;
2562 }
2563
2564 self.buffer.update(cx, |buffer, cx| {
2565 buffer.edit(
2566 edits,
2567 Some(AutoindentMode::Block {
2568 original_indent_columns,
2569 }),
2570 cx,
2571 )
2572 });
2573 }
2574
2575 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2576 self.hide_context_menu(window, cx);
2577
2578 match phase {
2579 SelectPhase::Begin {
2580 position,
2581 add,
2582 click_count,
2583 } => self.begin_selection(position, add, click_count, window, cx),
2584 SelectPhase::BeginColumnar {
2585 position,
2586 goal_column,
2587 reset,
2588 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2589 SelectPhase::Extend {
2590 position,
2591 click_count,
2592 } => self.extend_selection(position, click_count, window, cx),
2593 SelectPhase::Update {
2594 position,
2595 goal_column,
2596 scroll_delta,
2597 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2598 SelectPhase::End => self.end_selection(window, cx),
2599 }
2600 }
2601
2602 fn extend_selection(
2603 &mut self,
2604 position: DisplayPoint,
2605 click_count: usize,
2606 window: &mut Window,
2607 cx: &mut Context<Self>,
2608 ) {
2609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2610 let tail = self.selections.newest::<usize>(cx).tail();
2611 self.begin_selection(position, false, click_count, window, cx);
2612
2613 let position = position.to_offset(&display_map, Bias::Left);
2614 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2615
2616 let mut pending_selection = self
2617 .selections
2618 .pending_anchor()
2619 .expect("extend_selection not called with pending selection");
2620 if position >= tail {
2621 pending_selection.start = tail_anchor;
2622 } else {
2623 pending_selection.end = tail_anchor;
2624 pending_selection.reversed = true;
2625 }
2626
2627 let mut pending_mode = self.selections.pending_mode().unwrap();
2628 match &mut pending_mode {
2629 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2630 _ => {}
2631 }
2632
2633 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2634 s.set_pending(pending_selection, pending_mode)
2635 });
2636 }
2637
2638 fn begin_selection(
2639 &mut self,
2640 position: DisplayPoint,
2641 add: bool,
2642 click_count: usize,
2643 window: &mut Window,
2644 cx: &mut Context<Self>,
2645 ) {
2646 if !self.focus_handle.is_focused(window) {
2647 self.last_focused_descendant = None;
2648 window.focus(&self.focus_handle);
2649 }
2650
2651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2652 let buffer = &display_map.buffer_snapshot;
2653 let newest_selection = self.selections.newest_anchor().clone();
2654 let position = display_map.clip_point(position, Bias::Left);
2655
2656 let start;
2657 let end;
2658 let mode;
2659 let mut auto_scroll;
2660 match click_count {
2661 1 => {
2662 start = buffer.anchor_before(position.to_point(&display_map));
2663 end = start;
2664 mode = SelectMode::Character;
2665 auto_scroll = true;
2666 }
2667 2 => {
2668 let range = movement::surrounding_word(&display_map, position);
2669 start = buffer.anchor_before(range.start.to_point(&display_map));
2670 end = buffer.anchor_before(range.end.to_point(&display_map));
2671 mode = SelectMode::Word(start..end);
2672 auto_scroll = true;
2673 }
2674 3 => {
2675 let position = display_map
2676 .clip_point(position, Bias::Left)
2677 .to_point(&display_map);
2678 let line_start = display_map.prev_line_boundary(position).0;
2679 let next_line_start = buffer.clip_point(
2680 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2681 Bias::Left,
2682 );
2683 start = buffer.anchor_before(line_start);
2684 end = buffer.anchor_before(next_line_start);
2685 mode = SelectMode::Line(start..end);
2686 auto_scroll = true;
2687 }
2688 _ => {
2689 start = buffer.anchor_before(0);
2690 end = buffer.anchor_before(buffer.len());
2691 mode = SelectMode::All;
2692 auto_scroll = false;
2693 }
2694 }
2695 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2696
2697 let point_to_delete: Option<usize> = {
2698 let selected_points: Vec<Selection<Point>> =
2699 self.selections.disjoint_in_range(start..end, cx);
2700
2701 if !add || click_count > 1 {
2702 None
2703 } else if !selected_points.is_empty() {
2704 Some(selected_points[0].id)
2705 } else {
2706 let clicked_point_already_selected =
2707 self.selections.disjoint.iter().find(|selection| {
2708 selection.start.to_point(buffer) == start.to_point(buffer)
2709 || selection.end.to_point(buffer) == end.to_point(buffer)
2710 });
2711
2712 clicked_point_already_selected.map(|selection| selection.id)
2713 }
2714 };
2715
2716 let selections_count = self.selections.count();
2717
2718 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2719 if let Some(point_to_delete) = point_to_delete {
2720 s.delete(point_to_delete);
2721
2722 if selections_count == 1 {
2723 s.set_pending_anchor_range(start..end, mode);
2724 }
2725 } else {
2726 if !add {
2727 s.clear_disjoint();
2728 } else if click_count > 1 {
2729 s.delete(newest_selection.id)
2730 }
2731
2732 s.set_pending_anchor_range(start..end, mode);
2733 }
2734 });
2735 }
2736
2737 fn begin_columnar_selection(
2738 &mut self,
2739 position: DisplayPoint,
2740 goal_column: u32,
2741 reset: bool,
2742 window: &mut Window,
2743 cx: &mut Context<Self>,
2744 ) {
2745 if !self.focus_handle.is_focused(window) {
2746 self.last_focused_descendant = None;
2747 window.focus(&self.focus_handle);
2748 }
2749
2750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2751
2752 if reset {
2753 let pointer_position = display_map
2754 .buffer_snapshot
2755 .anchor_before(position.to_point(&display_map));
2756
2757 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2758 s.clear_disjoint();
2759 s.set_pending_anchor_range(
2760 pointer_position..pointer_position,
2761 SelectMode::Character,
2762 );
2763 });
2764 }
2765
2766 let tail = self.selections.newest::<Point>(cx).tail();
2767 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2768
2769 if !reset {
2770 self.select_columns(
2771 tail.to_display_point(&display_map),
2772 position,
2773 goal_column,
2774 &display_map,
2775 window,
2776 cx,
2777 );
2778 }
2779 }
2780
2781 fn update_selection(
2782 &mut self,
2783 position: DisplayPoint,
2784 goal_column: u32,
2785 scroll_delta: gpui::Point<f32>,
2786 window: &mut Window,
2787 cx: &mut Context<Self>,
2788 ) {
2789 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2790
2791 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2792 let tail = tail.to_display_point(&display_map);
2793 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2794 } else if let Some(mut pending) = self.selections.pending_anchor() {
2795 let buffer = self.buffer.read(cx).snapshot(cx);
2796 let head;
2797 let tail;
2798 let mode = self.selections.pending_mode().unwrap();
2799 match &mode {
2800 SelectMode::Character => {
2801 head = position.to_point(&display_map);
2802 tail = pending.tail().to_point(&buffer);
2803 }
2804 SelectMode::Word(original_range) => {
2805 let original_display_range = original_range.start.to_display_point(&display_map)
2806 ..original_range.end.to_display_point(&display_map);
2807 let original_buffer_range = original_display_range.start.to_point(&display_map)
2808 ..original_display_range.end.to_point(&display_map);
2809 if movement::is_inside_word(&display_map, position)
2810 || original_display_range.contains(&position)
2811 {
2812 let word_range = movement::surrounding_word(&display_map, position);
2813 if word_range.start < original_display_range.start {
2814 head = word_range.start.to_point(&display_map);
2815 } else {
2816 head = word_range.end.to_point(&display_map);
2817 }
2818 } else {
2819 head = position.to_point(&display_map);
2820 }
2821
2822 if head <= original_buffer_range.start {
2823 tail = original_buffer_range.end;
2824 } else {
2825 tail = original_buffer_range.start;
2826 }
2827 }
2828 SelectMode::Line(original_range) => {
2829 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2830
2831 let position = display_map
2832 .clip_point(position, Bias::Left)
2833 .to_point(&display_map);
2834 let line_start = display_map.prev_line_boundary(position).0;
2835 let next_line_start = buffer.clip_point(
2836 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2837 Bias::Left,
2838 );
2839
2840 if line_start < original_range.start {
2841 head = line_start
2842 } else {
2843 head = next_line_start
2844 }
2845
2846 if head <= original_range.start {
2847 tail = original_range.end;
2848 } else {
2849 tail = original_range.start;
2850 }
2851 }
2852 SelectMode::All => {
2853 return;
2854 }
2855 };
2856
2857 if head < tail {
2858 pending.start = buffer.anchor_before(head);
2859 pending.end = buffer.anchor_before(tail);
2860 pending.reversed = true;
2861 } else {
2862 pending.start = buffer.anchor_before(tail);
2863 pending.end = buffer.anchor_before(head);
2864 pending.reversed = false;
2865 }
2866
2867 self.change_selections(None, window, cx, |s| {
2868 s.set_pending(pending, mode);
2869 });
2870 } else {
2871 log::error!("update_selection dispatched with no pending selection");
2872 return;
2873 }
2874
2875 self.apply_scroll_delta(scroll_delta, window, cx);
2876 cx.notify();
2877 }
2878
2879 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2880 self.columnar_selection_tail.take();
2881 if self.selections.pending_anchor().is_some() {
2882 let selections = self.selections.all::<usize>(cx);
2883 self.change_selections(None, window, cx, |s| {
2884 s.select(selections);
2885 s.clear_pending();
2886 });
2887 }
2888 }
2889
2890 fn select_columns(
2891 &mut self,
2892 tail: DisplayPoint,
2893 head: DisplayPoint,
2894 goal_column: u32,
2895 display_map: &DisplaySnapshot,
2896 window: &mut Window,
2897 cx: &mut Context<Self>,
2898 ) {
2899 let start_row = cmp::min(tail.row(), head.row());
2900 let end_row = cmp::max(tail.row(), head.row());
2901 let start_column = cmp::min(tail.column(), goal_column);
2902 let end_column = cmp::max(tail.column(), goal_column);
2903 let reversed = start_column < tail.column();
2904
2905 let selection_ranges = (start_row.0..=end_row.0)
2906 .map(DisplayRow)
2907 .filter_map(|row| {
2908 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2909 let start = display_map
2910 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2911 .to_point(display_map);
2912 let end = display_map
2913 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2914 .to_point(display_map);
2915 if reversed {
2916 Some(end..start)
2917 } else {
2918 Some(start..end)
2919 }
2920 } else {
2921 None
2922 }
2923 })
2924 .collect::<Vec<_>>();
2925
2926 self.change_selections(None, window, cx, |s| {
2927 s.select_ranges(selection_ranges);
2928 });
2929 cx.notify();
2930 }
2931
2932 pub fn has_pending_nonempty_selection(&self) -> bool {
2933 let pending_nonempty_selection = match self.selections.pending_anchor() {
2934 Some(Selection { start, end, .. }) => start != end,
2935 None => false,
2936 };
2937
2938 pending_nonempty_selection
2939 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2940 }
2941
2942 pub fn has_pending_selection(&self) -> bool {
2943 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2944 }
2945
2946 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2947 self.selection_mark_mode = false;
2948
2949 if self.clear_expanded_diff_hunks(cx) {
2950 cx.notify();
2951 return;
2952 }
2953 if self.dismiss_menus_and_popups(true, window, cx) {
2954 return;
2955 }
2956
2957 if self.mode == EditorMode::Full
2958 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2959 {
2960 return;
2961 }
2962
2963 cx.propagate();
2964 }
2965
2966 pub fn dismiss_menus_and_popups(
2967 &mut self,
2968 is_user_requested: bool,
2969 window: &mut Window,
2970 cx: &mut Context<Self>,
2971 ) -> bool {
2972 if self.take_rename(false, window, cx).is_some() {
2973 return true;
2974 }
2975
2976 if hide_hover(self, cx) {
2977 return true;
2978 }
2979
2980 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2981 return true;
2982 }
2983
2984 if self.hide_context_menu(window, cx).is_some() {
2985 return true;
2986 }
2987
2988 if self.mouse_context_menu.take().is_some() {
2989 return true;
2990 }
2991
2992 if is_user_requested && self.discard_inline_completion(true, cx) {
2993 return true;
2994 }
2995
2996 if self.snippet_stack.pop().is_some() {
2997 return true;
2998 }
2999
3000 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3001 self.dismiss_diagnostics(cx);
3002 return true;
3003 }
3004
3005 false
3006 }
3007
3008 fn linked_editing_ranges_for(
3009 &self,
3010 selection: Range<text::Anchor>,
3011 cx: &App,
3012 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3013 if self.linked_edit_ranges.is_empty() {
3014 return None;
3015 }
3016 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3017 selection.end.buffer_id.and_then(|end_buffer_id| {
3018 if selection.start.buffer_id != Some(end_buffer_id) {
3019 return None;
3020 }
3021 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3022 let snapshot = buffer.read(cx).snapshot();
3023 self.linked_edit_ranges
3024 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3025 .map(|ranges| (ranges, snapshot, buffer))
3026 })?;
3027 use text::ToOffset as TO;
3028 // find offset from the start of current range to current cursor position
3029 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3030
3031 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3032 let start_difference = start_offset - start_byte_offset;
3033 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3034 let end_difference = end_offset - start_byte_offset;
3035 // Current range has associated linked ranges.
3036 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3037 for range in linked_ranges.iter() {
3038 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3039 let end_offset = start_offset + end_difference;
3040 let start_offset = start_offset + start_difference;
3041 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3042 continue;
3043 }
3044 if self.selections.disjoint_anchor_ranges().any(|s| {
3045 if s.start.buffer_id != selection.start.buffer_id
3046 || s.end.buffer_id != selection.end.buffer_id
3047 {
3048 return false;
3049 }
3050 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3051 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3052 }) {
3053 continue;
3054 }
3055 let start = buffer_snapshot.anchor_after(start_offset);
3056 let end = buffer_snapshot.anchor_after(end_offset);
3057 linked_edits
3058 .entry(buffer.clone())
3059 .or_default()
3060 .push(start..end);
3061 }
3062 Some(linked_edits)
3063 }
3064
3065 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3066 let text: Arc<str> = text.into();
3067
3068 if self.read_only(cx) {
3069 return;
3070 }
3071
3072 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3073
3074 let selections = self.selections.all_adjusted(cx);
3075 let mut bracket_inserted = false;
3076 let mut edits = Vec::new();
3077 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3078 let mut new_selections = Vec::with_capacity(selections.len());
3079 let mut new_autoclose_regions = Vec::new();
3080 let snapshot = self.buffer.read(cx).read(cx);
3081
3082 for (selection, autoclose_region) in
3083 self.selections_with_autoclose_regions(selections, &snapshot)
3084 {
3085 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3086 // Determine if the inserted text matches the opening or closing
3087 // bracket of any of this language's bracket pairs.
3088 let mut bracket_pair = None;
3089 let mut is_bracket_pair_start = false;
3090 let mut is_bracket_pair_end = false;
3091 if !text.is_empty() {
3092 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3093 // and they are removing the character that triggered IME popup.
3094 for (pair, enabled) in scope.brackets() {
3095 if !pair.close && !pair.surround {
3096 continue;
3097 }
3098
3099 if enabled && pair.start.ends_with(text.as_ref()) {
3100 let prefix_len = pair.start.len() - text.len();
3101 let preceding_text_matches_prefix = prefix_len == 0
3102 || (selection.start.column >= (prefix_len as u32)
3103 && snapshot.contains_str_at(
3104 Point::new(
3105 selection.start.row,
3106 selection.start.column - (prefix_len as u32),
3107 ),
3108 &pair.start[..prefix_len],
3109 ));
3110 if preceding_text_matches_prefix {
3111 bracket_pair = Some(pair.clone());
3112 is_bracket_pair_start = true;
3113 break;
3114 }
3115 }
3116 if pair.end.as_str() == text.as_ref() {
3117 bracket_pair = Some(pair.clone());
3118 is_bracket_pair_end = true;
3119 break;
3120 }
3121 }
3122 }
3123
3124 if let Some(bracket_pair) = bracket_pair {
3125 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3126 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3127 let auto_surround =
3128 self.use_auto_surround && snapshot_settings.use_auto_surround;
3129 if selection.is_empty() {
3130 if is_bracket_pair_start {
3131 // If the inserted text is a suffix of an opening bracket and the
3132 // selection is preceded by the rest of the opening bracket, then
3133 // insert the closing bracket.
3134 let following_text_allows_autoclose = snapshot
3135 .chars_at(selection.start)
3136 .next()
3137 .map_or(true, |c| scope.should_autoclose_before(c));
3138
3139 let preceding_text_allows_autoclose = selection.start.column == 0
3140 || snapshot.reversed_chars_at(selection.start).next().map_or(
3141 true,
3142 |c| {
3143 bracket_pair.start != bracket_pair.end
3144 || !snapshot
3145 .char_classifier_at(selection.start)
3146 .is_word(c)
3147 },
3148 );
3149
3150 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3151 && bracket_pair.start.len() == 1
3152 {
3153 let target = bracket_pair.start.chars().next().unwrap();
3154 let current_line_count = snapshot
3155 .reversed_chars_at(selection.start)
3156 .take_while(|&c| c != '\n')
3157 .filter(|&c| c == target)
3158 .count();
3159 current_line_count % 2 == 1
3160 } else {
3161 false
3162 };
3163
3164 if autoclose
3165 && bracket_pair.close
3166 && following_text_allows_autoclose
3167 && preceding_text_allows_autoclose
3168 && !is_closing_quote
3169 {
3170 let anchor = snapshot.anchor_before(selection.end);
3171 new_selections.push((selection.map(|_| anchor), text.len()));
3172 new_autoclose_regions.push((
3173 anchor,
3174 text.len(),
3175 selection.id,
3176 bracket_pair.clone(),
3177 ));
3178 edits.push((
3179 selection.range(),
3180 format!("{}{}", text, bracket_pair.end).into(),
3181 ));
3182 bracket_inserted = true;
3183 continue;
3184 }
3185 }
3186
3187 if let Some(region) = autoclose_region {
3188 // If the selection is followed by an auto-inserted closing bracket,
3189 // then don't insert that closing bracket again; just move the selection
3190 // past the closing bracket.
3191 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3192 && text.as_ref() == region.pair.end.as_str();
3193 if should_skip {
3194 let anchor = snapshot.anchor_after(selection.end);
3195 new_selections
3196 .push((selection.map(|_| anchor), region.pair.end.len()));
3197 continue;
3198 }
3199 }
3200
3201 let always_treat_brackets_as_autoclosed = snapshot
3202 .language_settings_at(selection.start, cx)
3203 .always_treat_brackets_as_autoclosed;
3204 if always_treat_brackets_as_autoclosed
3205 && is_bracket_pair_end
3206 && snapshot.contains_str_at(selection.end, text.as_ref())
3207 {
3208 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3209 // and the inserted text is a closing bracket and the selection is followed
3210 // by the closing bracket then move the selection past the closing bracket.
3211 let anchor = snapshot.anchor_after(selection.end);
3212 new_selections.push((selection.map(|_| anchor), text.len()));
3213 continue;
3214 }
3215 }
3216 // If an opening bracket is 1 character long and is typed while
3217 // text is selected, then surround that text with the bracket pair.
3218 else if auto_surround
3219 && bracket_pair.surround
3220 && is_bracket_pair_start
3221 && bracket_pair.start.chars().count() == 1
3222 {
3223 edits.push((selection.start..selection.start, text.clone()));
3224 edits.push((
3225 selection.end..selection.end,
3226 bracket_pair.end.as_str().into(),
3227 ));
3228 bracket_inserted = true;
3229 new_selections.push((
3230 Selection {
3231 id: selection.id,
3232 start: snapshot.anchor_after(selection.start),
3233 end: snapshot.anchor_before(selection.end),
3234 reversed: selection.reversed,
3235 goal: selection.goal,
3236 },
3237 0,
3238 ));
3239 continue;
3240 }
3241 }
3242 }
3243
3244 if self.auto_replace_emoji_shortcode
3245 && selection.is_empty()
3246 && text.as_ref().ends_with(':')
3247 {
3248 if let Some(possible_emoji_short_code) =
3249 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3250 {
3251 if !possible_emoji_short_code.is_empty() {
3252 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3253 let emoji_shortcode_start = Point::new(
3254 selection.start.row,
3255 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3256 );
3257
3258 // Remove shortcode from buffer
3259 edits.push((
3260 emoji_shortcode_start..selection.start,
3261 "".to_string().into(),
3262 ));
3263 new_selections.push((
3264 Selection {
3265 id: selection.id,
3266 start: snapshot.anchor_after(emoji_shortcode_start),
3267 end: snapshot.anchor_before(selection.start),
3268 reversed: selection.reversed,
3269 goal: selection.goal,
3270 },
3271 0,
3272 ));
3273
3274 // Insert emoji
3275 let selection_start_anchor = snapshot.anchor_after(selection.start);
3276 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3277 edits.push((selection.start..selection.end, emoji.to_string().into()));
3278
3279 continue;
3280 }
3281 }
3282 }
3283 }
3284
3285 // If not handling any auto-close operation, then just replace the selected
3286 // text with the given input and move the selection to the end of the
3287 // newly inserted text.
3288 let anchor = snapshot.anchor_after(selection.end);
3289 if !self.linked_edit_ranges.is_empty() {
3290 let start_anchor = snapshot.anchor_before(selection.start);
3291
3292 let is_word_char = text.chars().next().map_or(true, |char| {
3293 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3294 classifier.is_word(char)
3295 });
3296
3297 if is_word_char {
3298 if let Some(ranges) = self
3299 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3300 {
3301 for (buffer, edits) in ranges {
3302 linked_edits
3303 .entry(buffer.clone())
3304 .or_default()
3305 .extend(edits.into_iter().map(|range| (range, text.clone())));
3306 }
3307 }
3308 }
3309 }
3310
3311 new_selections.push((selection.map(|_| anchor), 0));
3312 edits.push((selection.start..selection.end, text.clone()));
3313 }
3314
3315 drop(snapshot);
3316
3317 self.transact(window, cx, |this, window, cx| {
3318 let initial_buffer_versions =
3319 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3320
3321 this.buffer.update(cx, |buffer, cx| {
3322 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3323 });
3324 for (buffer, edits) in linked_edits {
3325 buffer.update(cx, |buffer, cx| {
3326 let snapshot = buffer.snapshot();
3327 let edits = edits
3328 .into_iter()
3329 .map(|(range, text)| {
3330 use text::ToPoint as TP;
3331 let end_point = TP::to_point(&range.end, &snapshot);
3332 let start_point = TP::to_point(&range.start, &snapshot);
3333 (start_point..end_point, text)
3334 })
3335 .sorted_by_key(|(range, _)| range.start);
3336 buffer.edit(edits, None, cx);
3337 })
3338 }
3339 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3340 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3341 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3342 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3343 .zip(new_selection_deltas)
3344 .map(|(selection, delta)| Selection {
3345 id: selection.id,
3346 start: selection.start + delta,
3347 end: selection.end + delta,
3348 reversed: selection.reversed,
3349 goal: SelectionGoal::None,
3350 })
3351 .collect::<Vec<_>>();
3352
3353 let mut i = 0;
3354 for (position, delta, selection_id, pair) in new_autoclose_regions {
3355 let position = position.to_offset(&map.buffer_snapshot) + delta;
3356 let start = map.buffer_snapshot.anchor_before(position);
3357 let end = map.buffer_snapshot.anchor_after(position);
3358 while let Some(existing_state) = this.autoclose_regions.get(i) {
3359 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3360 Ordering::Less => i += 1,
3361 Ordering::Greater => break,
3362 Ordering::Equal => {
3363 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3364 Ordering::Less => i += 1,
3365 Ordering::Equal => break,
3366 Ordering::Greater => break,
3367 }
3368 }
3369 }
3370 }
3371 this.autoclose_regions.insert(
3372 i,
3373 AutocloseRegion {
3374 selection_id,
3375 range: start..end,
3376 pair,
3377 },
3378 );
3379 }
3380
3381 let had_active_inline_completion = this.has_active_inline_completion();
3382 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3383 s.select(new_selections)
3384 });
3385
3386 if !bracket_inserted {
3387 if let Some(on_type_format_task) =
3388 this.trigger_on_type_formatting(text.to_string(), window, cx)
3389 {
3390 on_type_format_task.detach_and_log_err(cx);
3391 }
3392 }
3393
3394 let editor_settings = EditorSettings::get_global(cx);
3395 if bracket_inserted
3396 && (editor_settings.auto_signature_help
3397 || editor_settings.show_signature_help_after_edits)
3398 {
3399 this.show_signature_help(&ShowSignatureHelp, window, cx);
3400 }
3401
3402 let trigger_in_words =
3403 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3404 if this.hard_wrap.is_some() {
3405 let latest: Range<Point> = this.selections.newest(cx).range();
3406 if latest.is_empty()
3407 && this
3408 .buffer()
3409 .read(cx)
3410 .snapshot(cx)
3411 .line_len(MultiBufferRow(latest.start.row))
3412 == latest.start.column
3413 {
3414 this.rewrap_impl(
3415 RewrapOptions {
3416 override_language_settings: true,
3417 preserve_existing_whitespace: true,
3418 },
3419 cx,
3420 )
3421 }
3422 }
3423 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3424 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3425 this.refresh_inline_completion(true, false, window, cx);
3426 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3427 });
3428 }
3429
3430 fn find_possible_emoji_shortcode_at_position(
3431 snapshot: &MultiBufferSnapshot,
3432 position: Point,
3433 ) -> Option<String> {
3434 let mut chars = Vec::new();
3435 let mut found_colon = false;
3436 for char in snapshot.reversed_chars_at(position).take(100) {
3437 // Found a possible emoji shortcode in the middle of the buffer
3438 if found_colon {
3439 if char.is_whitespace() {
3440 chars.reverse();
3441 return Some(chars.iter().collect());
3442 }
3443 // If the previous character is not a whitespace, we are in the middle of a word
3444 // and we only want to complete the shortcode if the word is made up of other emojis
3445 let mut containing_word = String::new();
3446 for ch in snapshot
3447 .reversed_chars_at(position)
3448 .skip(chars.len() + 1)
3449 .take(100)
3450 {
3451 if ch.is_whitespace() {
3452 break;
3453 }
3454 containing_word.push(ch);
3455 }
3456 let containing_word = containing_word.chars().rev().collect::<String>();
3457 if util::word_consists_of_emojis(containing_word.as_str()) {
3458 chars.reverse();
3459 return Some(chars.iter().collect());
3460 }
3461 }
3462
3463 if char.is_whitespace() || !char.is_ascii() {
3464 return None;
3465 }
3466 if char == ':' {
3467 found_colon = true;
3468 } else {
3469 chars.push(char);
3470 }
3471 }
3472 // Found a possible emoji shortcode at the beginning of the buffer
3473 chars.reverse();
3474 Some(chars.iter().collect())
3475 }
3476
3477 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3478 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3479 self.transact(window, cx, |this, window, cx| {
3480 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3481 let selections = this.selections.all::<usize>(cx);
3482 let multi_buffer = this.buffer.read(cx);
3483 let buffer = multi_buffer.snapshot(cx);
3484 selections
3485 .iter()
3486 .map(|selection| {
3487 let start_point = selection.start.to_point(&buffer);
3488 let mut indent =
3489 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3490 indent.len = cmp::min(indent.len, start_point.column);
3491 let start = selection.start;
3492 let end = selection.end;
3493 let selection_is_empty = start == end;
3494 let language_scope = buffer.language_scope_at(start);
3495 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3496 &language_scope
3497 {
3498 let insert_extra_newline =
3499 insert_extra_newline_brackets(&buffer, start..end, language)
3500 || insert_extra_newline_tree_sitter(&buffer, start..end);
3501
3502 // Comment extension on newline is allowed only for cursor selections
3503 let comment_delimiter = maybe!({
3504 if !selection_is_empty {
3505 return None;
3506 }
3507
3508 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3509 return None;
3510 }
3511
3512 let delimiters = language.line_comment_prefixes();
3513 let max_len_of_delimiter =
3514 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3515 let (snapshot, range) =
3516 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3517
3518 let mut index_of_first_non_whitespace = 0;
3519 let comment_candidate = snapshot
3520 .chars_for_range(range)
3521 .skip_while(|c| {
3522 let should_skip = c.is_whitespace();
3523 if should_skip {
3524 index_of_first_non_whitespace += 1;
3525 }
3526 should_skip
3527 })
3528 .take(max_len_of_delimiter)
3529 .collect::<String>();
3530 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3531 comment_candidate.starts_with(comment_prefix.as_ref())
3532 })?;
3533 let cursor_is_placed_after_comment_marker =
3534 index_of_first_non_whitespace + comment_prefix.len()
3535 <= start_point.column as usize;
3536 if cursor_is_placed_after_comment_marker {
3537 Some(comment_prefix.clone())
3538 } else {
3539 None
3540 }
3541 });
3542 (comment_delimiter, insert_extra_newline)
3543 } else {
3544 (None, false)
3545 };
3546
3547 let capacity_for_delimiter = comment_delimiter
3548 .as_deref()
3549 .map(str::len)
3550 .unwrap_or_default();
3551 let mut new_text =
3552 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3553 new_text.push('\n');
3554 new_text.extend(indent.chars());
3555 if let Some(delimiter) = &comment_delimiter {
3556 new_text.push_str(delimiter);
3557 }
3558 if insert_extra_newline {
3559 new_text = new_text.repeat(2);
3560 }
3561
3562 let anchor = buffer.anchor_after(end);
3563 let new_selection = selection.map(|_| anchor);
3564 (
3565 (start..end, new_text),
3566 (insert_extra_newline, new_selection),
3567 )
3568 })
3569 .unzip()
3570 };
3571
3572 this.edit_with_autoindent(edits, cx);
3573 let buffer = this.buffer.read(cx).snapshot(cx);
3574 let new_selections = selection_fixup_info
3575 .into_iter()
3576 .map(|(extra_newline_inserted, new_selection)| {
3577 let mut cursor = new_selection.end.to_point(&buffer);
3578 if extra_newline_inserted {
3579 cursor.row -= 1;
3580 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3581 }
3582 new_selection.map(|_| cursor)
3583 })
3584 .collect();
3585
3586 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3587 s.select(new_selections)
3588 });
3589 this.refresh_inline_completion(true, false, window, cx);
3590 });
3591 }
3592
3593 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3595
3596 let buffer = self.buffer.read(cx);
3597 let snapshot = buffer.snapshot(cx);
3598
3599 let mut edits = Vec::new();
3600 let mut rows = Vec::new();
3601
3602 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3603 let cursor = selection.head();
3604 let row = cursor.row;
3605
3606 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3607
3608 let newline = "\n".to_string();
3609 edits.push((start_of_line..start_of_line, newline));
3610
3611 rows.push(row + rows_inserted as u32);
3612 }
3613
3614 self.transact(window, cx, |editor, window, cx| {
3615 editor.edit(edits, cx);
3616
3617 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3618 let mut index = 0;
3619 s.move_cursors_with(|map, _, _| {
3620 let row = rows[index];
3621 index += 1;
3622
3623 let point = Point::new(row, 0);
3624 let boundary = map.next_line_boundary(point).1;
3625 let clipped = map.clip_point(boundary, Bias::Left);
3626
3627 (clipped, SelectionGoal::None)
3628 });
3629 });
3630
3631 let mut indent_edits = Vec::new();
3632 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3633 for row in rows {
3634 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3635 for (row, indent) in indents {
3636 if indent.len == 0 {
3637 continue;
3638 }
3639
3640 let text = match indent.kind {
3641 IndentKind::Space => " ".repeat(indent.len as usize),
3642 IndentKind::Tab => "\t".repeat(indent.len as usize),
3643 };
3644 let point = Point::new(row.0, 0);
3645 indent_edits.push((point..point, text));
3646 }
3647 }
3648 editor.edit(indent_edits, cx);
3649 });
3650 }
3651
3652 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3654
3655 let buffer = self.buffer.read(cx);
3656 let snapshot = buffer.snapshot(cx);
3657
3658 let mut edits = Vec::new();
3659 let mut rows = Vec::new();
3660 let mut rows_inserted = 0;
3661
3662 for selection in self.selections.all_adjusted(cx) {
3663 let cursor = selection.head();
3664 let row = cursor.row;
3665
3666 let point = Point::new(row + 1, 0);
3667 let start_of_line = snapshot.clip_point(point, Bias::Left);
3668
3669 let newline = "\n".to_string();
3670 edits.push((start_of_line..start_of_line, newline));
3671
3672 rows_inserted += 1;
3673 rows.push(row + rows_inserted);
3674 }
3675
3676 self.transact(window, cx, |editor, window, cx| {
3677 editor.edit(edits, cx);
3678
3679 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3680 let mut index = 0;
3681 s.move_cursors_with(|map, _, _| {
3682 let row = rows[index];
3683 index += 1;
3684
3685 let point = Point::new(row, 0);
3686 let boundary = map.next_line_boundary(point).1;
3687 let clipped = map.clip_point(boundary, Bias::Left);
3688
3689 (clipped, SelectionGoal::None)
3690 });
3691 });
3692
3693 let mut indent_edits = Vec::new();
3694 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3695 for row in rows {
3696 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3697 for (row, indent) in indents {
3698 if indent.len == 0 {
3699 continue;
3700 }
3701
3702 let text = match indent.kind {
3703 IndentKind::Space => " ".repeat(indent.len as usize),
3704 IndentKind::Tab => "\t".repeat(indent.len as usize),
3705 };
3706 let point = Point::new(row.0, 0);
3707 indent_edits.push((point..point, text));
3708 }
3709 }
3710 editor.edit(indent_edits, cx);
3711 });
3712 }
3713
3714 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3715 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3716 original_indent_columns: Vec::new(),
3717 });
3718 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3719 }
3720
3721 fn insert_with_autoindent_mode(
3722 &mut self,
3723 text: &str,
3724 autoindent_mode: Option<AutoindentMode>,
3725 window: &mut Window,
3726 cx: &mut Context<Self>,
3727 ) {
3728 if self.read_only(cx) {
3729 return;
3730 }
3731
3732 let text: Arc<str> = text.into();
3733 self.transact(window, cx, |this, window, cx| {
3734 let old_selections = this.selections.all_adjusted(cx);
3735 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3736 let anchors = {
3737 let snapshot = buffer.read(cx);
3738 old_selections
3739 .iter()
3740 .map(|s| {
3741 let anchor = snapshot.anchor_after(s.head());
3742 s.map(|_| anchor)
3743 })
3744 .collect::<Vec<_>>()
3745 };
3746 buffer.edit(
3747 old_selections
3748 .iter()
3749 .map(|s| (s.start..s.end, text.clone())),
3750 autoindent_mode,
3751 cx,
3752 );
3753 anchors
3754 });
3755
3756 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3757 s.select_anchors(selection_anchors);
3758 });
3759
3760 cx.notify();
3761 });
3762 }
3763
3764 fn trigger_completion_on_input(
3765 &mut self,
3766 text: &str,
3767 trigger_in_words: bool,
3768 window: &mut Window,
3769 cx: &mut Context<Self>,
3770 ) {
3771 let ignore_completion_provider = self
3772 .context_menu
3773 .borrow()
3774 .as_ref()
3775 .map(|menu| match menu {
3776 CodeContextMenu::Completions(completions_menu) => {
3777 completions_menu.ignore_completion_provider
3778 }
3779 CodeContextMenu::CodeActions(_) => false,
3780 })
3781 .unwrap_or(false);
3782
3783 if ignore_completion_provider {
3784 self.show_word_completions(&ShowWordCompletions, window, cx);
3785 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3786 self.show_completions(
3787 &ShowCompletions {
3788 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3789 },
3790 window,
3791 cx,
3792 );
3793 } else {
3794 self.hide_context_menu(window, cx);
3795 }
3796 }
3797
3798 fn is_completion_trigger(
3799 &self,
3800 text: &str,
3801 trigger_in_words: bool,
3802 cx: &mut Context<Self>,
3803 ) -> bool {
3804 let position = self.selections.newest_anchor().head();
3805 let multibuffer = self.buffer.read(cx);
3806 let Some(buffer) = position
3807 .buffer_id
3808 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3809 else {
3810 return false;
3811 };
3812
3813 if let Some(completion_provider) = &self.completion_provider {
3814 completion_provider.is_completion_trigger(
3815 &buffer,
3816 position.text_anchor,
3817 text,
3818 trigger_in_words,
3819 cx,
3820 )
3821 } else {
3822 false
3823 }
3824 }
3825
3826 /// If any empty selections is touching the start of its innermost containing autoclose
3827 /// region, expand it to select the brackets.
3828 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3829 let selections = self.selections.all::<usize>(cx);
3830 let buffer = self.buffer.read(cx).read(cx);
3831 let new_selections = self
3832 .selections_with_autoclose_regions(selections, &buffer)
3833 .map(|(mut selection, region)| {
3834 if !selection.is_empty() {
3835 return selection;
3836 }
3837
3838 if let Some(region) = region {
3839 let mut range = region.range.to_offset(&buffer);
3840 if selection.start == range.start && range.start >= region.pair.start.len() {
3841 range.start -= region.pair.start.len();
3842 if buffer.contains_str_at(range.start, ®ion.pair.start)
3843 && buffer.contains_str_at(range.end, ®ion.pair.end)
3844 {
3845 range.end += region.pair.end.len();
3846 selection.start = range.start;
3847 selection.end = range.end;
3848
3849 return selection;
3850 }
3851 }
3852 }
3853
3854 let always_treat_brackets_as_autoclosed = buffer
3855 .language_settings_at(selection.start, cx)
3856 .always_treat_brackets_as_autoclosed;
3857
3858 if !always_treat_brackets_as_autoclosed {
3859 return selection;
3860 }
3861
3862 if let Some(scope) = buffer.language_scope_at(selection.start) {
3863 for (pair, enabled) in scope.brackets() {
3864 if !enabled || !pair.close {
3865 continue;
3866 }
3867
3868 if buffer.contains_str_at(selection.start, &pair.end) {
3869 let pair_start_len = pair.start.len();
3870 if buffer.contains_str_at(
3871 selection.start.saturating_sub(pair_start_len),
3872 &pair.start,
3873 ) {
3874 selection.start -= pair_start_len;
3875 selection.end += pair.end.len();
3876
3877 return selection;
3878 }
3879 }
3880 }
3881 }
3882
3883 selection
3884 })
3885 .collect();
3886
3887 drop(buffer);
3888 self.change_selections(None, window, cx, |selections| {
3889 selections.select(new_selections)
3890 });
3891 }
3892
3893 /// Iterate the given selections, and for each one, find the smallest surrounding
3894 /// autoclose region. This uses the ordering of the selections and the autoclose
3895 /// regions to avoid repeated comparisons.
3896 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3897 &'a self,
3898 selections: impl IntoIterator<Item = Selection<D>>,
3899 buffer: &'a MultiBufferSnapshot,
3900 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3901 let mut i = 0;
3902 let mut regions = self.autoclose_regions.as_slice();
3903 selections.into_iter().map(move |selection| {
3904 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3905
3906 let mut enclosing = None;
3907 while let Some(pair_state) = regions.get(i) {
3908 if pair_state.range.end.to_offset(buffer) < range.start {
3909 regions = ®ions[i + 1..];
3910 i = 0;
3911 } else if pair_state.range.start.to_offset(buffer) > range.end {
3912 break;
3913 } else {
3914 if pair_state.selection_id == selection.id {
3915 enclosing = Some(pair_state);
3916 }
3917 i += 1;
3918 }
3919 }
3920
3921 (selection, enclosing)
3922 })
3923 }
3924
3925 /// Remove any autoclose regions that no longer contain their selection.
3926 fn invalidate_autoclose_regions(
3927 &mut self,
3928 mut selections: &[Selection<Anchor>],
3929 buffer: &MultiBufferSnapshot,
3930 ) {
3931 self.autoclose_regions.retain(|state| {
3932 let mut i = 0;
3933 while let Some(selection) = selections.get(i) {
3934 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3935 selections = &selections[1..];
3936 continue;
3937 }
3938 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3939 break;
3940 }
3941 if selection.id == state.selection_id {
3942 return true;
3943 } else {
3944 i += 1;
3945 }
3946 }
3947 false
3948 });
3949 }
3950
3951 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3952 let offset = position.to_offset(buffer);
3953 let (word_range, kind) = buffer.surrounding_word(offset, true);
3954 if offset > word_range.start && kind == Some(CharKind::Word) {
3955 Some(
3956 buffer
3957 .text_for_range(word_range.start..offset)
3958 .collect::<String>(),
3959 )
3960 } else {
3961 None
3962 }
3963 }
3964
3965 pub fn toggle_inlay_hints(
3966 &mut self,
3967 _: &ToggleInlayHints,
3968 _: &mut Window,
3969 cx: &mut Context<Self>,
3970 ) {
3971 self.refresh_inlay_hints(
3972 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3973 cx,
3974 );
3975 }
3976
3977 pub fn inlay_hints_enabled(&self) -> bool {
3978 self.inlay_hint_cache.enabled
3979 }
3980
3981 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3982 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3983 return;
3984 }
3985
3986 let reason_description = reason.description();
3987 let ignore_debounce = matches!(
3988 reason,
3989 InlayHintRefreshReason::SettingsChange(_)
3990 | InlayHintRefreshReason::Toggle(_)
3991 | InlayHintRefreshReason::ExcerptsRemoved(_)
3992 | InlayHintRefreshReason::ModifiersChanged(_)
3993 );
3994 let (invalidate_cache, required_languages) = match reason {
3995 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3996 match self.inlay_hint_cache.modifiers_override(enabled) {
3997 Some(enabled) => {
3998 if enabled {
3999 (InvalidationStrategy::RefreshRequested, None)
4000 } else {
4001 self.splice_inlays(
4002 &self
4003 .visible_inlay_hints(cx)
4004 .iter()
4005 .map(|inlay| inlay.id)
4006 .collect::<Vec<InlayId>>(),
4007 Vec::new(),
4008 cx,
4009 );
4010 return;
4011 }
4012 }
4013 None => return,
4014 }
4015 }
4016 InlayHintRefreshReason::Toggle(enabled) => {
4017 if self.inlay_hint_cache.toggle(enabled) {
4018 if enabled {
4019 (InvalidationStrategy::RefreshRequested, None)
4020 } else {
4021 self.splice_inlays(
4022 &self
4023 .visible_inlay_hints(cx)
4024 .iter()
4025 .map(|inlay| inlay.id)
4026 .collect::<Vec<InlayId>>(),
4027 Vec::new(),
4028 cx,
4029 );
4030 return;
4031 }
4032 } else {
4033 return;
4034 }
4035 }
4036 InlayHintRefreshReason::SettingsChange(new_settings) => {
4037 match self.inlay_hint_cache.update_settings(
4038 &self.buffer,
4039 new_settings,
4040 self.visible_inlay_hints(cx),
4041 cx,
4042 ) {
4043 ControlFlow::Break(Some(InlaySplice {
4044 to_remove,
4045 to_insert,
4046 })) => {
4047 self.splice_inlays(&to_remove, to_insert, cx);
4048 return;
4049 }
4050 ControlFlow::Break(None) => return,
4051 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4052 }
4053 }
4054 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4055 if let Some(InlaySplice {
4056 to_remove,
4057 to_insert,
4058 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4059 {
4060 self.splice_inlays(&to_remove, to_insert, cx);
4061 }
4062 return;
4063 }
4064 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4065 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4066 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4067 }
4068 InlayHintRefreshReason::RefreshRequested => {
4069 (InvalidationStrategy::RefreshRequested, None)
4070 }
4071 };
4072
4073 if let Some(InlaySplice {
4074 to_remove,
4075 to_insert,
4076 }) = self.inlay_hint_cache.spawn_hint_refresh(
4077 reason_description,
4078 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4079 invalidate_cache,
4080 ignore_debounce,
4081 cx,
4082 ) {
4083 self.splice_inlays(&to_remove, to_insert, cx);
4084 }
4085 }
4086
4087 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4088 self.display_map
4089 .read(cx)
4090 .current_inlays()
4091 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4092 .cloned()
4093 .collect()
4094 }
4095
4096 pub fn excerpts_for_inlay_hints_query(
4097 &self,
4098 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4099 cx: &mut Context<Editor>,
4100 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4101 let Some(project) = self.project.as_ref() else {
4102 return HashMap::default();
4103 };
4104 let project = project.read(cx);
4105 let multi_buffer = self.buffer().read(cx);
4106 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4107 let multi_buffer_visible_start = self
4108 .scroll_manager
4109 .anchor()
4110 .anchor
4111 .to_point(&multi_buffer_snapshot);
4112 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4113 multi_buffer_visible_start
4114 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4115 Bias::Left,
4116 );
4117 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4118 multi_buffer_snapshot
4119 .range_to_buffer_ranges(multi_buffer_visible_range)
4120 .into_iter()
4121 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4122 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4123 let buffer_file = project::File::from_dyn(buffer.file())?;
4124 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4125 let worktree_entry = buffer_worktree
4126 .read(cx)
4127 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4128 if worktree_entry.is_ignored {
4129 return None;
4130 }
4131
4132 let language = buffer.language()?;
4133 if let Some(restrict_to_languages) = restrict_to_languages {
4134 if !restrict_to_languages.contains(language) {
4135 return None;
4136 }
4137 }
4138 Some((
4139 excerpt_id,
4140 (
4141 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4142 buffer.version().clone(),
4143 excerpt_visible_range,
4144 ),
4145 ))
4146 })
4147 .collect()
4148 }
4149
4150 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4151 TextLayoutDetails {
4152 text_system: window.text_system().clone(),
4153 editor_style: self.style.clone().unwrap(),
4154 rem_size: window.rem_size(),
4155 scroll_anchor: self.scroll_manager.anchor(),
4156 visible_rows: self.visible_line_count(),
4157 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4158 }
4159 }
4160
4161 pub fn splice_inlays(
4162 &self,
4163 to_remove: &[InlayId],
4164 to_insert: Vec<Inlay>,
4165 cx: &mut Context<Self>,
4166 ) {
4167 self.display_map.update(cx, |display_map, cx| {
4168 display_map.splice_inlays(to_remove, to_insert, cx)
4169 });
4170 cx.notify();
4171 }
4172
4173 fn trigger_on_type_formatting(
4174 &self,
4175 input: String,
4176 window: &mut Window,
4177 cx: &mut Context<Self>,
4178 ) -> Option<Task<Result<()>>> {
4179 if input.len() != 1 {
4180 return None;
4181 }
4182
4183 let project = self.project.as_ref()?;
4184 let position = self.selections.newest_anchor().head();
4185 let (buffer, buffer_position) = self
4186 .buffer
4187 .read(cx)
4188 .text_anchor_for_position(position, cx)?;
4189
4190 let settings = language_settings::language_settings(
4191 buffer
4192 .read(cx)
4193 .language_at(buffer_position)
4194 .map(|l| l.name()),
4195 buffer.read(cx).file(),
4196 cx,
4197 );
4198 if !settings.use_on_type_format {
4199 return None;
4200 }
4201
4202 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4203 // hence we do LSP request & edit on host side only — add formats to host's history.
4204 let push_to_lsp_host_history = true;
4205 // If this is not the host, append its history with new edits.
4206 let push_to_client_history = project.read(cx).is_via_collab();
4207
4208 let on_type_formatting = project.update(cx, |project, cx| {
4209 project.on_type_format(
4210 buffer.clone(),
4211 buffer_position,
4212 input,
4213 push_to_lsp_host_history,
4214 cx,
4215 )
4216 });
4217 Some(cx.spawn_in(window, async move |editor, cx| {
4218 if let Some(transaction) = on_type_formatting.await? {
4219 if push_to_client_history {
4220 buffer
4221 .update(cx, |buffer, _| {
4222 buffer.push_transaction(transaction, Instant::now());
4223 })
4224 .ok();
4225 }
4226 editor.update(cx, |editor, cx| {
4227 editor.refresh_document_highlights(cx);
4228 })?;
4229 }
4230 Ok(())
4231 }))
4232 }
4233
4234 pub fn show_word_completions(
4235 &mut self,
4236 _: &ShowWordCompletions,
4237 window: &mut Window,
4238 cx: &mut Context<Self>,
4239 ) {
4240 self.open_completions_menu(true, None, window, cx);
4241 }
4242
4243 pub fn show_completions(
4244 &mut self,
4245 options: &ShowCompletions,
4246 window: &mut Window,
4247 cx: &mut Context<Self>,
4248 ) {
4249 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4250 }
4251
4252 fn open_completions_menu(
4253 &mut self,
4254 ignore_completion_provider: bool,
4255 trigger: Option<&str>,
4256 window: &mut Window,
4257 cx: &mut Context<Self>,
4258 ) {
4259 if self.pending_rename.is_some() {
4260 return;
4261 }
4262 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4263 return;
4264 }
4265
4266 let position = self.selections.newest_anchor().head();
4267 if position.diff_base_anchor.is_some() {
4268 return;
4269 }
4270 let (buffer, buffer_position) =
4271 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4272 output
4273 } else {
4274 return;
4275 };
4276 let buffer_snapshot = buffer.read(cx).snapshot();
4277 let show_completion_documentation = buffer_snapshot
4278 .settings_at(buffer_position, cx)
4279 .show_completion_documentation;
4280
4281 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4282
4283 let trigger_kind = match trigger {
4284 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4285 CompletionTriggerKind::TRIGGER_CHARACTER
4286 }
4287 _ => CompletionTriggerKind::INVOKED,
4288 };
4289 let completion_context = CompletionContext {
4290 trigger_character: trigger.and_then(|trigger| {
4291 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4292 Some(String::from(trigger))
4293 } else {
4294 None
4295 }
4296 }),
4297 trigger_kind,
4298 };
4299
4300 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4301 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4302 let word_to_exclude = buffer_snapshot
4303 .text_for_range(old_range.clone())
4304 .collect::<String>();
4305 (
4306 buffer_snapshot.anchor_before(old_range.start)
4307 ..buffer_snapshot.anchor_after(old_range.end),
4308 Some(word_to_exclude),
4309 )
4310 } else {
4311 (buffer_position..buffer_position, None)
4312 };
4313
4314 let completion_settings = language_settings(
4315 buffer_snapshot
4316 .language_at(buffer_position)
4317 .map(|language| language.name()),
4318 buffer_snapshot.file(),
4319 cx,
4320 )
4321 .completions;
4322
4323 // The document can be large, so stay in reasonable bounds when searching for words,
4324 // otherwise completion pop-up might be slow to appear.
4325 const WORD_LOOKUP_ROWS: u32 = 5_000;
4326 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4327 let min_word_search = buffer_snapshot.clip_point(
4328 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4329 Bias::Left,
4330 );
4331 let max_word_search = buffer_snapshot.clip_point(
4332 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4333 Bias::Right,
4334 );
4335 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4336 ..buffer_snapshot.point_to_offset(max_word_search);
4337
4338 let provider = self
4339 .completion_provider
4340 .as_ref()
4341 .filter(|_| !ignore_completion_provider);
4342 let skip_digits = query
4343 .as_ref()
4344 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4345
4346 let (mut words, provided_completions) = match provider {
4347 Some(provider) => {
4348 let completions = provider.completions(
4349 position.excerpt_id,
4350 &buffer,
4351 buffer_position,
4352 completion_context,
4353 window,
4354 cx,
4355 );
4356
4357 let words = match completion_settings.words {
4358 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4359 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4360 .background_spawn(async move {
4361 buffer_snapshot.words_in_range(WordsQuery {
4362 fuzzy_contents: None,
4363 range: word_search_range,
4364 skip_digits,
4365 })
4366 }),
4367 };
4368
4369 (words, completions)
4370 }
4371 None => (
4372 cx.background_spawn(async move {
4373 buffer_snapshot.words_in_range(WordsQuery {
4374 fuzzy_contents: None,
4375 range: word_search_range,
4376 skip_digits,
4377 })
4378 }),
4379 Task::ready(Ok(None)),
4380 ),
4381 };
4382
4383 let sort_completions = provider
4384 .as_ref()
4385 .map_or(true, |provider| provider.sort_completions());
4386
4387 let filter_completions = provider
4388 .as_ref()
4389 .map_or(true, |provider| provider.filter_completions());
4390
4391 let id = post_inc(&mut self.next_completion_id);
4392 let task = cx.spawn_in(window, async move |editor, cx| {
4393 async move {
4394 editor.update(cx, |this, _| {
4395 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4396 })?;
4397
4398 let mut completions = Vec::new();
4399 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4400 completions.extend(provided_completions);
4401 if completion_settings.words == WordsCompletionMode::Fallback {
4402 words = Task::ready(HashMap::default());
4403 }
4404 }
4405
4406 let mut words = words.await;
4407 if let Some(word_to_exclude) = &word_to_exclude {
4408 words.remove(word_to_exclude);
4409 }
4410 for lsp_completion in &completions {
4411 words.remove(&lsp_completion.new_text);
4412 }
4413 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4414 old_range: old_range.clone(),
4415 new_text: word.clone(),
4416 label: CodeLabel::plain(word, None),
4417 icon_path: None,
4418 documentation: None,
4419 source: CompletionSource::BufferWord {
4420 word_range,
4421 resolved: false,
4422 },
4423 confirm: None,
4424 }));
4425
4426 let menu = if completions.is_empty() {
4427 None
4428 } else {
4429 let mut menu = CompletionsMenu::new(
4430 id,
4431 sort_completions,
4432 show_completion_documentation,
4433 ignore_completion_provider,
4434 position,
4435 buffer.clone(),
4436 completions.into(),
4437 );
4438
4439 menu.filter(
4440 if filter_completions {
4441 query.as_deref()
4442 } else {
4443 None
4444 },
4445 cx.background_executor().clone(),
4446 )
4447 .await;
4448
4449 menu.visible().then_some(menu)
4450 };
4451
4452 editor.update_in(cx, |editor, window, cx| {
4453 match editor.context_menu.borrow().as_ref() {
4454 None => {}
4455 Some(CodeContextMenu::Completions(prev_menu)) => {
4456 if prev_menu.id > id {
4457 return;
4458 }
4459 }
4460 _ => return,
4461 }
4462
4463 if editor.focus_handle.is_focused(window) && menu.is_some() {
4464 let mut menu = menu.unwrap();
4465 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4466
4467 *editor.context_menu.borrow_mut() =
4468 Some(CodeContextMenu::Completions(menu));
4469
4470 if editor.show_edit_predictions_in_menu() {
4471 editor.update_visible_inline_completion(window, cx);
4472 } else {
4473 editor.discard_inline_completion(false, cx);
4474 }
4475
4476 cx.notify();
4477 } else if editor.completion_tasks.len() <= 1 {
4478 // If there are no more completion tasks and the last menu was
4479 // empty, we should hide it.
4480 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4481 // If it was already hidden and we don't show inline
4482 // completions in the menu, we should also show the
4483 // inline-completion when available.
4484 if was_hidden && editor.show_edit_predictions_in_menu() {
4485 editor.update_visible_inline_completion(window, cx);
4486 }
4487 }
4488 })?;
4489
4490 anyhow::Ok(())
4491 }
4492 .log_err()
4493 .await
4494 });
4495
4496 self.completion_tasks.push((id, task));
4497 }
4498
4499 #[cfg(feature = "test-support")]
4500 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4501 let menu = self.context_menu.borrow();
4502 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4503 let completions = menu.completions.borrow();
4504 Some(completions.to_vec())
4505 } else {
4506 None
4507 }
4508 }
4509
4510 pub fn confirm_completion(
4511 &mut self,
4512 action: &ConfirmCompletion,
4513 window: &mut Window,
4514 cx: &mut Context<Self>,
4515 ) -> Option<Task<Result<()>>> {
4516 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4517 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4518 }
4519
4520 pub fn compose_completion(
4521 &mut self,
4522 action: &ComposeCompletion,
4523 window: &mut Window,
4524 cx: &mut Context<Self>,
4525 ) -> Option<Task<Result<()>>> {
4526 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4527 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4528 }
4529
4530 fn do_completion(
4531 &mut self,
4532 item_ix: Option<usize>,
4533 intent: CompletionIntent,
4534 window: &mut Window,
4535 cx: &mut Context<Editor>,
4536 ) -> Option<Task<Result<()>>> {
4537 use language::ToOffset as _;
4538
4539 let completions_menu =
4540 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4541 menu
4542 } else {
4543 return None;
4544 };
4545
4546 let candidate_id = {
4547 let entries = completions_menu.entries.borrow();
4548 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4549 if self.show_edit_predictions_in_menu() {
4550 self.discard_inline_completion(true, cx);
4551 }
4552 mat.candidate_id
4553 };
4554
4555 let buffer_handle = completions_menu.buffer;
4556 let completion = completions_menu
4557 .completions
4558 .borrow()
4559 .get(candidate_id)?
4560 .clone();
4561 cx.stop_propagation();
4562
4563 let snippet;
4564 let new_text;
4565 if completion.is_snippet() {
4566 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4567 new_text = snippet.as_ref().unwrap().text.clone();
4568 } else {
4569 snippet = None;
4570 new_text = completion.new_text.clone();
4571 };
4572 let selections = self.selections.all::<usize>(cx);
4573 let buffer = buffer_handle.read(cx);
4574 let old_range = completion.old_range.to_offset(buffer);
4575 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4576
4577 let newest_selection = self.selections.newest_anchor();
4578 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4579 return None;
4580 }
4581
4582 let lookbehind = newest_selection
4583 .start
4584 .text_anchor
4585 .to_offset(buffer)
4586 .saturating_sub(old_range.start);
4587 let lookahead = old_range
4588 .end
4589 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4590 let mut common_prefix_len = old_text
4591 .bytes()
4592 .zip(new_text.bytes())
4593 .take_while(|(a, b)| a == b)
4594 .count();
4595
4596 let snapshot = self.buffer.read(cx).snapshot(cx);
4597 let mut range_to_replace: Option<Range<isize>> = None;
4598 let mut ranges = Vec::new();
4599 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4600 for selection in &selections {
4601 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4602 let start = selection.start.saturating_sub(lookbehind);
4603 let end = selection.end + lookahead;
4604 if selection.id == newest_selection.id {
4605 range_to_replace = Some(
4606 ((start + common_prefix_len) as isize - selection.start as isize)
4607 ..(end as isize - selection.start as isize),
4608 );
4609 }
4610 ranges.push(start + common_prefix_len..end);
4611 } else {
4612 common_prefix_len = 0;
4613 ranges.clear();
4614 ranges.extend(selections.iter().map(|s| {
4615 if s.id == newest_selection.id {
4616 range_to_replace = Some(
4617 old_range.start.to_offset_utf16(&snapshot).0 as isize
4618 - selection.start as isize
4619 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4620 - selection.start as isize,
4621 );
4622 old_range.clone()
4623 } else {
4624 s.start..s.end
4625 }
4626 }));
4627 break;
4628 }
4629 if !self.linked_edit_ranges.is_empty() {
4630 let start_anchor = snapshot.anchor_before(selection.head());
4631 let end_anchor = snapshot.anchor_after(selection.tail());
4632 if let Some(ranges) = self
4633 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4634 {
4635 for (buffer, edits) in ranges {
4636 linked_edits.entry(buffer.clone()).or_default().extend(
4637 edits
4638 .into_iter()
4639 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4640 );
4641 }
4642 }
4643 }
4644 }
4645 let text = &new_text[common_prefix_len..];
4646
4647 cx.emit(EditorEvent::InputHandled {
4648 utf16_range_to_replace: range_to_replace,
4649 text: text.into(),
4650 });
4651
4652 self.transact(window, cx, |this, window, cx| {
4653 if let Some(mut snippet) = snippet {
4654 snippet.text = text.to_string();
4655 for tabstop in snippet
4656 .tabstops
4657 .iter_mut()
4658 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4659 {
4660 tabstop.start -= common_prefix_len as isize;
4661 tabstop.end -= common_prefix_len as isize;
4662 }
4663
4664 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4665 } else {
4666 this.buffer.update(cx, |buffer, cx| {
4667 let edits = ranges.iter().map(|range| (range.clone(), text));
4668 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4669 });
4670 }
4671 for (buffer, edits) in linked_edits {
4672 buffer.update(cx, |buffer, cx| {
4673 let snapshot = buffer.snapshot();
4674 let edits = edits
4675 .into_iter()
4676 .map(|(range, text)| {
4677 use text::ToPoint as TP;
4678 let end_point = TP::to_point(&range.end, &snapshot);
4679 let start_point = TP::to_point(&range.start, &snapshot);
4680 (start_point..end_point, text)
4681 })
4682 .sorted_by_key(|(range, _)| range.start);
4683 buffer.edit(edits, None, cx);
4684 })
4685 }
4686
4687 this.refresh_inline_completion(true, false, window, cx);
4688 });
4689
4690 let show_new_completions_on_confirm = completion
4691 .confirm
4692 .as_ref()
4693 .map_or(false, |confirm| confirm(intent, window, cx));
4694 if show_new_completions_on_confirm {
4695 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4696 }
4697
4698 let provider = self.completion_provider.as_ref()?;
4699 drop(completion);
4700 let apply_edits = provider.apply_additional_edits_for_completion(
4701 buffer_handle,
4702 completions_menu.completions.clone(),
4703 candidate_id,
4704 true,
4705 cx,
4706 );
4707
4708 let editor_settings = EditorSettings::get_global(cx);
4709 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4710 // After the code completion is finished, users often want to know what signatures are needed.
4711 // so we should automatically call signature_help
4712 self.show_signature_help(&ShowSignatureHelp, window, cx);
4713 }
4714
4715 Some(cx.foreground_executor().spawn(async move {
4716 apply_edits.await?;
4717 Ok(())
4718 }))
4719 }
4720
4721 pub fn toggle_code_actions(
4722 &mut self,
4723 action: &ToggleCodeActions,
4724 window: &mut Window,
4725 cx: &mut Context<Self>,
4726 ) {
4727 let mut context_menu = self.context_menu.borrow_mut();
4728 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4729 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4730 // Toggle if we're selecting the same one
4731 *context_menu = None;
4732 cx.notify();
4733 return;
4734 } else {
4735 // Otherwise, clear it and start a new one
4736 *context_menu = None;
4737 cx.notify();
4738 }
4739 }
4740 drop(context_menu);
4741 let snapshot = self.snapshot(window, cx);
4742 let deployed_from_indicator = action.deployed_from_indicator;
4743 let mut task = self.code_actions_task.take();
4744 let action = action.clone();
4745 cx.spawn_in(window, async move |editor, cx| {
4746 while let Some(prev_task) = task {
4747 prev_task.await.log_err();
4748 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4749 }
4750
4751 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4752 if editor.focus_handle.is_focused(window) {
4753 let multibuffer_point = action
4754 .deployed_from_indicator
4755 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4756 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4757 let (buffer, buffer_row) = snapshot
4758 .buffer_snapshot
4759 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4760 .and_then(|(buffer_snapshot, range)| {
4761 editor
4762 .buffer
4763 .read(cx)
4764 .buffer(buffer_snapshot.remote_id())
4765 .map(|buffer| (buffer, range.start.row))
4766 })?;
4767 let (_, code_actions) = editor
4768 .available_code_actions
4769 .clone()
4770 .and_then(|(location, code_actions)| {
4771 let snapshot = location.buffer.read(cx).snapshot();
4772 let point_range = location.range.to_point(&snapshot);
4773 let point_range = point_range.start.row..=point_range.end.row;
4774 if point_range.contains(&buffer_row) {
4775 Some((location, code_actions))
4776 } else {
4777 None
4778 }
4779 })
4780 .unzip();
4781 let buffer_id = buffer.read(cx).remote_id();
4782 let tasks = editor
4783 .tasks
4784 .get(&(buffer_id, buffer_row))
4785 .map(|t| Arc::new(t.to_owned()));
4786 if tasks.is_none() && code_actions.is_none() {
4787 return None;
4788 }
4789
4790 editor.completion_tasks.clear();
4791 editor.discard_inline_completion(false, cx);
4792 let task_context =
4793 tasks
4794 .as_ref()
4795 .zip(editor.project.clone())
4796 .map(|(tasks, project)| {
4797 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4798 });
4799
4800 Some(cx.spawn_in(window, async move |editor, cx| {
4801 let task_context = match task_context {
4802 Some(task_context) => task_context.await,
4803 None => None,
4804 };
4805 let resolved_tasks =
4806 tasks.zip(task_context).map(|(tasks, task_context)| {
4807 Rc::new(ResolvedTasks {
4808 templates: tasks.resolve(&task_context).collect(),
4809 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4810 multibuffer_point.row,
4811 tasks.column,
4812 )),
4813 })
4814 });
4815 let spawn_straight_away = resolved_tasks
4816 .as_ref()
4817 .map_or(false, |tasks| tasks.templates.len() == 1)
4818 && code_actions
4819 .as_ref()
4820 .map_or(true, |actions| actions.is_empty());
4821 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4822 *editor.context_menu.borrow_mut() =
4823 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4824 buffer,
4825 actions: CodeActionContents {
4826 tasks: resolved_tasks,
4827 actions: code_actions,
4828 },
4829 selected_item: Default::default(),
4830 scroll_handle: UniformListScrollHandle::default(),
4831 deployed_from_indicator,
4832 }));
4833 if spawn_straight_away {
4834 if let Some(task) = editor.confirm_code_action(
4835 &ConfirmCodeAction { item_ix: Some(0) },
4836 window,
4837 cx,
4838 ) {
4839 cx.notify();
4840 return task;
4841 }
4842 }
4843 cx.notify();
4844 Task::ready(Ok(()))
4845 }) {
4846 task.await
4847 } else {
4848 Ok(())
4849 }
4850 }))
4851 } else {
4852 Some(Task::ready(Ok(())))
4853 }
4854 })?;
4855 if let Some(task) = spawned_test_task {
4856 task.await?;
4857 }
4858
4859 Ok::<_, anyhow::Error>(())
4860 })
4861 .detach_and_log_err(cx);
4862 }
4863
4864 pub fn confirm_code_action(
4865 &mut self,
4866 action: &ConfirmCodeAction,
4867 window: &mut Window,
4868 cx: &mut Context<Self>,
4869 ) -> Option<Task<Result<()>>> {
4870 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4871
4872 let actions_menu =
4873 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4874 menu
4875 } else {
4876 return None;
4877 };
4878
4879 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4880 let action = actions_menu.actions.get(action_ix)?;
4881 let title = action.label();
4882 let buffer = actions_menu.buffer;
4883 let workspace = self.workspace()?;
4884
4885 match action {
4886 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4887 match resolved_task.task_type() {
4888 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
4889 workspace::tasks::schedule_resolved_task(
4890 workspace,
4891 task_source_kind,
4892 resolved_task,
4893 false,
4894 cx,
4895 );
4896
4897 Some(Task::ready(Ok(())))
4898 }),
4899 task::TaskType::Debug(debug_args) => {
4900 if debug_args.locator.is_some() {
4901 workspace.update(cx, |workspace, cx| {
4902 workspace::tasks::schedule_resolved_task(
4903 workspace,
4904 task_source_kind,
4905 resolved_task,
4906 false,
4907 cx,
4908 );
4909 });
4910
4911 return Some(Task::ready(Ok(())));
4912 }
4913
4914 if let Some(project) = self.project.as_ref() {
4915 project
4916 .update(cx, |project, cx| {
4917 project.start_debug_session(
4918 resolved_task.resolved_debug_adapter_config().unwrap(),
4919 cx,
4920 )
4921 })
4922 .detach_and_log_err(cx);
4923 Some(Task::ready(Ok(())))
4924 } else {
4925 Some(Task::ready(Ok(())))
4926 }
4927 }
4928 }
4929 }
4930 CodeActionsItem::CodeAction {
4931 excerpt_id,
4932 action,
4933 provider,
4934 } => {
4935 let apply_code_action =
4936 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4937 let workspace = workspace.downgrade();
4938 Some(cx.spawn_in(window, async move |editor, cx| {
4939 let project_transaction = apply_code_action.await?;
4940 Self::open_project_transaction(
4941 &editor,
4942 workspace,
4943 project_transaction,
4944 title,
4945 cx,
4946 )
4947 .await
4948 }))
4949 }
4950 }
4951 }
4952
4953 pub async fn open_project_transaction(
4954 this: &WeakEntity<Editor>,
4955 workspace: WeakEntity<Workspace>,
4956 transaction: ProjectTransaction,
4957 title: String,
4958 cx: &mut AsyncWindowContext,
4959 ) -> Result<()> {
4960 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4961 cx.update(|_, cx| {
4962 entries.sort_unstable_by_key(|(buffer, _)| {
4963 buffer.read(cx).file().map(|f| f.path().clone())
4964 });
4965 })?;
4966
4967 // If the project transaction's edits are all contained within this editor, then
4968 // avoid opening a new editor to display them.
4969
4970 if let Some((buffer, transaction)) = entries.first() {
4971 if entries.len() == 1 {
4972 let excerpt = this.update(cx, |editor, cx| {
4973 editor
4974 .buffer()
4975 .read(cx)
4976 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4977 })?;
4978 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4979 if excerpted_buffer == *buffer {
4980 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4981 let excerpt_range = excerpt_range.to_offset(buffer);
4982 buffer
4983 .edited_ranges_for_transaction::<usize>(transaction)
4984 .all(|range| {
4985 excerpt_range.start <= range.start
4986 && excerpt_range.end >= range.end
4987 })
4988 })?;
4989
4990 if all_edits_within_excerpt {
4991 return Ok(());
4992 }
4993 }
4994 }
4995 }
4996 } else {
4997 return Ok(());
4998 }
4999
5000 let mut ranges_to_highlight = Vec::new();
5001 let excerpt_buffer = cx.new(|cx| {
5002 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5003 for (buffer_handle, transaction) in &entries {
5004 let buffer = buffer_handle.read(cx);
5005 ranges_to_highlight.extend(
5006 multibuffer.push_excerpts_with_context_lines(
5007 buffer_handle.clone(),
5008 buffer
5009 .edited_ranges_for_transaction::<usize>(transaction)
5010 .collect(),
5011 DEFAULT_MULTIBUFFER_CONTEXT,
5012 cx,
5013 ),
5014 );
5015 }
5016 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5017 multibuffer
5018 })?;
5019
5020 workspace.update_in(cx, |workspace, window, cx| {
5021 let project = workspace.project().clone();
5022 let editor =
5023 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5024 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5025 editor.update(cx, |editor, cx| {
5026 editor.highlight_background::<Self>(
5027 &ranges_to_highlight,
5028 |theme| theme.editor_highlighted_line_background,
5029 cx,
5030 );
5031 });
5032 })?;
5033
5034 Ok(())
5035 }
5036
5037 pub fn clear_code_action_providers(&mut self) {
5038 self.code_action_providers.clear();
5039 self.available_code_actions.take();
5040 }
5041
5042 pub fn add_code_action_provider(
5043 &mut self,
5044 provider: Rc<dyn CodeActionProvider>,
5045 window: &mut Window,
5046 cx: &mut Context<Self>,
5047 ) {
5048 if self
5049 .code_action_providers
5050 .iter()
5051 .any(|existing_provider| existing_provider.id() == provider.id())
5052 {
5053 return;
5054 }
5055
5056 self.code_action_providers.push(provider);
5057 self.refresh_code_actions(window, cx);
5058 }
5059
5060 pub fn remove_code_action_provider(
5061 &mut self,
5062 id: Arc<str>,
5063 window: &mut Window,
5064 cx: &mut Context<Self>,
5065 ) {
5066 self.code_action_providers
5067 .retain(|provider| provider.id() != id);
5068 self.refresh_code_actions(window, cx);
5069 }
5070
5071 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5072 let buffer = self.buffer.read(cx);
5073 let newest_selection = self.selections.newest_anchor().clone();
5074 if newest_selection.head().diff_base_anchor.is_some() {
5075 return None;
5076 }
5077 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5078 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5079 if start_buffer != end_buffer {
5080 return None;
5081 }
5082
5083 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5084 cx.background_executor()
5085 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5086 .await;
5087
5088 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5089 let providers = this.code_action_providers.clone();
5090 let tasks = this
5091 .code_action_providers
5092 .iter()
5093 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5094 .collect::<Vec<_>>();
5095 (providers, tasks)
5096 })?;
5097
5098 let mut actions = Vec::new();
5099 for (provider, provider_actions) in
5100 providers.into_iter().zip(future::join_all(tasks).await)
5101 {
5102 if let Some(provider_actions) = provider_actions.log_err() {
5103 actions.extend(provider_actions.into_iter().map(|action| {
5104 AvailableCodeAction {
5105 excerpt_id: newest_selection.start.excerpt_id,
5106 action,
5107 provider: provider.clone(),
5108 }
5109 }));
5110 }
5111 }
5112
5113 this.update(cx, |this, cx| {
5114 this.available_code_actions = if actions.is_empty() {
5115 None
5116 } else {
5117 Some((
5118 Location {
5119 buffer: start_buffer,
5120 range: start..end,
5121 },
5122 actions.into(),
5123 ))
5124 };
5125 cx.notify();
5126 })
5127 }));
5128 None
5129 }
5130
5131 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5132 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5133 self.show_git_blame_inline = false;
5134
5135 self.show_git_blame_inline_delay_task =
5136 Some(cx.spawn_in(window, async move |this, cx| {
5137 cx.background_executor().timer(delay).await;
5138
5139 this.update(cx, |this, cx| {
5140 this.show_git_blame_inline = true;
5141 cx.notify();
5142 })
5143 .log_err();
5144 }));
5145 }
5146 }
5147
5148 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5149 if self.pending_rename.is_some() {
5150 return None;
5151 }
5152
5153 let provider = self.semantics_provider.clone()?;
5154 let buffer = self.buffer.read(cx);
5155 let newest_selection = self.selections.newest_anchor().clone();
5156 let cursor_position = newest_selection.head();
5157 let (cursor_buffer, cursor_buffer_position) =
5158 buffer.text_anchor_for_position(cursor_position, cx)?;
5159 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5160 if cursor_buffer != tail_buffer {
5161 return None;
5162 }
5163 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5164 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5165 cx.background_executor()
5166 .timer(Duration::from_millis(debounce))
5167 .await;
5168
5169 let highlights = if let Some(highlights) = cx
5170 .update(|cx| {
5171 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5172 })
5173 .ok()
5174 .flatten()
5175 {
5176 highlights.await.log_err()
5177 } else {
5178 None
5179 };
5180
5181 if let Some(highlights) = highlights {
5182 this.update(cx, |this, cx| {
5183 if this.pending_rename.is_some() {
5184 return;
5185 }
5186
5187 let buffer_id = cursor_position.buffer_id;
5188 let buffer = this.buffer.read(cx);
5189 if !buffer
5190 .text_anchor_for_position(cursor_position, cx)
5191 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5192 {
5193 return;
5194 }
5195
5196 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5197 let mut write_ranges = Vec::new();
5198 let mut read_ranges = Vec::new();
5199 for highlight in highlights {
5200 for (excerpt_id, excerpt_range) in
5201 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5202 {
5203 let start = highlight
5204 .range
5205 .start
5206 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5207 let end = highlight
5208 .range
5209 .end
5210 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5211 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5212 continue;
5213 }
5214
5215 let range = Anchor {
5216 buffer_id,
5217 excerpt_id,
5218 text_anchor: start,
5219 diff_base_anchor: None,
5220 }..Anchor {
5221 buffer_id,
5222 excerpt_id,
5223 text_anchor: end,
5224 diff_base_anchor: None,
5225 };
5226 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5227 write_ranges.push(range);
5228 } else {
5229 read_ranges.push(range);
5230 }
5231 }
5232 }
5233
5234 this.highlight_background::<DocumentHighlightRead>(
5235 &read_ranges,
5236 |theme| theme.editor_document_highlight_read_background,
5237 cx,
5238 );
5239 this.highlight_background::<DocumentHighlightWrite>(
5240 &write_ranges,
5241 |theme| theme.editor_document_highlight_write_background,
5242 cx,
5243 );
5244 cx.notify();
5245 })
5246 .log_err();
5247 }
5248 }));
5249 None
5250 }
5251
5252 pub fn refresh_selected_text_highlights(
5253 &mut self,
5254 window: &mut Window,
5255 cx: &mut Context<Editor>,
5256 ) {
5257 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5258 return;
5259 }
5260 self.selection_highlight_task.take();
5261 if !EditorSettings::get_global(cx).selection_highlight {
5262 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5263 return;
5264 }
5265 if self.selections.count() != 1 || self.selections.line_mode {
5266 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5267 return;
5268 }
5269 let selection = self.selections.newest::<Point>(cx);
5270 if selection.is_empty() || selection.start.row != selection.end.row {
5271 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5272 return;
5273 }
5274 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5275 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5276 cx.background_executor()
5277 .timer(Duration::from_millis(debounce))
5278 .await;
5279 let Some(Some(matches_task)) = editor
5280 .update_in(cx, |editor, _, cx| {
5281 if editor.selections.count() != 1 || editor.selections.line_mode {
5282 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5283 return None;
5284 }
5285 let selection = editor.selections.newest::<Point>(cx);
5286 if selection.is_empty() || selection.start.row != selection.end.row {
5287 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5288 return None;
5289 }
5290 let buffer = editor.buffer().read(cx).snapshot(cx);
5291 let query = buffer.text_for_range(selection.range()).collect::<String>();
5292 if query.trim().is_empty() {
5293 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5294 return None;
5295 }
5296 Some(cx.background_spawn(async move {
5297 let mut ranges = Vec::new();
5298 let selection_anchors = selection.range().to_anchors(&buffer);
5299 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5300 for (search_buffer, search_range, excerpt_id) in
5301 buffer.range_to_buffer_ranges(range)
5302 {
5303 ranges.extend(
5304 project::search::SearchQuery::text(
5305 query.clone(),
5306 false,
5307 false,
5308 false,
5309 Default::default(),
5310 Default::default(),
5311 None,
5312 )
5313 .unwrap()
5314 .search(search_buffer, Some(search_range.clone()))
5315 .await
5316 .into_iter()
5317 .filter_map(
5318 |match_range| {
5319 let start = search_buffer.anchor_after(
5320 search_range.start + match_range.start,
5321 );
5322 let end = search_buffer.anchor_before(
5323 search_range.start + match_range.end,
5324 );
5325 let range = Anchor::range_in_buffer(
5326 excerpt_id,
5327 search_buffer.remote_id(),
5328 start..end,
5329 );
5330 (range != selection_anchors).then_some(range)
5331 },
5332 ),
5333 );
5334 }
5335 }
5336 ranges
5337 }))
5338 })
5339 .log_err()
5340 else {
5341 return;
5342 };
5343 let matches = matches_task.await;
5344 editor
5345 .update_in(cx, |editor, _, cx| {
5346 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5347 if !matches.is_empty() {
5348 editor.highlight_background::<SelectedTextHighlight>(
5349 &matches,
5350 |theme| theme.editor_document_highlight_bracket_background,
5351 cx,
5352 )
5353 }
5354 })
5355 .log_err();
5356 }));
5357 }
5358
5359 pub fn refresh_inline_completion(
5360 &mut self,
5361 debounce: bool,
5362 user_requested: bool,
5363 window: &mut Window,
5364 cx: &mut Context<Self>,
5365 ) -> Option<()> {
5366 let provider = self.edit_prediction_provider()?;
5367 let cursor = self.selections.newest_anchor().head();
5368 let (buffer, cursor_buffer_position) =
5369 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5370
5371 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5372 self.discard_inline_completion(false, cx);
5373 return None;
5374 }
5375
5376 if !user_requested
5377 && (!self.should_show_edit_predictions()
5378 || !self.is_focused(window)
5379 || buffer.read(cx).is_empty())
5380 {
5381 self.discard_inline_completion(false, cx);
5382 return None;
5383 }
5384
5385 self.update_visible_inline_completion(window, cx);
5386 provider.refresh(
5387 self.project.clone(),
5388 buffer,
5389 cursor_buffer_position,
5390 debounce,
5391 cx,
5392 );
5393 Some(())
5394 }
5395
5396 fn show_edit_predictions_in_menu(&self) -> bool {
5397 match self.edit_prediction_settings {
5398 EditPredictionSettings::Disabled => false,
5399 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5400 }
5401 }
5402
5403 pub fn edit_predictions_enabled(&self) -> bool {
5404 match self.edit_prediction_settings {
5405 EditPredictionSettings::Disabled => false,
5406 EditPredictionSettings::Enabled { .. } => true,
5407 }
5408 }
5409
5410 fn edit_prediction_requires_modifier(&self) -> bool {
5411 match self.edit_prediction_settings {
5412 EditPredictionSettings::Disabled => false,
5413 EditPredictionSettings::Enabled {
5414 preview_requires_modifier,
5415 ..
5416 } => preview_requires_modifier,
5417 }
5418 }
5419
5420 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5421 if self.edit_prediction_provider.is_none() {
5422 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5423 } else {
5424 let selection = self.selections.newest_anchor();
5425 let cursor = selection.head();
5426
5427 if let Some((buffer, cursor_buffer_position)) =
5428 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5429 {
5430 self.edit_prediction_settings =
5431 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5432 }
5433 }
5434 }
5435
5436 fn edit_prediction_settings_at_position(
5437 &self,
5438 buffer: &Entity<Buffer>,
5439 buffer_position: language::Anchor,
5440 cx: &App,
5441 ) -> EditPredictionSettings {
5442 if self.mode != EditorMode::Full
5443 || !self.show_inline_completions_override.unwrap_or(true)
5444 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5445 {
5446 return EditPredictionSettings::Disabled;
5447 }
5448
5449 let buffer = buffer.read(cx);
5450
5451 let file = buffer.file();
5452
5453 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5454 return EditPredictionSettings::Disabled;
5455 };
5456
5457 let by_provider = matches!(
5458 self.menu_inline_completions_policy,
5459 MenuInlineCompletionsPolicy::ByProvider
5460 );
5461
5462 let show_in_menu = by_provider
5463 && self
5464 .edit_prediction_provider
5465 .as_ref()
5466 .map_or(false, |provider| {
5467 provider.provider.show_completions_in_menu()
5468 });
5469
5470 let preview_requires_modifier =
5471 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5472
5473 EditPredictionSettings::Enabled {
5474 show_in_menu,
5475 preview_requires_modifier,
5476 }
5477 }
5478
5479 fn should_show_edit_predictions(&self) -> bool {
5480 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5481 }
5482
5483 pub fn edit_prediction_preview_is_active(&self) -> bool {
5484 matches!(
5485 self.edit_prediction_preview,
5486 EditPredictionPreview::Active { .. }
5487 )
5488 }
5489
5490 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5491 let cursor = self.selections.newest_anchor().head();
5492 if let Some((buffer, cursor_position)) =
5493 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5494 {
5495 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5496 } else {
5497 false
5498 }
5499 }
5500
5501 fn edit_predictions_enabled_in_buffer(
5502 &self,
5503 buffer: &Entity<Buffer>,
5504 buffer_position: language::Anchor,
5505 cx: &App,
5506 ) -> bool {
5507 maybe!({
5508 if self.read_only(cx) {
5509 return Some(false);
5510 }
5511 let provider = self.edit_prediction_provider()?;
5512 if !provider.is_enabled(&buffer, buffer_position, cx) {
5513 return Some(false);
5514 }
5515 let buffer = buffer.read(cx);
5516 let Some(file) = buffer.file() else {
5517 return Some(true);
5518 };
5519 let settings = all_language_settings(Some(file), cx);
5520 Some(settings.edit_predictions_enabled_for_file(file, cx))
5521 })
5522 .unwrap_or(false)
5523 }
5524
5525 fn cycle_inline_completion(
5526 &mut self,
5527 direction: Direction,
5528 window: &mut Window,
5529 cx: &mut Context<Self>,
5530 ) -> Option<()> {
5531 let provider = self.edit_prediction_provider()?;
5532 let cursor = self.selections.newest_anchor().head();
5533 let (buffer, cursor_buffer_position) =
5534 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5535 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5536 return None;
5537 }
5538
5539 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5540 self.update_visible_inline_completion(window, cx);
5541
5542 Some(())
5543 }
5544
5545 pub fn show_inline_completion(
5546 &mut self,
5547 _: &ShowEditPrediction,
5548 window: &mut Window,
5549 cx: &mut Context<Self>,
5550 ) {
5551 if !self.has_active_inline_completion() {
5552 self.refresh_inline_completion(false, true, window, cx);
5553 return;
5554 }
5555
5556 self.update_visible_inline_completion(window, cx);
5557 }
5558
5559 pub fn display_cursor_names(
5560 &mut self,
5561 _: &DisplayCursorNames,
5562 window: &mut Window,
5563 cx: &mut Context<Self>,
5564 ) {
5565 self.show_cursor_names(window, cx);
5566 }
5567
5568 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5569 self.show_cursor_names = true;
5570 cx.notify();
5571 cx.spawn_in(window, async move |this, cx| {
5572 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5573 this.update(cx, |this, cx| {
5574 this.show_cursor_names = false;
5575 cx.notify()
5576 })
5577 .ok()
5578 })
5579 .detach();
5580 }
5581
5582 pub fn next_edit_prediction(
5583 &mut self,
5584 _: &NextEditPrediction,
5585 window: &mut Window,
5586 cx: &mut Context<Self>,
5587 ) {
5588 if self.has_active_inline_completion() {
5589 self.cycle_inline_completion(Direction::Next, window, cx);
5590 } else {
5591 let is_copilot_disabled = self
5592 .refresh_inline_completion(false, true, window, cx)
5593 .is_none();
5594 if is_copilot_disabled {
5595 cx.propagate();
5596 }
5597 }
5598 }
5599
5600 pub fn previous_edit_prediction(
5601 &mut self,
5602 _: &PreviousEditPrediction,
5603 window: &mut Window,
5604 cx: &mut Context<Self>,
5605 ) {
5606 if self.has_active_inline_completion() {
5607 self.cycle_inline_completion(Direction::Prev, window, cx);
5608 } else {
5609 let is_copilot_disabled = self
5610 .refresh_inline_completion(false, true, window, cx)
5611 .is_none();
5612 if is_copilot_disabled {
5613 cx.propagate();
5614 }
5615 }
5616 }
5617
5618 pub fn accept_edit_prediction(
5619 &mut self,
5620 _: &AcceptEditPrediction,
5621 window: &mut Window,
5622 cx: &mut Context<Self>,
5623 ) {
5624 if self.show_edit_predictions_in_menu() {
5625 self.hide_context_menu(window, cx);
5626 }
5627
5628 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5629 return;
5630 };
5631
5632 self.report_inline_completion_event(
5633 active_inline_completion.completion_id.clone(),
5634 true,
5635 cx,
5636 );
5637
5638 match &active_inline_completion.completion {
5639 InlineCompletion::Move { target, .. } => {
5640 let target = *target;
5641
5642 if let Some(position_map) = &self.last_position_map {
5643 if position_map
5644 .visible_row_range
5645 .contains(&target.to_display_point(&position_map.snapshot).row())
5646 || !self.edit_prediction_requires_modifier()
5647 {
5648 self.unfold_ranges(&[target..target], true, false, cx);
5649 // Note that this is also done in vim's handler of the Tab action.
5650 self.change_selections(
5651 Some(Autoscroll::newest()),
5652 window,
5653 cx,
5654 |selections| {
5655 selections.select_anchor_ranges([target..target]);
5656 },
5657 );
5658 self.clear_row_highlights::<EditPredictionPreview>();
5659
5660 self.edit_prediction_preview
5661 .set_previous_scroll_position(None);
5662 } else {
5663 self.edit_prediction_preview
5664 .set_previous_scroll_position(Some(
5665 position_map.snapshot.scroll_anchor,
5666 ));
5667
5668 self.highlight_rows::<EditPredictionPreview>(
5669 target..target,
5670 cx.theme().colors().editor_highlighted_line_background,
5671 true,
5672 cx,
5673 );
5674 self.request_autoscroll(Autoscroll::fit(), cx);
5675 }
5676 }
5677 }
5678 InlineCompletion::Edit { edits, .. } => {
5679 if let Some(provider) = self.edit_prediction_provider() {
5680 provider.accept(cx);
5681 }
5682
5683 let snapshot = self.buffer.read(cx).snapshot(cx);
5684 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5685
5686 self.buffer.update(cx, |buffer, cx| {
5687 buffer.edit(edits.iter().cloned(), None, cx)
5688 });
5689
5690 self.change_selections(None, window, cx, |s| {
5691 s.select_anchor_ranges([last_edit_end..last_edit_end])
5692 });
5693
5694 self.update_visible_inline_completion(window, cx);
5695 if self.active_inline_completion.is_none() {
5696 self.refresh_inline_completion(true, true, window, cx);
5697 }
5698
5699 cx.notify();
5700 }
5701 }
5702
5703 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5704 }
5705
5706 pub fn accept_partial_inline_completion(
5707 &mut self,
5708 _: &AcceptPartialEditPrediction,
5709 window: &mut Window,
5710 cx: &mut Context<Self>,
5711 ) {
5712 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5713 return;
5714 };
5715 if self.selections.count() != 1 {
5716 return;
5717 }
5718
5719 self.report_inline_completion_event(
5720 active_inline_completion.completion_id.clone(),
5721 true,
5722 cx,
5723 );
5724
5725 match &active_inline_completion.completion {
5726 InlineCompletion::Move { target, .. } => {
5727 let target = *target;
5728 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5729 selections.select_anchor_ranges([target..target]);
5730 });
5731 }
5732 InlineCompletion::Edit { edits, .. } => {
5733 // Find an insertion that starts at the cursor position.
5734 let snapshot = self.buffer.read(cx).snapshot(cx);
5735 let cursor_offset = self.selections.newest::<usize>(cx).head();
5736 let insertion = edits.iter().find_map(|(range, text)| {
5737 let range = range.to_offset(&snapshot);
5738 if range.is_empty() && range.start == cursor_offset {
5739 Some(text)
5740 } else {
5741 None
5742 }
5743 });
5744
5745 if let Some(text) = insertion {
5746 let mut partial_completion = text
5747 .chars()
5748 .by_ref()
5749 .take_while(|c| c.is_alphabetic())
5750 .collect::<String>();
5751 if partial_completion.is_empty() {
5752 partial_completion = text
5753 .chars()
5754 .by_ref()
5755 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5756 .collect::<String>();
5757 }
5758
5759 cx.emit(EditorEvent::InputHandled {
5760 utf16_range_to_replace: None,
5761 text: partial_completion.clone().into(),
5762 });
5763
5764 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5765
5766 self.refresh_inline_completion(true, true, window, cx);
5767 cx.notify();
5768 } else {
5769 self.accept_edit_prediction(&Default::default(), window, cx);
5770 }
5771 }
5772 }
5773 }
5774
5775 fn discard_inline_completion(
5776 &mut self,
5777 should_report_inline_completion_event: bool,
5778 cx: &mut Context<Self>,
5779 ) -> bool {
5780 if should_report_inline_completion_event {
5781 let completion_id = self
5782 .active_inline_completion
5783 .as_ref()
5784 .and_then(|active_completion| active_completion.completion_id.clone());
5785
5786 self.report_inline_completion_event(completion_id, false, cx);
5787 }
5788
5789 if let Some(provider) = self.edit_prediction_provider() {
5790 provider.discard(cx);
5791 }
5792
5793 self.take_active_inline_completion(cx)
5794 }
5795
5796 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5797 let Some(provider) = self.edit_prediction_provider() else {
5798 return;
5799 };
5800
5801 let Some((_, buffer, _)) = self
5802 .buffer
5803 .read(cx)
5804 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5805 else {
5806 return;
5807 };
5808
5809 let extension = buffer
5810 .read(cx)
5811 .file()
5812 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5813
5814 let event_type = match accepted {
5815 true => "Edit Prediction Accepted",
5816 false => "Edit Prediction Discarded",
5817 };
5818 telemetry::event!(
5819 event_type,
5820 provider = provider.name(),
5821 prediction_id = id,
5822 suggestion_accepted = accepted,
5823 file_extension = extension,
5824 );
5825 }
5826
5827 pub fn has_active_inline_completion(&self) -> bool {
5828 self.active_inline_completion.is_some()
5829 }
5830
5831 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5832 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5833 return false;
5834 };
5835
5836 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5837 self.clear_highlights::<InlineCompletionHighlight>(cx);
5838 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5839 true
5840 }
5841
5842 /// Returns true when we're displaying the edit prediction popover below the cursor
5843 /// like we are not previewing and the LSP autocomplete menu is visible
5844 /// or we are in `when_holding_modifier` mode.
5845 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5846 if self.edit_prediction_preview_is_active()
5847 || !self.show_edit_predictions_in_menu()
5848 || !self.edit_predictions_enabled()
5849 {
5850 return false;
5851 }
5852
5853 if self.has_visible_completions_menu() {
5854 return true;
5855 }
5856
5857 has_completion && self.edit_prediction_requires_modifier()
5858 }
5859
5860 fn handle_modifiers_changed(
5861 &mut self,
5862 modifiers: Modifiers,
5863 position_map: &PositionMap,
5864 window: &mut Window,
5865 cx: &mut Context<Self>,
5866 ) {
5867 if self.show_edit_predictions_in_menu() {
5868 self.update_edit_prediction_preview(&modifiers, window, cx);
5869 }
5870
5871 self.update_selection_mode(&modifiers, position_map, window, cx);
5872
5873 let mouse_position = window.mouse_position();
5874 if !position_map.text_hitbox.is_hovered(window) {
5875 return;
5876 }
5877
5878 self.update_hovered_link(
5879 position_map.point_for_position(mouse_position),
5880 &position_map.snapshot,
5881 modifiers,
5882 window,
5883 cx,
5884 )
5885 }
5886
5887 fn update_selection_mode(
5888 &mut self,
5889 modifiers: &Modifiers,
5890 position_map: &PositionMap,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) {
5894 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5895 return;
5896 }
5897
5898 let mouse_position = window.mouse_position();
5899 let point_for_position = position_map.point_for_position(mouse_position);
5900 let position = point_for_position.previous_valid;
5901
5902 self.select(
5903 SelectPhase::BeginColumnar {
5904 position,
5905 reset: false,
5906 goal_column: point_for_position.exact_unclipped.column(),
5907 },
5908 window,
5909 cx,
5910 );
5911 }
5912
5913 fn update_edit_prediction_preview(
5914 &mut self,
5915 modifiers: &Modifiers,
5916 window: &mut Window,
5917 cx: &mut Context<Self>,
5918 ) {
5919 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5920 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5921 return;
5922 };
5923
5924 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5925 if matches!(
5926 self.edit_prediction_preview,
5927 EditPredictionPreview::Inactive { .. }
5928 ) {
5929 self.edit_prediction_preview = EditPredictionPreview::Active {
5930 previous_scroll_position: None,
5931 since: Instant::now(),
5932 };
5933
5934 self.update_visible_inline_completion(window, cx);
5935 cx.notify();
5936 }
5937 } else if let EditPredictionPreview::Active {
5938 previous_scroll_position,
5939 since,
5940 } = self.edit_prediction_preview
5941 {
5942 if let (Some(previous_scroll_position), Some(position_map)) =
5943 (previous_scroll_position, self.last_position_map.as_ref())
5944 {
5945 self.set_scroll_position(
5946 previous_scroll_position
5947 .scroll_position(&position_map.snapshot.display_snapshot),
5948 window,
5949 cx,
5950 );
5951 }
5952
5953 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5954 released_too_fast: since.elapsed() < Duration::from_millis(200),
5955 };
5956 self.clear_row_highlights::<EditPredictionPreview>();
5957 self.update_visible_inline_completion(window, cx);
5958 cx.notify();
5959 }
5960 }
5961
5962 fn update_visible_inline_completion(
5963 &mut self,
5964 _window: &mut Window,
5965 cx: &mut Context<Self>,
5966 ) -> Option<()> {
5967 let selection = self.selections.newest_anchor();
5968 let cursor = selection.head();
5969 let multibuffer = self.buffer.read(cx).snapshot(cx);
5970 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5971 let excerpt_id = cursor.excerpt_id;
5972
5973 let show_in_menu = self.show_edit_predictions_in_menu();
5974 let completions_menu_has_precedence = !show_in_menu
5975 && (self.context_menu.borrow().is_some()
5976 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5977
5978 if completions_menu_has_precedence
5979 || !offset_selection.is_empty()
5980 || self
5981 .active_inline_completion
5982 .as_ref()
5983 .map_or(false, |completion| {
5984 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5985 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5986 !invalidation_range.contains(&offset_selection.head())
5987 })
5988 {
5989 self.discard_inline_completion(false, cx);
5990 return None;
5991 }
5992
5993 self.take_active_inline_completion(cx);
5994 let Some(provider) = self.edit_prediction_provider() else {
5995 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5996 return None;
5997 };
5998
5999 let (buffer, cursor_buffer_position) =
6000 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6001
6002 self.edit_prediction_settings =
6003 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6004
6005 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6006
6007 if self.edit_prediction_indent_conflict {
6008 let cursor_point = cursor.to_point(&multibuffer);
6009
6010 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6011
6012 if let Some((_, indent)) = indents.iter().next() {
6013 if indent.len == cursor_point.column {
6014 self.edit_prediction_indent_conflict = false;
6015 }
6016 }
6017 }
6018
6019 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6020 let edits = inline_completion
6021 .edits
6022 .into_iter()
6023 .flat_map(|(range, new_text)| {
6024 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6025 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6026 Some((start..end, new_text))
6027 })
6028 .collect::<Vec<_>>();
6029 if edits.is_empty() {
6030 return None;
6031 }
6032
6033 let first_edit_start = edits.first().unwrap().0.start;
6034 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6035 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6036
6037 let last_edit_end = edits.last().unwrap().0.end;
6038 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6039 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6040
6041 let cursor_row = cursor.to_point(&multibuffer).row;
6042
6043 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6044
6045 let mut inlay_ids = Vec::new();
6046 let invalidation_row_range;
6047 let move_invalidation_row_range = if cursor_row < edit_start_row {
6048 Some(cursor_row..edit_end_row)
6049 } else if cursor_row > edit_end_row {
6050 Some(edit_start_row..cursor_row)
6051 } else {
6052 None
6053 };
6054 let is_move =
6055 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6056 let completion = if is_move {
6057 invalidation_row_range =
6058 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6059 let target = first_edit_start;
6060 InlineCompletion::Move { target, snapshot }
6061 } else {
6062 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6063 && !self.inline_completions_hidden_for_vim_mode;
6064
6065 if show_completions_in_buffer {
6066 if edits
6067 .iter()
6068 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6069 {
6070 let mut inlays = Vec::new();
6071 for (range, new_text) in &edits {
6072 let inlay = Inlay::inline_completion(
6073 post_inc(&mut self.next_inlay_id),
6074 range.start,
6075 new_text.as_str(),
6076 );
6077 inlay_ids.push(inlay.id);
6078 inlays.push(inlay);
6079 }
6080
6081 self.splice_inlays(&[], inlays, cx);
6082 } else {
6083 let background_color = cx.theme().status().deleted_background;
6084 self.highlight_text::<InlineCompletionHighlight>(
6085 edits.iter().map(|(range, _)| range.clone()).collect(),
6086 HighlightStyle {
6087 background_color: Some(background_color),
6088 ..Default::default()
6089 },
6090 cx,
6091 );
6092 }
6093 }
6094
6095 invalidation_row_range = edit_start_row..edit_end_row;
6096
6097 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6098 if provider.show_tab_accept_marker() {
6099 EditDisplayMode::TabAccept
6100 } else {
6101 EditDisplayMode::Inline
6102 }
6103 } else {
6104 EditDisplayMode::DiffPopover
6105 };
6106
6107 InlineCompletion::Edit {
6108 edits,
6109 edit_preview: inline_completion.edit_preview,
6110 display_mode,
6111 snapshot,
6112 }
6113 };
6114
6115 let invalidation_range = multibuffer
6116 .anchor_before(Point::new(invalidation_row_range.start, 0))
6117 ..multibuffer.anchor_after(Point::new(
6118 invalidation_row_range.end,
6119 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6120 ));
6121
6122 self.stale_inline_completion_in_menu = None;
6123 self.active_inline_completion = Some(InlineCompletionState {
6124 inlay_ids,
6125 completion,
6126 completion_id: inline_completion.id,
6127 invalidation_range,
6128 });
6129
6130 cx.notify();
6131
6132 Some(())
6133 }
6134
6135 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6136 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6137 }
6138
6139 fn render_code_actions_indicator(
6140 &self,
6141 _style: &EditorStyle,
6142 row: DisplayRow,
6143 is_active: bool,
6144 breakpoint: Option<&(Anchor, Breakpoint)>,
6145 cx: &mut Context<Self>,
6146 ) -> Option<IconButton> {
6147 let color = Color::Muted;
6148 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6149
6150 if self.available_code_actions.is_some() {
6151 Some(
6152 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6153 .shape(ui::IconButtonShape::Square)
6154 .icon_size(IconSize::XSmall)
6155 .icon_color(color)
6156 .toggle_state(is_active)
6157 .tooltip({
6158 let focus_handle = self.focus_handle.clone();
6159 move |window, cx| {
6160 Tooltip::for_action_in(
6161 "Toggle Code Actions",
6162 &ToggleCodeActions {
6163 deployed_from_indicator: None,
6164 },
6165 &focus_handle,
6166 window,
6167 cx,
6168 )
6169 }
6170 })
6171 .on_click(cx.listener(move |editor, _e, window, cx| {
6172 window.focus(&editor.focus_handle(cx));
6173 editor.toggle_code_actions(
6174 &ToggleCodeActions {
6175 deployed_from_indicator: Some(row),
6176 },
6177 window,
6178 cx,
6179 );
6180 }))
6181 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6182 editor.set_breakpoint_context_menu(
6183 row,
6184 position,
6185 event.down.position,
6186 window,
6187 cx,
6188 );
6189 })),
6190 )
6191 } else {
6192 None
6193 }
6194 }
6195
6196 fn clear_tasks(&mut self) {
6197 self.tasks.clear()
6198 }
6199
6200 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6201 if self.tasks.insert(key, value).is_some() {
6202 // This case should hopefully be rare, but just in case...
6203 log::error!("multiple different run targets found on a single line, only the last target will be rendered")
6204 }
6205 }
6206
6207 /// Get all display points of breakpoints that will be rendered within editor
6208 ///
6209 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6210 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6211 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6212 fn active_breakpoints(
6213 &mut self,
6214 range: Range<DisplayRow>,
6215 window: &mut Window,
6216 cx: &mut Context<Self>,
6217 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6218 let mut breakpoint_display_points = HashMap::default();
6219
6220 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6221 return breakpoint_display_points;
6222 };
6223
6224 let snapshot = self.snapshot(window, cx);
6225
6226 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6227 let Some(project) = self.project.as_ref() else {
6228 return breakpoint_display_points;
6229 };
6230
6231 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6232 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6233
6234 for (buffer_snapshot, range, excerpt_id) in
6235 multi_buffer_snapshot.range_to_buffer_ranges(range)
6236 {
6237 let Some(buffer) = project.read_with(cx, |this, cx| {
6238 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6239 }) else {
6240 continue;
6241 };
6242 let breakpoints = breakpoint_store.read(cx).breakpoints(
6243 &buffer,
6244 Some(
6245 buffer_snapshot.anchor_before(range.start)
6246 ..buffer_snapshot.anchor_after(range.end),
6247 ),
6248 buffer_snapshot,
6249 cx,
6250 );
6251 for (anchor, breakpoint) in breakpoints {
6252 let multi_buffer_anchor =
6253 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6254 let position = multi_buffer_anchor
6255 .to_point(&multi_buffer_snapshot)
6256 .to_display_point(&snapshot);
6257
6258 breakpoint_display_points
6259 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6260 }
6261 }
6262
6263 breakpoint_display_points
6264 }
6265
6266 fn breakpoint_context_menu(
6267 &self,
6268 anchor: Anchor,
6269 window: &mut Window,
6270 cx: &mut Context<Self>,
6271 ) -> Entity<ui::ContextMenu> {
6272 let weak_editor = cx.weak_entity();
6273 let focus_handle = self.focus_handle(cx);
6274
6275 let row = self
6276 .buffer
6277 .read(cx)
6278 .snapshot(cx)
6279 .summary_for_anchor::<Point>(&anchor)
6280 .row;
6281
6282 let breakpoint = self
6283 .breakpoint_at_row(row, window, cx)
6284 .map(|(_, bp)| Arc::from(bp));
6285
6286 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.message.is_some()) {
6287 "Edit Log Breakpoint"
6288 } else {
6289 "Set Log Breakpoint"
6290 };
6291
6292 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6293 "Unset Breakpoint"
6294 } else {
6295 "Set Breakpoint"
6296 };
6297
6298 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.state {
6299 BreakpointState::Enabled => Some("Disable"),
6300 BreakpointState::Disabled => Some("Enable"),
6301 });
6302
6303 let breakpoint = breakpoint.unwrap_or_else(|| {
6304 Arc::new(Breakpoint {
6305 state: BreakpointState::Enabled,
6306 message: None,
6307 })
6308 });
6309
6310 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6311 menu.on_blur_subscription(Subscription::new(|| {}))
6312 .context(focus_handle)
6313 .when_some(toggle_state_msg, |this, msg| {
6314 this.entry(msg, None, {
6315 let weak_editor = weak_editor.clone();
6316 let breakpoint = breakpoint.clone();
6317 move |_window, cx| {
6318 weak_editor
6319 .update(cx, |this, cx| {
6320 this.edit_breakpoint_at_anchor(
6321 anchor,
6322 breakpoint.as_ref().clone(),
6323 BreakpointEditAction::InvertState,
6324 cx,
6325 );
6326 })
6327 .log_err();
6328 }
6329 })
6330 })
6331 .entry(set_breakpoint_msg, None, {
6332 let weak_editor = weak_editor.clone();
6333 let breakpoint = breakpoint.clone();
6334 move |_window, cx| {
6335 weak_editor
6336 .update(cx, |this, cx| {
6337 this.edit_breakpoint_at_anchor(
6338 anchor,
6339 breakpoint.as_ref().clone(),
6340 BreakpointEditAction::Toggle,
6341 cx,
6342 );
6343 })
6344 .log_err();
6345 }
6346 })
6347 .entry(log_breakpoint_msg, None, move |window, cx| {
6348 weak_editor
6349 .update(cx, |this, cx| {
6350 this.add_edit_breakpoint_block(anchor, breakpoint.as_ref(), window, cx);
6351 })
6352 .log_err();
6353 })
6354 })
6355 }
6356
6357 fn render_breakpoint(
6358 &self,
6359 position: Anchor,
6360 row: DisplayRow,
6361 breakpoint: &Breakpoint,
6362 cx: &mut Context<Self>,
6363 ) -> IconButton {
6364 let (color, icon) = {
6365 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6366 (false, false) => ui::IconName::DebugBreakpoint,
6367 (true, false) => ui::IconName::DebugLogBreakpoint,
6368 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6369 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6370 };
6371
6372 let color = if self
6373 .gutter_breakpoint_indicator
6374 .0
6375 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6376 {
6377 Color::Hint
6378 } else {
6379 Color::Debugger
6380 };
6381
6382 (color, icon)
6383 };
6384
6385 let breakpoint = Arc::from(breakpoint.clone());
6386
6387 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6388 .icon_size(IconSize::XSmall)
6389 .size(ui::ButtonSize::None)
6390 .icon_color(color)
6391 .style(ButtonStyle::Transparent)
6392 .on_click(cx.listener({
6393 let breakpoint = breakpoint.clone();
6394
6395 move |editor, event: &ClickEvent, window, cx| {
6396 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6397 BreakpointEditAction::InvertState
6398 } else {
6399 BreakpointEditAction::Toggle
6400 };
6401
6402 window.focus(&editor.focus_handle(cx));
6403 editor.edit_breakpoint_at_anchor(
6404 position,
6405 breakpoint.as_ref().clone(),
6406 edit_action,
6407 cx,
6408 );
6409 }
6410 }))
6411 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6412 editor.set_breakpoint_context_menu(
6413 row,
6414 Some(position),
6415 event.down.position,
6416 window,
6417 cx,
6418 );
6419 }))
6420 }
6421
6422 fn build_tasks_context(
6423 project: &Entity<Project>,
6424 buffer: &Entity<Buffer>,
6425 buffer_row: u32,
6426 tasks: &Arc<RunnableTasks>,
6427 cx: &mut Context<Self>,
6428 ) -> Task<Option<task::TaskContext>> {
6429 let position = Point::new(buffer_row, tasks.column);
6430 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6431 let location = Location {
6432 buffer: buffer.clone(),
6433 range: range_start..range_start,
6434 };
6435 // Fill in the environmental variables from the tree-sitter captures
6436 let mut captured_task_variables = TaskVariables::default();
6437 for (capture_name, value) in tasks.extra_variables.clone() {
6438 captured_task_variables.insert(
6439 task::VariableName::Custom(capture_name.into()),
6440 value.clone(),
6441 );
6442 }
6443 project.update(cx, |project, cx| {
6444 project.task_store().update(cx, |task_store, cx| {
6445 task_store.task_context_for_location(captured_task_variables, location, cx)
6446 })
6447 })
6448 }
6449
6450 pub fn spawn_nearest_task(
6451 &mut self,
6452 action: &SpawnNearestTask,
6453 window: &mut Window,
6454 cx: &mut Context<Self>,
6455 ) {
6456 let Some((workspace, _)) = self.workspace.clone() else {
6457 return;
6458 };
6459 let Some(project) = self.project.clone() else {
6460 return;
6461 };
6462
6463 // Try to find a closest, enclosing node using tree-sitter that has a
6464 // task
6465 let Some((buffer, buffer_row, tasks)) = self
6466 .find_enclosing_node_task(cx)
6467 // Or find the task that's closest in row-distance.
6468 .or_else(|| self.find_closest_task(cx))
6469 else {
6470 return;
6471 };
6472
6473 let reveal_strategy = action.reveal;
6474 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6475 cx.spawn_in(window, async move |_, cx| {
6476 let context = task_context.await?;
6477 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6478
6479 let resolved = resolved_task.resolved.as_mut()?;
6480 resolved.reveal = reveal_strategy;
6481
6482 workspace
6483 .update(cx, |workspace, cx| {
6484 workspace::tasks::schedule_resolved_task(
6485 workspace,
6486 task_source_kind,
6487 resolved_task,
6488 false,
6489 cx,
6490 );
6491 })
6492 .ok()
6493 })
6494 .detach();
6495 }
6496
6497 fn find_closest_task(
6498 &mut self,
6499 cx: &mut Context<Self>,
6500 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6501 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6502
6503 let ((buffer_id, row), tasks) = self
6504 .tasks
6505 .iter()
6506 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6507
6508 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6509 let tasks = Arc::new(tasks.to_owned());
6510 Some((buffer, *row, tasks))
6511 }
6512
6513 fn find_enclosing_node_task(
6514 &mut self,
6515 cx: &mut Context<Self>,
6516 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6517 let snapshot = self.buffer.read(cx).snapshot(cx);
6518 let offset = self.selections.newest::<usize>(cx).head();
6519 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6520 let buffer_id = excerpt.buffer().remote_id();
6521
6522 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6523 let mut cursor = layer.node().walk();
6524
6525 while cursor.goto_first_child_for_byte(offset).is_some() {
6526 if cursor.node().end_byte() == offset {
6527 cursor.goto_next_sibling();
6528 }
6529 }
6530
6531 // Ascend to the smallest ancestor that contains the range and has a task.
6532 loop {
6533 let node = cursor.node();
6534 let node_range = node.byte_range();
6535 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6536
6537 // Check if this node contains our offset
6538 if node_range.start <= offset && node_range.end >= offset {
6539 // If it contains offset, check for task
6540 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6541 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6542 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6543 }
6544 }
6545
6546 if !cursor.goto_parent() {
6547 break;
6548 }
6549 }
6550 None
6551 }
6552
6553 fn render_run_indicator(
6554 &self,
6555 _style: &EditorStyle,
6556 is_active: bool,
6557 row: DisplayRow,
6558 breakpoint: Option<(Anchor, Breakpoint)>,
6559 cx: &mut Context<Self>,
6560 ) -> IconButton {
6561 let color = Color::Muted;
6562 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6563
6564 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6565 .shape(ui::IconButtonShape::Square)
6566 .icon_size(IconSize::XSmall)
6567 .icon_color(color)
6568 .toggle_state(is_active)
6569 .on_click(cx.listener(move |editor, _e, window, cx| {
6570 window.focus(&editor.focus_handle(cx));
6571 editor.toggle_code_actions(
6572 &ToggleCodeActions {
6573 deployed_from_indicator: Some(row),
6574 },
6575 window,
6576 cx,
6577 );
6578 }))
6579 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6580 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6581 }))
6582 }
6583
6584 pub fn context_menu_visible(&self) -> bool {
6585 !self.edit_prediction_preview_is_active()
6586 && self
6587 .context_menu
6588 .borrow()
6589 .as_ref()
6590 .map_or(false, |menu| menu.visible())
6591 }
6592
6593 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6594 self.context_menu
6595 .borrow()
6596 .as_ref()
6597 .map(|menu| menu.origin())
6598 }
6599
6600 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6601 self.context_menu_options = Some(options);
6602 }
6603
6604 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6605 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6606
6607 fn render_edit_prediction_popover(
6608 &mut self,
6609 text_bounds: &Bounds<Pixels>,
6610 content_origin: gpui::Point<Pixels>,
6611 editor_snapshot: &EditorSnapshot,
6612 visible_row_range: Range<DisplayRow>,
6613 scroll_top: f32,
6614 scroll_bottom: f32,
6615 line_layouts: &[LineWithInvisibles],
6616 line_height: Pixels,
6617 scroll_pixel_position: gpui::Point<Pixels>,
6618 newest_selection_head: Option<DisplayPoint>,
6619 editor_width: Pixels,
6620 style: &EditorStyle,
6621 window: &mut Window,
6622 cx: &mut App,
6623 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6624 let active_inline_completion = self.active_inline_completion.as_ref()?;
6625
6626 if self.edit_prediction_visible_in_cursor_popover(true) {
6627 return None;
6628 }
6629
6630 match &active_inline_completion.completion {
6631 InlineCompletion::Move { target, .. } => {
6632 let target_display_point = target.to_display_point(editor_snapshot);
6633
6634 if self.edit_prediction_requires_modifier() {
6635 if !self.edit_prediction_preview_is_active() {
6636 return None;
6637 }
6638
6639 self.render_edit_prediction_modifier_jump_popover(
6640 text_bounds,
6641 content_origin,
6642 visible_row_range,
6643 line_layouts,
6644 line_height,
6645 scroll_pixel_position,
6646 newest_selection_head,
6647 target_display_point,
6648 window,
6649 cx,
6650 )
6651 } else {
6652 self.render_edit_prediction_eager_jump_popover(
6653 text_bounds,
6654 content_origin,
6655 editor_snapshot,
6656 visible_row_range,
6657 scroll_top,
6658 scroll_bottom,
6659 line_height,
6660 scroll_pixel_position,
6661 target_display_point,
6662 editor_width,
6663 window,
6664 cx,
6665 )
6666 }
6667 }
6668 InlineCompletion::Edit {
6669 display_mode: EditDisplayMode::Inline,
6670 ..
6671 } => None,
6672 InlineCompletion::Edit {
6673 display_mode: EditDisplayMode::TabAccept,
6674 edits,
6675 ..
6676 } => {
6677 let range = &edits.first()?.0;
6678 let target_display_point = range.end.to_display_point(editor_snapshot);
6679
6680 self.render_edit_prediction_end_of_line_popover(
6681 "Accept",
6682 editor_snapshot,
6683 visible_row_range,
6684 target_display_point,
6685 line_height,
6686 scroll_pixel_position,
6687 content_origin,
6688 editor_width,
6689 window,
6690 cx,
6691 )
6692 }
6693 InlineCompletion::Edit {
6694 edits,
6695 edit_preview,
6696 display_mode: EditDisplayMode::DiffPopover,
6697 snapshot,
6698 } => self.render_edit_prediction_diff_popover(
6699 text_bounds,
6700 content_origin,
6701 editor_snapshot,
6702 visible_row_range,
6703 line_layouts,
6704 line_height,
6705 scroll_pixel_position,
6706 newest_selection_head,
6707 editor_width,
6708 style,
6709 edits,
6710 edit_preview,
6711 snapshot,
6712 window,
6713 cx,
6714 ),
6715 }
6716 }
6717
6718 fn render_edit_prediction_modifier_jump_popover(
6719 &mut self,
6720 text_bounds: &Bounds<Pixels>,
6721 content_origin: gpui::Point<Pixels>,
6722 visible_row_range: Range<DisplayRow>,
6723 line_layouts: &[LineWithInvisibles],
6724 line_height: Pixels,
6725 scroll_pixel_position: gpui::Point<Pixels>,
6726 newest_selection_head: Option<DisplayPoint>,
6727 target_display_point: DisplayPoint,
6728 window: &mut Window,
6729 cx: &mut App,
6730 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6731 let scrolled_content_origin =
6732 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6733
6734 const SCROLL_PADDING_Y: Pixels = px(12.);
6735
6736 if target_display_point.row() < visible_row_range.start {
6737 return self.render_edit_prediction_scroll_popover(
6738 |_| SCROLL_PADDING_Y,
6739 IconName::ArrowUp,
6740 visible_row_range,
6741 line_layouts,
6742 newest_selection_head,
6743 scrolled_content_origin,
6744 window,
6745 cx,
6746 );
6747 } else if target_display_point.row() >= visible_row_range.end {
6748 return self.render_edit_prediction_scroll_popover(
6749 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6750 IconName::ArrowDown,
6751 visible_row_range,
6752 line_layouts,
6753 newest_selection_head,
6754 scrolled_content_origin,
6755 window,
6756 cx,
6757 );
6758 }
6759
6760 const POLE_WIDTH: Pixels = px(2.);
6761
6762 let line_layout =
6763 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6764 let target_column = target_display_point.column() as usize;
6765
6766 let target_x = line_layout.x_for_index(target_column);
6767 let target_y =
6768 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6769
6770 let flag_on_right = target_x < text_bounds.size.width / 2.;
6771
6772 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6773 border_color.l += 0.001;
6774
6775 let mut element = v_flex()
6776 .items_end()
6777 .when(flag_on_right, |el| el.items_start())
6778 .child(if flag_on_right {
6779 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6780 .rounded_bl(px(0.))
6781 .rounded_tl(px(0.))
6782 .border_l_2()
6783 .border_color(border_color)
6784 } else {
6785 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6786 .rounded_br(px(0.))
6787 .rounded_tr(px(0.))
6788 .border_r_2()
6789 .border_color(border_color)
6790 })
6791 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6792 .into_any();
6793
6794 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6795
6796 let mut origin = scrolled_content_origin + point(target_x, target_y)
6797 - point(
6798 if flag_on_right {
6799 POLE_WIDTH
6800 } else {
6801 size.width - POLE_WIDTH
6802 },
6803 size.height - line_height,
6804 );
6805
6806 origin.x = origin.x.max(content_origin.x);
6807
6808 element.prepaint_at(origin, window, cx);
6809
6810 Some((element, origin))
6811 }
6812
6813 fn render_edit_prediction_scroll_popover(
6814 &mut self,
6815 to_y: impl Fn(Size<Pixels>) -> Pixels,
6816 scroll_icon: IconName,
6817 visible_row_range: Range<DisplayRow>,
6818 line_layouts: &[LineWithInvisibles],
6819 newest_selection_head: Option<DisplayPoint>,
6820 scrolled_content_origin: gpui::Point<Pixels>,
6821 window: &mut Window,
6822 cx: &mut App,
6823 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6824 let mut element = self
6825 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6826 .into_any();
6827
6828 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6829
6830 let cursor = newest_selection_head?;
6831 let cursor_row_layout =
6832 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6833 let cursor_column = cursor.column() as usize;
6834
6835 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6836
6837 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6838
6839 element.prepaint_at(origin, window, cx);
6840 Some((element, origin))
6841 }
6842
6843 fn render_edit_prediction_eager_jump_popover(
6844 &mut self,
6845 text_bounds: &Bounds<Pixels>,
6846 content_origin: gpui::Point<Pixels>,
6847 editor_snapshot: &EditorSnapshot,
6848 visible_row_range: Range<DisplayRow>,
6849 scroll_top: f32,
6850 scroll_bottom: f32,
6851 line_height: Pixels,
6852 scroll_pixel_position: gpui::Point<Pixels>,
6853 target_display_point: DisplayPoint,
6854 editor_width: Pixels,
6855 window: &mut Window,
6856 cx: &mut App,
6857 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6858 if target_display_point.row().as_f32() < scroll_top {
6859 let mut element = self
6860 .render_edit_prediction_line_popover(
6861 "Jump to Edit",
6862 Some(IconName::ArrowUp),
6863 window,
6864 cx,
6865 )?
6866 .into_any();
6867
6868 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6869 let offset = point(
6870 (text_bounds.size.width - size.width) / 2.,
6871 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6872 );
6873
6874 let origin = text_bounds.origin + offset;
6875 element.prepaint_at(origin, window, cx);
6876 Some((element, origin))
6877 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6878 let mut element = self
6879 .render_edit_prediction_line_popover(
6880 "Jump to Edit",
6881 Some(IconName::ArrowDown),
6882 window,
6883 cx,
6884 )?
6885 .into_any();
6886
6887 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6888 let offset = point(
6889 (text_bounds.size.width - size.width) / 2.,
6890 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6891 );
6892
6893 let origin = text_bounds.origin + offset;
6894 element.prepaint_at(origin, window, cx);
6895 Some((element, origin))
6896 } else {
6897 self.render_edit_prediction_end_of_line_popover(
6898 "Jump to Edit",
6899 editor_snapshot,
6900 visible_row_range,
6901 target_display_point,
6902 line_height,
6903 scroll_pixel_position,
6904 content_origin,
6905 editor_width,
6906 window,
6907 cx,
6908 )
6909 }
6910 }
6911
6912 fn render_edit_prediction_end_of_line_popover(
6913 self: &mut Editor,
6914 label: &'static str,
6915 editor_snapshot: &EditorSnapshot,
6916 visible_row_range: Range<DisplayRow>,
6917 target_display_point: DisplayPoint,
6918 line_height: Pixels,
6919 scroll_pixel_position: gpui::Point<Pixels>,
6920 content_origin: gpui::Point<Pixels>,
6921 editor_width: Pixels,
6922 window: &mut Window,
6923 cx: &mut App,
6924 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6925 let target_line_end = DisplayPoint::new(
6926 target_display_point.row(),
6927 editor_snapshot.line_len(target_display_point.row()),
6928 );
6929
6930 let mut element = self
6931 .render_edit_prediction_line_popover(label, None, window, cx)?
6932 .into_any();
6933
6934 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6935
6936 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
6937
6938 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
6939 let mut origin = start_point
6940 + line_origin
6941 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
6942 origin.x = origin.x.max(content_origin.x);
6943
6944 let max_x = content_origin.x + editor_width - size.width;
6945
6946 if origin.x > max_x {
6947 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
6948
6949 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
6950 origin.y += offset;
6951 IconName::ArrowUp
6952 } else {
6953 origin.y -= offset;
6954 IconName::ArrowDown
6955 };
6956
6957 element = self
6958 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
6959 .into_any();
6960
6961 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6962
6963 origin.x = content_origin.x + editor_width - size.width - px(2.);
6964 }
6965
6966 element.prepaint_at(origin, window, cx);
6967 Some((element, origin))
6968 }
6969
6970 fn render_edit_prediction_diff_popover(
6971 self: &Editor,
6972 text_bounds: &Bounds<Pixels>,
6973 content_origin: gpui::Point<Pixels>,
6974 editor_snapshot: &EditorSnapshot,
6975 visible_row_range: Range<DisplayRow>,
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 edits: &Vec<(Range<Anchor>, String)>,
6983 edit_preview: &Option<language::EditPreview>,
6984 snapshot: &language::BufferSnapshot,
6985 window: &mut Window,
6986 cx: &mut App,
6987 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6988 let edit_start = edits
6989 .first()
6990 .unwrap()
6991 .0
6992 .start
6993 .to_display_point(editor_snapshot);
6994 let edit_end = edits
6995 .last()
6996 .unwrap()
6997 .0
6998 .end
6999 .to_display_point(editor_snapshot);
7000
7001 let is_visible = visible_row_range.contains(&edit_start.row())
7002 || visible_row_range.contains(&edit_end.row());
7003 if !is_visible {
7004 return None;
7005 }
7006
7007 let highlighted_edits =
7008 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7009
7010 let styled_text = highlighted_edits.to_styled_text(&style.text);
7011 let line_count = highlighted_edits.text.lines().count();
7012
7013 const BORDER_WIDTH: Pixels = px(1.);
7014
7015 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7016 let has_keybind = keybind.is_some();
7017
7018 let mut element = h_flex()
7019 .items_start()
7020 .child(
7021 h_flex()
7022 .bg(cx.theme().colors().editor_background)
7023 .border(BORDER_WIDTH)
7024 .shadow_sm()
7025 .border_color(cx.theme().colors().border)
7026 .rounded_l_lg()
7027 .when(line_count > 1, |el| el.rounded_br_lg())
7028 .pr_1()
7029 .child(styled_text),
7030 )
7031 .child(
7032 h_flex()
7033 .h(line_height + BORDER_WIDTH * 2.)
7034 .px_1p5()
7035 .gap_1()
7036 // Workaround: For some reason, there's a gap if we don't do this
7037 .ml(-BORDER_WIDTH)
7038 .shadow(smallvec![gpui::BoxShadow {
7039 color: gpui::black().opacity(0.05),
7040 offset: point(px(1.), px(1.)),
7041 blur_radius: px(2.),
7042 spread_radius: px(0.),
7043 }])
7044 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7045 .border(BORDER_WIDTH)
7046 .border_color(cx.theme().colors().border)
7047 .rounded_r_lg()
7048 .id("edit_prediction_diff_popover_keybind")
7049 .when(!has_keybind, |el| {
7050 let status_colors = cx.theme().status();
7051
7052 el.bg(status_colors.error_background)
7053 .border_color(status_colors.error.opacity(0.6))
7054 .child(Icon::new(IconName::Info).color(Color::Error))
7055 .cursor_default()
7056 .hoverable_tooltip(move |_window, cx| {
7057 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7058 })
7059 })
7060 .children(keybind),
7061 )
7062 .into_any();
7063
7064 let longest_row =
7065 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7066 let longest_line_width = if visible_row_range.contains(&longest_row) {
7067 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7068 } else {
7069 layout_line(
7070 longest_row,
7071 editor_snapshot,
7072 style,
7073 editor_width,
7074 |_| false,
7075 window,
7076 cx,
7077 )
7078 .width
7079 };
7080
7081 let viewport_bounds =
7082 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7083 right: -EditorElement::SCROLLBAR_WIDTH,
7084 ..Default::default()
7085 });
7086
7087 let x_after_longest =
7088 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7089 - scroll_pixel_position.x;
7090
7091 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7092
7093 // Fully visible if it can be displayed within the window (allow overlapping other
7094 // panes). However, this is only allowed if the popover starts within text_bounds.
7095 let can_position_to_the_right = x_after_longest < text_bounds.right()
7096 && x_after_longest + element_bounds.width < viewport_bounds.right();
7097
7098 let mut origin = if can_position_to_the_right {
7099 point(
7100 x_after_longest,
7101 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7102 - scroll_pixel_position.y,
7103 )
7104 } else {
7105 let cursor_row = newest_selection_head.map(|head| head.row());
7106 let above_edit = edit_start
7107 .row()
7108 .0
7109 .checked_sub(line_count as u32)
7110 .map(DisplayRow);
7111 let below_edit = Some(edit_end.row() + 1);
7112 let above_cursor =
7113 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7114 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7115
7116 // Place the edit popover adjacent to the edit if there is a location
7117 // available that is onscreen and does not obscure the cursor. Otherwise,
7118 // place it adjacent to the cursor.
7119 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7120 .into_iter()
7121 .flatten()
7122 .find(|&start_row| {
7123 let end_row = start_row + line_count as u32;
7124 visible_row_range.contains(&start_row)
7125 && visible_row_range.contains(&end_row)
7126 && cursor_row.map_or(true, |cursor_row| {
7127 !((start_row..end_row).contains(&cursor_row))
7128 })
7129 })?;
7130
7131 content_origin
7132 + point(
7133 -scroll_pixel_position.x,
7134 row_target.as_f32() * line_height - scroll_pixel_position.y,
7135 )
7136 };
7137
7138 origin.x -= BORDER_WIDTH;
7139
7140 window.defer_draw(element, origin, 1);
7141
7142 // Do not return an element, since it will already be drawn due to defer_draw.
7143 None
7144 }
7145
7146 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7147 px(30.)
7148 }
7149
7150 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7151 if self.read_only(cx) {
7152 cx.theme().players().read_only()
7153 } else {
7154 self.style.as_ref().unwrap().local_player
7155 }
7156 }
7157
7158 fn render_edit_prediction_accept_keybind(
7159 &self,
7160 window: &mut Window,
7161 cx: &App,
7162 ) -> Option<AnyElement> {
7163 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7164 let accept_keystroke = accept_binding.keystroke()?;
7165
7166 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7167
7168 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7169 Color::Accent
7170 } else {
7171 Color::Muted
7172 };
7173
7174 h_flex()
7175 .px_0p5()
7176 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7177 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7178 .text_size(TextSize::XSmall.rems(cx))
7179 .child(h_flex().children(ui::render_modifiers(
7180 &accept_keystroke.modifiers,
7181 PlatformStyle::platform(),
7182 Some(modifiers_color),
7183 Some(IconSize::XSmall.rems().into()),
7184 true,
7185 )))
7186 .when(is_platform_style_mac, |parent| {
7187 parent.child(accept_keystroke.key.clone())
7188 })
7189 .when(!is_platform_style_mac, |parent| {
7190 parent.child(
7191 Key::new(
7192 util::capitalize(&accept_keystroke.key),
7193 Some(Color::Default),
7194 )
7195 .size(Some(IconSize::XSmall.rems().into())),
7196 )
7197 })
7198 .into_any()
7199 .into()
7200 }
7201
7202 fn render_edit_prediction_line_popover(
7203 &self,
7204 label: impl Into<SharedString>,
7205 icon: Option<IconName>,
7206 window: &mut Window,
7207 cx: &App,
7208 ) -> Option<Stateful<Div>> {
7209 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7210
7211 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7212 let has_keybind = keybind.is_some();
7213
7214 let result = h_flex()
7215 .id("ep-line-popover")
7216 .py_0p5()
7217 .pl_1()
7218 .pr(padding_right)
7219 .gap_1()
7220 .rounded_md()
7221 .border_1()
7222 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7223 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7224 .shadow_sm()
7225 .when(!has_keybind, |el| {
7226 let status_colors = cx.theme().status();
7227
7228 el.bg(status_colors.error_background)
7229 .border_color(status_colors.error.opacity(0.6))
7230 .pl_2()
7231 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7232 .cursor_default()
7233 .hoverable_tooltip(move |_window, cx| {
7234 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7235 })
7236 })
7237 .children(keybind)
7238 .child(
7239 Label::new(label)
7240 .size(LabelSize::Small)
7241 .when(!has_keybind, |el| {
7242 el.color(cx.theme().status().error.into()).strikethrough()
7243 }),
7244 )
7245 .when(!has_keybind, |el| {
7246 el.child(
7247 h_flex().ml_1().child(
7248 Icon::new(IconName::Info)
7249 .size(IconSize::Small)
7250 .color(cx.theme().status().error.into()),
7251 ),
7252 )
7253 })
7254 .when_some(icon, |element, icon| {
7255 element.child(
7256 div()
7257 .mt(px(1.5))
7258 .child(Icon::new(icon).size(IconSize::Small)),
7259 )
7260 });
7261
7262 Some(result)
7263 }
7264
7265 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7266 let accent_color = cx.theme().colors().text_accent;
7267 let editor_bg_color = cx.theme().colors().editor_background;
7268 editor_bg_color.blend(accent_color.opacity(0.1))
7269 }
7270
7271 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7272 let accent_color = cx.theme().colors().text_accent;
7273 let editor_bg_color = cx.theme().colors().editor_background;
7274 editor_bg_color.blend(accent_color.opacity(0.6))
7275 }
7276
7277 fn render_edit_prediction_cursor_popover(
7278 &self,
7279 min_width: Pixels,
7280 max_width: Pixels,
7281 cursor_point: Point,
7282 style: &EditorStyle,
7283 accept_keystroke: Option<&gpui::Keystroke>,
7284 _window: &Window,
7285 cx: &mut Context<Editor>,
7286 ) -> Option<AnyElement> {
7287 let provider = self.edit_prediction_provider.as_ref()?;
7288
7289 if provider.provider.needs_terms_acceptance(cx) {
7290 return Some(
7291 h_flex()
7292 .min_w(min_width)
7293 .flex_1()
7294 .px_2()
7295 .py_1()
7296 .gap_3()
7297 .elevation_2(cx)
7298 .hover(|style| style.bg(cx.theme().colors().element_hover))
7299 .id("accept-terms")
7300 .cursor_pointer()
7301 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7302 .on_click(cx.listener(|this, _event, window, cx| {
7303 cx.stop_propagation();
7304 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7305 window.dispatch_action(
7306 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7307 cx,
7308 );
7309 }))
7310 .child(
7311 h_flex()
7312 .flex_1()
7313 .gap_2()
7314 .child(Icon::new(IconName::ZedPredict))
7315 .child(Label::new("Accept Terms of Service"))
7316 .child(div().w_full())
7317 .child(
7318 Icon::new(IconName::ArrowUpRight)
7319 .color(Color::Muted)
7320 .size(IconSize::Small),
7321 )
7322 .into_any_element(),
7323 )
7324 .into_any(),
7325 );
7326 }
7327
7328 let is_refreshing = provider.provider.is_refreshing(cx);
7329
7330 fn pending_completion_container() -> Div {
7331 h_flex()
7332 .h_full()
7333 .flex_1()
7334 .gap_2()
7335 .child(Icon::new(IconName::ZedPredict))
7336 }
7337
7338 let completion = match &self.active_inline_completion {
7339 Some(prediction) => {
7340 if !self.has_visible_completions_menu() {
7341 const RADIUS: Pixels = px(6.);
7342 const BORDER_WIDTH: Pixels = px(1.);
7343
7344 return Some(
7345 h_flex()
7346 .elevation_2(cx)
7347 .border(BORDER_WIDTH)
7348 .border_color(cx.theme().colors().border)
7349 .when(accept_keystroke.is_none(), |el| {
7350 el.border_color(cx.theme().status().error)
7351 })
7352 .rounded(RADIUS)
7353 .rounded_tl(px(0.))
7354 .overflow_hidden()
7355 .child(div().px_1p5().child(match &prediction.completion {
7356 InlineCompletion::Move { target, snapshot } => {
7357 use text::ToPoint as _;
7358 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7359 {
7360 Icon::new(IconName::ZedPredictDown)
7361 } else {
7362 Icon::new(IconName::ZedPredictUp)
7363 }
7364 }
7365 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7366 }))
7367 .child(
7368 h_flex()
7369 .gap_1()
7370 .py_1()
7371 .px_2()
7372 .rounded_r(RADIUS - BORDER_WIDTH)
7373 .border_l_1()
7374 .border_color(cx.theme().colors().border)
7375 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7376 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7377 el.child(
7378 Label::new("Hold")
7379 .size(LabelSize::Small)
7380 .when(accept_keystroke.is_none(), |el| {
7381 el.strikethrough()
7382 })
7383 .line_height_style(LineHeightStyle::UiLabel),
7384 )
7385 })
7386 .id("edit_prediction_cursor_popover_keybind")
7387 .when(accept_keystroke.is_none(), |el| {
7388 let status_colors = cx.theme().status();
7389
7390 el.bg(status_colors.error_background)
7391 .border_color(status_colors.error.opacity(0.6))
7392 .child(Icon::new(IconName::Info).color(Color::Error))
7393 .cursor_default()
7394 .hoverable_tooltip(move |_window, cx| {
7395 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7396 .into()
7397 })
7398 })
7399 .when_some(
7400 accept_keystroke.as_ref(),
7401 |el, accept_keystroke| {
7402 el.child(h_flex().children(ui::render_modifiers(
7403 &accept_keystroke.modifiers,
7404 PlatformStyle::platform(),
7405 Some(Color::Default),
7406 Some(IconSize::XSmall.rems().into()),
7407 false,
7408 )))
7409 },
7410 ),
7411 )
7412 .into_any(),
7413 );
7414 }
7415
7416 self.render_edit_prediction_cursor_popover_preview(
7417 prediction,
7418 cursor_point,
7419 style,
7420 cx,
7421 )?
7422 }
7423
7424 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7425 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7426 stale_completion,
7427 cursor_point,
7428 style,
7429 cx,
7430 )?,
7431
7432 None => {
7433 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7434 }
7435 },
7436
7437 None => pending_completion_container().child(Label::new("No Prediction")),
7438 };
7439
7440 let completion = if is_refreshing {
7441 completion
7442 .with_animation(
7443 "loading-completion",
7444 Animation::new(Duration::from_secs(2))
7445 .repeat()
7446 .with_easing(pulsating_between(0.4, 0.8)),
7447 |label, delta| label.opacity(delta),
7448 )
7449 .into_any_element()
7450 } else {
7451 completion.into_any_element()
7452 };
7453
7454 let has_completion = self.active_inline_completion.is_some();
7455
7456 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7457 Some(
7458 h_flex()
7459 .min_w(min_width)
7460 .max_w(max_width)
7461 .flex_1()
7462 .elevation_2(cx)
7463 .border_color(cx.theme().colors().border)
7464 .child(
7465 div()
7466 .flex_1()
7467 .py_1()
7468 .px_2()
7469 .overflow_hidden()
7470 .child(completion),
7471 )
7472 .when_some(accept_keystroke, |el, accept_keystroke| {
7473 if !accept_keystroke.modifiers.modified() {
7474 return el;
7475 }
7476
7477 el.child(
7478 h_flex()
7479 .h_full()
7480 .border_l_1()
7481 .rounded_r_lg()
7482 .border_color(cx.theme().colors().border)
7483 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7484 .gap_1()
7485 .py_1()
7486 .px_2()
7487 .child(
7488 h_flex()
7489 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7490 .when(is_platform_style_mac, |parent| parent.gap_1())
7491 .child(h_flex().children(ui::render_modifiers(
7492 &accept_keystroke.modifiers,
7493 PlatformStyle::platform(),
7494 Some(if !has_completion {
7495 Color::Muted
7496 } else {
7497 Color::Default
7498 }),
7499 None,
7500 false,
7501 ))),
7502 )
7503 .child(Label::new("Preview").into_any_element())
7504 .opacity(if has_completion { 1.0 } else { 0.4 }),
7505 )
7506 })
7507 .into_any(),
7508 )
7509 }
7510
7511 fn render_edit_prediction_cursor_popover_preview(
7512 &self,
7513 completion: &InlineCompletionState,
7514 cursor_point: Point,
7515 style: &EditorStyle,
7516 cx: &mut Context<Editor>,
7517 ) -> Option<Div> {
7518 use text::ToPoint as _;
7519
7520 fn render_relative_row_jump(
7521 prefix: impl Into<String>,
7522 current_row: u32,
7523 target_row: u32,
7524 ) -> Div {
7525 let (row_diff, arrow) = if target_row < current_row {
7526 (current_row - target_row, IconName::ArrowUp)
7527 } else {
7528 (target_row - current_row, IconName::ArrowDown)
7529 };
7530
7531 h_flex()
7532 .child(
7533 Label::new(format!("{}{}", prefix.into(), row_diff))
7534 .color(Color::Muted)
7535 .size(LabelSize::Small),
7536 )
7537 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7538 }
7539
7540 match &completion.completion {
7541 InlineCompletion::Move {
7542 target, snapshot, ..
7543 } => Some(
7544 h_flex()
7545 .px_2()
7546 .gap_2()
7547 .flex_1()
7548 .child(
7549 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7550 Icon::new(IconName::ZedPredictDown)
7551 } else {
7552 Icon::new(IconName::ZedPredictUp)
7553 },
7554 )
7555 .child(Label::new("Jump to Edit")),
7556 ),
7557
7558 InlineCompletion::Edit {
7559 edits,
7560 edit_preview,
7561 snapshot,
7562 display_mode: _,
7563 } => {
7564 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7565
7566 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7567 &snapshot,
7568 &edits,
7569 edit_preview.as_ref()?,
7570 true,
7571 cx,
7572 )
7573 .first_line_preview();
7574
7575 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7576 .with_default_highlights(&style.text, highlighted_edits.highlights);
7577
7578 let preview = h_flex()
7579 .gap_1()
7580 .min_w_16()
7581 .child(styled_text)
7582 .when(has_more_lines, |parent| parent.child("…"));
7583
7584 let left = if first_edit_row != cursor_point.row {
7585 render_relative_row_jump("", cursor_point.row, first_edit_row)
7586 .into_any_element()
7587 } else {
7588 Icon::new(IconName::ZedPredict).into_any_element()
7589 };
7590
7591 Some(
7592 h_flex()
7593 .h_full()
7594 .flex_1()
7595 .gap_2()
7596 .pr_1()
7597 .overflow_x_hidden()
7598 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7599 .child(left)
7600 .child(preview),
7601 )
7602 }
7603 }
7604 }
7605
7606 fn render_context_menu(
7607 &self,
7608 style: &EditorStyle,
7609 max_height_in_lines: u32,
7610 y_flipped: bool,
7611 window: &mut Window,
7612 cx: &mut Context<Editor>,
7613 ) -> Option<AnyElement> {
7614 let menu = self.context_menu.borrow();
7615 let menu = menu.as_ref()?;
7616 if !menu.visible() {
7617 return None;
7618 };
7619 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7620 }
7621
7622 fn render_context_menu_aside(
7623 &mut self,
7624 max_size: Size<Pixels>,
7625 window: &mut Window,
7626 cx: &mut Context<Editor>,
7627 ) -> Option<AnyElement> {
7628 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7629 if menu.visible() {
7630 menu.render_aside(self, max_size, window, cx)
7631 } else {
7632 None
7633 }
7634 })
7635 }
7636
7637 fn hide_context_menu(
7638 &mut self,
7639 window: &mut Window,
7640 cx: &mut Context<Self>,
7641 ) -> Option<CodeContextMenu> {
7642 cx.notify();
7643 self.completion_tasks.clear();
7644 let context_menu = self.context_menu.borrow_mut().take();
7645 self.stale_inline_completion_in_menu.take();
7646 self.update_visible_inline_completion(window, cx);
7647 context_menu
7648 }
7649
7650 fn show_snippet_choices(
7651 &mut self,
7652 choices: &Vec<String>,
7653 selection: Range<Anchor>,
7654 cx: &mut Context<Self>,
7655 ) {
7656 if selection.start.buffer_id.is_none() {
7657 return;
7658 }
7659 let buffer_id = selection.start.buffer_id.unwrap();
7660 let buffer = self.buffer().read(cx).buffer(buffer_id);
7661 let id = post_inc(&mut self.next_completion_id);
7662
7663 if let Some(buffer) = buffer {
7664 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7665 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7666 ));
7667 }
7668 }
7669
7670 pub fn insert_snippet(
7671 &mut self,
7672 insertion_ranges: &[Range<usize>],
7673 snippet: Snippet,
7674 window: &mut Window,
7675 cx: &mut Context<Self>,
7676 ) -> Result<()> {
7677 struct Tabstop<T> {
7678 is_end_tabstop: bool,
7679 ranges: Vec<Range<T>>,
7680 choices: Option<Vec<String>>,
7681 }
7682
7683 let tabstops = self.buffer.update(cx, |buffer, cx| {
7684 let snippet_text: Arc<str> = snippet.text.clone().into();
7685 let edits = insertion_ranges
7686 .iter()
7687 .cloned()
7688 .map(|range| (range, snippet_text.clone()));
7689 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7690
7691 let snapshot = &*buffer.read(cx);
7692 let snippet = &snippet;
7693 snippet
7694 .tabstops
7695 .iter()
7696 .map(|tabstop| {
7697 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7698 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7699 });
7700 let mut tabstop_ranges = tabstop
7701 .ranges
7702 .iter()
7703 .flat_map(|tabstop_range| {
7704 let mut delta = 0_isize;
7705 insertion_ranges.iter().map(move |insertion_range| {
7706 let insertion_start = insertion_range.start as isize + delta;
7707 delta +=
7708 snippet.text.len() as isize - insertion_range.len() as isize;
7709
7710 let start = ((insertion_start + tabstop_range.start) as usize)
7711 .min(snapshot.len());
7712 let end = ((insertion_start + tabstop_range.end) as usize)
7713 .min(snapshot.len());
7714 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7715 })
7716 })
7717 .collect::<Vec<_>>();
7718 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7719
7720 Tabstop {
7721 is_end_tabstop,
7722 ranges: tabstop_ranges,
7723 choices: tabstop.choices.clone(),
7724 }
7725 })
7726 .collect::<Vec<_>>()
7727 });
7728 if let Some(tabstop) = tabstops.first() {
7729 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7730 s.select_ranges(tabstop.ranges.iter().cloned());
7731 });
7732
7733 if let Some(choices) = &tabstop.choices {
7734 if let Some(selection) = tabstop.ranges.first() {
7735 self.show_snippet_choices(choices, selection.clone(), cx)
7736 }
7737 }
7738
7739 // If we're already at the last tabstop and it's at the end of the snippet,
7740 // we're done, we don't need to keep the state around.
7741 if !tabstop.is_end_tabstop {
7742 let choices = tabstops
7743 .iter()
7744 .map(|tabstop| tabstop.choices.clone())
7745 .collect();
7746
7747 let ranges = tabstops
7748 .into_iter()
7749 .map(|tabstop| tabstop.ranges)
7750 .collect::<Vec<_>>();
7751
7752 self.snippet_stack.push(SnippetState {
7753 active_index: 0,
7754 ranges,
7755 choices,
7756 });
7757 }
7758
7759 // Check whether the just-entered snippet ends with an auto-closable bracket.
7760 if self.autoclose_regions.is_empty() {
7761 let snapshot = self.buffer.read(cx).snapshot(cx);
7762 for selection in &mut self.selections.all::<Point>(cx) {
7763 let selection_head = selection.head();
7764 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7765 continue;
7766 };
7767
7768 let mut bracket_pair = None;
7769 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7770 let prev_chars = snapshot
7771 .reversed_chars_at(selection_head)
7772 .collect::<String>();
7773 for (pair, enabled) in scope.brackets() {
7774 if enabled
7775 && pair.close
7776 && prev_chars.starts_with(pair.start.as_str())
7777 && next_chars.starts_with(pair.end.as_str())
7778 {
7779 bracket_pair = Some(pair.clone());
7780 break;
7781 }
7782 }
7783 if let Some(pair) = bracket_pair {
7784 let start = snapshot.anchor_after(selection_head);
7785 let end = snapshot.anchor_after(selection_head);
7786 self.autoclose_regions.push(AutocloseRegion {
7787 selection_id: selection.id,
7788 range: start..end,
7789 pair,
7790 });
7791 }
7792 }
7793 }
7794 }
7795 Ok(())
7796 }
7797
7798 pub fn move_to_next_snippet_tabstop(
7799 &mut self,
7800 window: &mut Window,
7801 cx: &mut Context<Self>,
7802 ) -> bool {
7803 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7804 }
7805
7806 pub fn move_to_prev_snippet_tabstop(
7807 &mut self,
7808 window: &mut Window,
7809 cx: &mut Context<Self>,
7810 ) -> bool {
7811 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7812 }
7813
7814 pub fn move_to_snippet_tabstop(
7815 &mut self,
7816 bias: Bias,
7817 window: &mut Window,
7818 cx: &mut Context<Self>,
7819 ) -> bool {
7820 if let Some(mut snippet) = self.snippet_stack.pop() {
7821 match bias {
7822 Bias::Left => {
7823 if snippet.active_index > 0 {
7824 snippet.active_index -= 1;
7825 } else {
7826 self.snippet_stack.push(snippet);
7827 return false;
7828 }
7829 }
7830 Bias::Right => {
7831 if snippet.active_index + 1 < snippet.ranges.len() {
7832 snippet.active_index += 1;
7833 } else {
7834 self.snippet_stack.push(snippet);
7835 return false;
7836 }
7837 }
7838 }
7839 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7840 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7841 s.select_anchor_ranges(current_ranges.iter().cloned())
7842 });
7843
7844 if let Some(choices) = &snippet.choices[snippet.active_index] {
7845 if let Some(selection) = current_ranges.first() {
7846 self.show_snippet_choices(&choices, selection.clone(), cx);
7847 }
7848 }
7849
7850 // If snippet state is not at the last tabstop, push it back on the stack
7851 if snippet.active_index + 1 < snippet.ranges.len() {
7852 self.snippet_stack.push(snippet);
7853 }
7854 return true;
7855 }
7856 }
7857
7858 false
7859 }
7860
7861 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7862 self.transact(window, cx, |this, window, cx| {
7863 this.select_all(&SelectAll, window, cx);
7864 this.insert("", window, cx);
7865 });
7866 }
7867
7868 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7869 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7870 self.transact(window, cx, |this, window, cx| {
7871 this.select_autoclose_pair(window, cx);
7872 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7873 if !this.linked_edit_ranges.is_empty() {
7874 let selections = this.selections.all::<MultiBufferPoint>(cx);
7875 let snapshot = this.buffer.read(cx).snapshot(cx);
7876
7877 for selection in selections.iter() {
7878 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7879 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7880 if selection_start.buffer_id != selection_end.buffer_id {
7881 continue;
7882 }
7883 if let Some(ranges) =
7884 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7885 {
7886 for (buffer, entries) in ranges {
7887 linked_ranges.entry(buffer).or_default().extend(entries);
7888 }
7889 }
7890 }
7891 }
7892
7893 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7894 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7895 for selection in &mut selections {
7896 if selection.is_empty() {
7897 let old_head = selection.head();
7898 let mut new_head =
7899 movement::left(&display_map, old_head.to_display_point(&display_map))
7900 .to_point(&display_map);
7901 if let Some((buffer, line_buffer_range)) = display_map
7902 .buffer_snapshot
7903 .buffer_line_for_row(MultiBufferRow(old_head.row))
7904 {
7905 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
7906 let indent_len = match indent_size.kind {
7907 IndentKind::Space => {
7908 buffer.settings_at(line_buffer_range.start, cx).tab_size
7909 }
7910 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7911 };
7912 if old_head.column <= indent_size.len && old_head.column > 0 {
7913 let indent_len = indent_len.get();
7914 new_head = cmp::min(
7915 new_head,
7916 MultiBufferPoint::new(
7917 old_head.row,
7918 ((old_head.column - 1) / indent_len) * indent_len,
7919 ),
7920 );
7921 }
7922 }
7923
7924 selection.set_head(new_head, SelectionGoal::None);
7925 }
7926 }
7927
7928 this.signature_help_state.set_backspace_pressed(true);
7929 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7930 s.select(selections)
7931 });
7932 this.insert("", window, cx);
7933 let empty_str: Arc<str> = Arc::from("");
7934 for (buffer, edits) in linked_ranges {
7935 let snapshot = buffer.read(cx).snapshot();
7936 use text::ToPoint as TP;
7937
7938 let edits = edits
7939 .into_iter()
7940 .map(|range| {
7941 let end_point = TP::to_point(&range.end, &snapshot);
7942 let mut start_point = TP::to_point(&range.start, &snapshot);
7943
7944 if end_point == start_point {
7945 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7946 .saturating_sub(1);
7947 start_point =
7948 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7949 };
7950
7951 (start_point..end_point, empty_str.clone())
7952 })
7953 .sorted_by_key(|(range, _)| range.start)
7954 .collect::<Vec<_>>();
7955 buffer.update(cx, |this, cx| {
7956 this.edit(edits, None, cx);
7957 })
7958 }
7959 this.refresh_inline_completion(true, false, window, cx);
7960 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7961 });
7962 }
7963
7964 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7966 self.transact(window, cx, |this, window, cx| {
7967 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7968 s.move_with(|map, selection| {
7969 if selection.is_empty() {
7970 let cursor = movement::right(map, selection.head());
7971 selection.end = cursor;
7972 selection.reversed = true;
7973 selection.goal = SelectionGoal::None;
7974 }
7975 })
7976 });
7977 this.insert("", window, cx);
7978 this.refresh_inline_completion(true, false, window, cx);
7979 });
7980 }
7981
7982 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7984 if self.move_to_prev_snippet_tabstop(window, cx) {
7985 return;
7986 }
7987 self.outdent(&Outdent, window, cx);
7988 }
7989
7990 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7991 if self.move_to_next_snippet_tabstop(window, cx) {
7992 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7993 return;
7994 }
7995 if self.read_only(cx) {
7996 return;
7997 }
7998 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7999 let mut selections = self.selections.all_adjusted(cx);
8000 let buffer = self.buffer.read(cx);
8001 let snapshot = buffer.snapshot(cx);
8002 let rows_iter = selections.iter().map(|s| s.head().row);
8003 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8004
8005 let mut edits = Vec::new();
8006 let mut prev_edited_row = 0;
8007 let mut row_delta = 0;
8008 for selection in &mut selections {
8009 if selection.start.row != prev_edited_row {
8010 row_delta = 0;
8011 }
8012 prev_edited_row = selection.end.row;
8013
8014 // If the selection is non-empty, then increase the indentation of the selected lines.
8015 if !selection.is_empty() {
8016 row_delta =
8017 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8018 continue;
8019 }
8020
8021 // If the selection is empty and the cursor is in the leading whitespace before the
8022 // suggested indentation, then auto-indent the line.
8023 let cursor = selection.head();
8024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8025 if let Some(suggested_indent) =
8026 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8027 {
8028 if cursor.column < suggested_indent.len
8029 && cursor.column <= current_indent.len
8030 && current_indent.len <= suggested_indent.len
8031 {
8032 selection.start = Point::new(cursor.row, suggested_indent.len);
8033 selection.end = selection.start;
8034 if row_delta == 0 {
8035 edits.extend(Buffer::edit_for_indent_size_adjustment(
8036 cursor.row,
8037 current_indent,
8038 suggested_indent,
8039 ));
8040 row_delta = suggested_indent.len - current_indent.len;
8041 }
8042 continue;
8043 }
8044 }
8045
8046 // Otherwise, insert a hard or soft tab.
8047 let settings = buffer.language_settings_at(cursor, cx);
8048 let tab_size = if settings.hard_tabs {
8049 IndentSize::tab()
8050 } else {
8051 let tab_size = settings.tab_size.get();
8052 let char_column = snapshot
8053 .text_for_range(Point::new(cursor.row, 0)..cursor)
8054 .flat_map(str::chars)
8055 .count()
8056 + row_delta as usize;
8057 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8058 IndentSize::spaces(chars_to_next_tab_stop)
8059 };
8060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8061 selection.end = selection.start;
8062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8063 row_delta += tab_size.len;
8064 }
8065
8066 self.transact(window, cx, |this, window, cx| {
8067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8068 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8069 s.select(selections)
8070 });
8071 this.refresh_inline_completion(true, false, window, cx);
8072 });
8073 }
8074
8075 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8076 if self.read_only(cx) {
8077 return;
8078 }
8079 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8080 let mut selections = self.selections.all::<Point>(cx);
8081 let mut prev_edited_row = 0;
8082 let mut row_delta = 0;
8083 let mut edits = Vec::new();
8084 let buffer = self.buffer.read(cx);
8085 let snapshot = buffer.snapshot(cx);
8086 for selection in &mut selections {
8087 if selection.start.row != prev_edited_row {
8088 row_delta = 0;
8089 }
8090 prev_edited_row = selection.end.row;
8091
8092 row_delta =
8093 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8094 }
8095
8096 self.transact(window, cx, |this, window, cx| {
8097 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8098 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8099 s.select(selections)
8100 });
8101 });
8102 }
8103
8104 fn indent_selection(
8105 buffer: &MultiBuffer,
8106 snapshot: &MultiBufferSnapshot,
8107 selection: &mut Selection<Point>,
8108 edits: &mut Vec<(Range<Point>, String)>,
8109 delta_for_start_row: u32,
8110 cx: &App,
8111 ) -> u32 {
8112 let settings = buffer.language_settings_at(selection.start, cx);
8113 let tab_size = settings.tab_size.get();
8114 let indent_kind = if settings.hard_tabs {
8115 IndentKind::Tab
8116 } else {
8117 IndentKind::Space
8118 };
8119 let mut start_row = selection.start.row;
8120 let mut end_row = selection.end.row + 1;
8121
8122 // If a selection ends at the beginning of a line, don't indent
8123 // that last line.
8124 if selection.end.column == 0 && selection.end.row > selection.start.row {
8125 end_row -= 1;
8126 }
8127
8128 // Avoid re-indenting a row that has already been indented by a
8129 // previous selection, but still update this selection's column
8130 // to reflect that indentation.
8131 if delta_for_start_row > 0 {
8132 start_row += 1;
8133 selection.start.column += delta_for_start_row;
8134 if selection.end.row == selection.start.row {
8135 selection.end.column += delta_for_start_row;
8136 }
8137 }
8138
8139 let mut delta_for_end_row = 0;
8140 let has_multiple_rows = start_row + 1 != end_row;
8141 for row in start_row..end_row {
8142 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8143 let indent_delta = match (current_indent.kind, indent_kind) {
8144 (IndentKind::Space, IndentKind::Space) => {
8145 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8146 IndentSize::spaces(columns_to_next_tab_stop)
8147 }
8148 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8149 (_, IndentKind::Tab) => IndentSize::tab(),
8150 };
8151
8152 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8153 0
8154 } else {
8155 selection.start.column
8156 };
8157 let row_start = Point::new(row, start);
8158 edits.push((
8159 row_start..row_start,
8160 indent_delta.chars().collect::<String>(),
8161 ));
8162
8163 // Update this selection's endpoints to reflect the indentation.
8164 if row == selection.start.row {
8165 selection.start.column += indent_delta.len;
8166 }
8167 if row == selection.end.row {
8168 selection.end.column += indent_delta.len;
8169 delta_for_end_row = indent_delta.len;
8170 }
8171 }
8172
8173 if selection.start.row == selection.end.row {
8174 delta_for_start_row + delta_for_end_row
8175 } else {
8176 delta_for_end_row
8177 }
8178 }
8179
8180 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8181 if self.read_only(cx) {
8182 return;
8183 }
8184 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8185 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8186 let selections = self.selections.all::<Point>(cx);
8187 let mut deletion_ranges = Vec::new();
8188 let mut last_outdent = None;
8189 {
8190 let buffer = self.buffer.read(cx);
8191 let snapshot = buffer.snapshot(cx);
8192 for selection in &selections {
8193 let settings = buffer.language_settings_at(selection.start, cx);
8194 let tab_size = settings.tab_size.get();
8195 let mut rows = selection.spanned_rows(false, &display_map);
8196
8197 // Avoid re-outdenting a row that has already been outdented by a
8198 // previous selection.
8199 if let Some(last_row) = last_outdent {
8200 if last_row == rows.start {
8201 rows.start = rows.start.next_row();
8202 }
8203 }
8204 let has_multiple_rows = rows.len() > 1;
8205 for row in rows.iter_rows() {
8206 let indent_size = snapshot.indent_size_for_line(row);
8207 if indent_size.len > 0 {
8208 let deletion_len = match indent_size.kind {
8209 IndentKind::Space => {
8210 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8211 if columns_to_prev_tab_stop == 0 {
8212 tab_size
8213 } else {
8214 columns_to_prev_tab_stop
8215 }
8216 }
8217 IndentKind::Tab => 1,
8218 };
8219 let start = if has_multiple_rows
8220 || deletion_len > selection.start.column
8221 || indent_size.len < selection.start.column
8222 {
8223 0
8224 } else {
8225 selection.start.column - deletion_len
8226 };
8227 deletion_ranges.push(
8228 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8229 );
8230 last_outdent = Some(row);
8231 }
8232 }
8233 }
8234 }
8235
8236 self.transact(window, cx, |this, window, cx| {
8237 this.buffer.update(cx, |buffer, cx| {
8238 let empty_str: Arc<str> = Arc::default();
8239 buffer.edit(
8240 deletion_ranges
8241 .into_iter()
8242 .map(|range| (range, empty_str.clone())),
8243 None,
8244 cx,
8245 );
8246 });
8247 let selections = this.selections.all::<usize>(cx);
8248 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8249 s.select(selections)
8250 });
8251 });
8252 }
8253
8254 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8255 if self.read_only(cx) {
8256 return;
8257 }
8258 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8259 let selections = self
8260 .selections
8261 .all::<usize>(cx)
8262 .into_iter()
8263 .map(|s| s.range());
8264
8265 self.transact(window, cx, |this, window, cx| {
8266 this.buffer.update(cx, |buffer, cx| {
8267 buffer.autoindent_ranges(selections, cx);
8268 });
8269 let selections = this.selections.all::<usize>(cx);
8270 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8271 s.select(selections)
8272 });
8273 });
8274 }
8275
8276 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8277 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8279 let selections = self.selections.all::<Point>(cx);
8280
8281 let mut new_cursors = Vec::new();
8282 let mut edit_ranges = Vec::new();
8283 let mut selections = selections.iter().peekable();
8284 while let Some(selection) = selections.next() {
8285 let mut rows = selection.spanned_rows(false, &display_map);
8286 let goal_display_column = selection.head().to_display_point(&display_map).column();
8287
8288 // Accumulate contiguous regions of rows that we want to delete.
8289 while let Some(next_selection) = selections.peek() {
8290 let next_rows = next_selection.spanned_rows(false, &display_map);
8291 if next_rows.start <= rows.end {
8292 rows.end = next_rows.end;
8293 selections.next().unwrap();
8294 } else {
8295 break;
8296 }
8297 }
8298
8299 let buffer = &display_map.buffer_snapshot;
8300 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8301 let edit_end;
8302 let cursor_buffer_row;
8303 if buffer.max_point().row >= rows.end.0 {
8304 // If there's a line after the range, delete the \n from the end of the row range
8305 // and position the cursor on the next line.
8306 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8307 cursor_buffer_row = rows.end;
8308 } else {
8309 // If there isn't a line after the range, delete the \n from the line before the
8310 // start of the row range and position the cursor there.
8311 edit_start = edit_start.saturating_sub(1);
8312 edit_end = buffer.len();
8313 cursor_buffer_row = rows.start.previous_row();
8314 }
8315
8316 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8317 *cursor.column_mut() =
8318 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8319
8320 new_cursors.push((
8321 selection.id,
8322 buffer.anchor_after(cursor.to_point(&display_map)),
8323 ));
8324 edit_ranges.push(edit_start..edit_end);
8325 }
8326
8327 self.transact(window, cx, |this, window, cx| {
8328 let buffer = this.buffer.update(cx, |buffer, cx| {
8329 let empty_str: Arc<str> = Arc::default();
8330 buffer.edit(
8331 edit_ranges
8332 .into_iter()
8333 .map(|range| (range, empty_str.clone())),
8334 None,
8335 cx,
8336 );
8337 buffer.snapshot(cx)
8338 });
8339 let new_selections = new_cursors
8340 .into_iter()
8341 .map(|(id, cursor)| {
8342 let cursor = cursor.to_point(&buffer);
8343 Selection {
8344 id,
8345 start: cursor,
8346 end: cursor,
8347 reversed: false,
8348 goal: SelectionGoal::None,
8349 }
8350 })
8351 .collect();
8352
8353 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8354 s.select(new_selections);
8355 });
8356 });
8357 }
8358
8359 pub fn join_lines_impl(
8360 &mut self,
8361 insert_whitespace: bool,
8362 window: &mut Window,
8363 cx: &mut Context<Self>,
8364 ) {
8365 if self.read_only(cx) {
8366 return;
8367 }
8368 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8369 for selection in self.selections.all::<Point>(cx) {
8370 let start = MultiBufferRow(selection.start.row);
8371 // Treat single line selections as if they include the next line. Otherwise this action
8372 // would do nothing for single line selections individual cursors.
8373 let end = if selection.start.row == selection.end.row {
8374 MultiBufferRow(selection.start.row + 1)
8375 } else {
8376 MultiBufferRow(selection.end.row)
8377 };
8378
8379 if let Some(last_row_range) = row_ranges.last_mut() {
8380 if start <= last_row_range.end {
8381 last_row_range.end = end;
8382 continue;
8383 }
8384 }
8385 row_ranges.push(start..end);
8386 }
8387
8388 let snapshot = self.buffer.read(cx).snapshot(cx);
8389 let mut cursor_positions = Vec::new();
8390 for row_range in &row_ranges {
8391 let anchor = snapshot.anchor_before(Point::new(
8392 row_range.end.previous_row().0,
8393 snapshot.line_len(row_range.end.previous_row()),
8394 ));
8395 cursor_positions.push(anchor..anchor);
8396 }
8397
8398 self.transact(window, cx, |this, window, cx| {
8399 for row_range in row_ranges.into_iter().rev() {
8400 for row in row_range.iter_rows().rev() {
8401 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8402 let next_line_row = row.next_row();
8403 let indent = snapshot.indent_size_for_line(next_line_row);
8404 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8405
8406 let replace =
8407 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8408 " "
8409 } else {
8410 ""
8411 };
8412
8413 this.buffer.update(cx, |buffer, cx| {
8414 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8415 });
8416 }
8417 }
8418
8419 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8420 s.select_anchor_ranges(cursor_positions)
8421 });
8422 });
8423 }
8424
8425 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8426 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8427 self.join_lines_impl(true, window, cx);
8428 }
8429
8430 pub fn sort_lines_case_sensitive(
8431 &mut self,
8432 _: &SortLinesCaseSensitive,
8433 window: &mut Window,
8434 cx: &mut Context<Self>,
8435 ) {
8436 self.manipulate_lines(window, cx, |lines| lines.sort())
8437 }
8438
8439 pub fn sort_lines_case_insensitive(
8440 &mut self,
8441 _: &SortLinesCaseInsensitive,
8442 window: &mut Window,
8443 cx: &mut Context<Self>,
8444 ) {
8445 self.manipulate_lines(window, cx, |lines| {
8446 lines.sort_by_key(|line| line.to_lowercase())
8447 })
8448 }
8449
8450 pub fn unique_lines_case_insensitive(
8451 &mut self,
8452 _: &UniqueLinesCaseInsensitive,
8453 window: &mut Window,
8454 cx: &mut Context<Self>,
8455 ) {
8456 self.manipulate_lines(window, cx, |lines| {
8457 let mut seen = HashSet::default();
8458 lines.retain(|line| seen.insert(line.to_lowercase()));
8459 })
8460 }
8461
8462 pub fn unique_lines_case_sensitive(
8463 &mut self,
8464 _: &UniqueLinesCaseSensitive,
8465 window: &mut Window,
8466 cx: &mut Context<Self>,
8467 ) {
8468 self.manipulate_lines(window, cx, |lines| {
8469 let mut seen = HashSet::default();
8470 lines.retain(|line| seen.insert(*line));
8471 })
8472 }
8473
8474 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8475 let Some(project) = self.project.clone() else {
8476 return;
8477 };
8478 self.reload(project, window, cx)
8479 .detach_and_notify_err(window, cx);
8480 }
8481
8482 pub fn restore_file(
8483 &mut self,
8484 _: &::git::RestoreFile,
8485 window: &mut Window,
8486 cx: &mut Context<Self>,
8487 ) {
8488 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8489 let mut buffer_ids = HashSet::default();
8490 let snapshot = self.buffer().read(cx).snapshot(cx);
8491 for selection in self.selections.all::<usize>(cx) {
8492 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8493 }
8494
8495 let buffer = self.buffer().read(cx);
8496 let ranges = buffer_ids
8497 .into_iter()
8498 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8499 .collect::<Vec<_>>();
8500
8501 self.restore_hunks_in_ranges(ranges, window, cx);
8502 }
8503
8504 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8505 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8506 let selections = self
8507 .selections
8508 .all(cx)
8509 .into_iter()
8510 .map(|s| s.range())
8511 .collect();
8512 self.restore_hunks_in_ranges(selections, window, cx);
8513 }
8514
8515 pub fn restore_hunks_in_ranges(
8516 &mut self,
8517 ranges: Vec<Range<Point>>,
8518 window: &mut Window,
8519 cx: &mut Context<Editor>,
8520 ) {
8521 let mut revert_changes = HashMap::default();
8522 let chunk_by = self
8523 .snapshot(window, cx)
8524 .hunks_for_ranges(ranges)
8525 .into_iter()
8526 .chunk_by(|hunk| hunk.buffer_id);
8527 for (buffer_id, hunks) in &chunk_by {
8528 let hunks = hunks.collect::<Vec<_>>();
8529 for hunk in &hunks {
8530 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8531 }
8532 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8533 }
8534 drop(chunk_by);
8535 if !revert_changes.is_empty() {
8536 self.transact(window, cx, |editor, window, cx| {
8537 editor.restore(revert_changes, window, cx);
8538 });
8539 }
8540 }
8541
8542 pub fn open_active_item_in_terminal(
8543 &mut self,
8544 _: &OpenInTerminal,
8545 window: &mut Window,
8546 cx: &mut Context<Self>,
8547 ) {
8548 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8549 let project_path = buffer.read(cx).project_path(cx)?;
8550 let project = self.project.as_ref()?.read(cx);
8551 let entry = project.entry_for_path(&project_path, cx)?;
8552 let parent = match &entry.canonical_path {
8553 Some(canonical_path) => canonical_path.to_path_buf(),
8554 None => project.absolute_path(&project_path, cx)?,
8555 }
8556 .parent()?
8557 .to_path_buf();
8558 Some(parent)
8559 }) {
8560 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8561 }
8562 }
8563
8564 fn set_breakpoint_context_menu(
8565 &mut self,
8566 display_row: DisplayRow,
8567 position: Option<Anchor>,
8568 clicked_point: gpui::Point<Pixels>,
8569 window: &mut Window,
8570 cx: &mut Context<Self>,
8571 ) {
8572 if !cx.has_flag::<Debugger>() {
8573 return;
8574 }
8575 let source = self
8576 .buffer
8577 .read(cx)
8578 .snapshot(cx)
8579 .anchor_before(Point::new(display_row.0, 0u32));
8580
8581 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8582
8583 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8584 self,
8585 source,
8586 clicked_point,
8587 context_menu,
8588 window,
8589 cx,
8590 );
8591 }
8592
8593 fn add_edit_breakpoint_block(
8594 &mut self,
8595 anchor: Anchor,
8596 breakpoint: &Breakpoint,
8597 window: &mut Window,
8598 cx: &mut Context<Self>,
8599 ) {
8600 let weak_editor = cx.weak_entity();
8601 let bp_prompt = cx.new(|cx| {
8602 BreakpointPromptEditor::new(weak_editor, anchor, breakpoint.clone(), window, cx)
8603 });
8604
8605 let height = bp_prompt.update(cx, |this, cx| {
8606 this.prompt
8607 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8608 });
8609 let cloned_prompt = bp_prompt.clone();
8610 let blocks = vec![BlockProperties {
8611 style: BlockStyle::Sticky,
8612 placement: BlockPlacement::Above(anchor),
8613 height,
8614 render: Arc::new(move |cx| {
8615 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8616 cloned_prompt.clone().into_any_element()
8617 }),
8618 priority: 0,
8619 }];
8620
8621 let focus_handle = bp_prompt.focus_handle(cx);
8622 window.focus(&focus_handle);
8623
8624 let block_ids = self.insert_blocks(blocks, None, cx);
8625 bp_prompt.update(cx, |prompt, _| {
8626 prompt.add_block_ids(block_ids);
8627 });
8628 }
8629
8630 fn breakpoint_at_cursor_head(
8631 &self,
8632 window: &mut Window,
8633 cx: &mut Context<Self>,
8634 ) -> Option<(Anchor, Breakpoint)> {
8635 let cursor_position: Point = self.selections.newest(cx).head();
8636 self.breakpoint_at_row(cursor_position.row, window, cx)
8637 }
8638
8639 pub(crate) fn breakpoint_at_row(
8640 &self,
8641 row: u32,
8642 window: &mut Window,
8643 cx: &mut Context<Self>,
8644 ) -> Option<(Anchor, Breakpoint)> {
8645 let snapshot = self.snapshot(window, cx);
8646 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8647
8648 let project = self.project.clone()?;
8649
8650 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8651 snapshot
8652 .buffer_snapshot
8653 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8654 })?;
8655
8656 let enclosing_excerpt = breakpoint_position.excerpt_id;
8657 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8658 let buffer_snapshot = buffer.read(cx).snapshot();
8659
8660 let row = buffer_snapshot
8661 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8662 .row;
8663
8664 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8665 let anchor_end = snapshot
8666 .buffer_snapshot
8667 .anchor_after(Point::new(row, line_len));
8668
8669 let bp = self
8670 .breakpoint_store
8671 .as_ref()?
8672 .read_with(cx, |breakpoint_store, cx| {
8673 breakpoint_store
8674 .breakpoints(
8675 &buffer,
8676 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8677 &buffer_snapshot,
8678 cx,
8679 )
8680 .next()
8681 .and_then(|(anchor, bp)| {
8682 let breakpoint_row = buffer_snapshot
8683 .summary_for_anchor::<text::PointUtf16>(anchor)
8684 .row;
8685
8686 if breakpoint_row == row {
8687 snapshot
8688 .buffer_snapshot
8689 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8690 .map(|anchor| (anchor, bp.clone()))
8691 } else {
8692 None
8693 }
8694 })
8695 });
8696 bp
8697 }
8698
8699 pub fn edit_log_breakpoint(
8700 &mut self,
8701 _: &EditLogBreakpoint,
8702 window: &mut Window,
8703 cx: &mut Context<Self>,
8704 ) {
8705 let (anchor, bp) = self
8706 .breakpoint_at_cursor_head(window, cx)
8707 .unwrap_or_else(|| {
8708 let cursor_position: Point = self.selections.newest(cx).head();
8709
8710 let breakpoint_position = self
8711 .snapshot(window, cx)
8712 .display_snapshot
8713 .buffer_snapshot
8714 .anchor_after(Point::new(cursor_position.row, 0));
8715
8716 (
8717 breakpoint_position,
8718 Breakpoint {
8719 message: None,
8720 state: BreakpointState::Enabled,
8721 },
8722 )
8723 });
8724
8725 self.add_edit_breakpoint_block(anchor, &bp, window, cx);
8726 }
8727
8728 pub fn enable_breakpoint(
8729 &mut self,
8730 _: &crate::actions::EnableBreakpoint,
8731 window: &mut Window,
8732 cx: &mut Context<Self>,
8733 ) {
8734 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8735 if breakpoint.is_disabled() {
8736 self.edit_breakpoint_at_anchor(
8737 anchor,
8738 breakpoint,
8739 BreakpointEditAction::InvertState,
8740 cx,
8741 );
8742 }
8743 }
8744 }
8745
8746 pub fn disable_breakpoint(
8747 &mut self,
8748 _: &crate::actions::DisableBreakpoint,
8749 window: &mut Window,
8750 cx: &mut Context<Self>,
8751 ) {
8752 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8753 if breakpoint.is_enabled() {
8754 self.edit_breakpoint_at_anchor(
8755 anchor,
8756 breakpoint,
8757 BreakpointEditAction::InvertState,
8758 cx,
8759 );
8760 }
8761 }
8762 }
8763
8764 pub fn toggle_breakpoint(
8765 &mut self,
8766 _: &crate::actions::ToggleBreakpoint,
8767 window: &mut Window,
8768 cx: &mut Context<Self>,
8769 ) {
8770 let edit_action = BreakpointEditAction::Toggle;
8771
8772 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8773 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8774 } else {
8775 let cursor_position: Point = self.selections.newest(cx).head();
8776
8777 let breakpoint_position = self
8778 .snapshot(window, cx)
8779 .display_snapshot
8780 .buffer_snapshot
8781 .anchor_after(Point::new(cursor_position.row, 0));
8782
8783 self.edit_breakpoint_at_anchor(
8784 breakpoint_position,
8785 Breakpoint::new_standard(),
8786 edit_action,
8787 cx,
8788 );
8789 }
8790 }
8791
8792 pub fn edit_breakpoint_at_anchor(
8793 &mut self,
8794 breakpoint_position: Anchor,
8795 breakpoint: Breakpoint,
8796 edit_action: BreakpointEditAction,
8797 cx: &mut Context<Self>,
8798 ) {
8799 let Some(breakpoint_store) = &self.breakpoint_store else {
8800 return;
8801 };
8802
8803 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8804 if breakpoint_position == Anchor::min() {
8805 self.buffer()
8806 .read(cx)
8807 .excerpt_buffer_ids()
8808 .into_iter()
8809 .next()
8810 } else {
8811 None
8812 }
8813 }) else {
8814 return;
8815 };
8816
8817 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8818 return;
8819 };
8820
8821 breakpoint_store.update(cx, |breakpoint_store, cx| {
8822 breakpoint_store.toggle_breakpoint(
8823 buffer,
8824 (breakpoint_position.text_anchor, breakpoint),
8825 edit_action,
8826 cx,
8827 );
8828 });
8829
8830 cx.notify();
8831 }
8832
8833 #[cfg(any(test, feature = "test-support"))]
8834 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8835 self.breakpoint_store.clone()
8836 }
8837
8838 pub fn prepare_restore_change(
8839 &self,
8840 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8841 hunk: &MultiBufferDiffHunk,
8842 cx: &mut App,
8843 ) -> Option<()> {
8844 if hunk.is_created_file() {
8845 return None;
8846 }
8847 let buffer = self.buffer.read(cx);
8848 let diff = buffer.diff_for(hunk.buffer_id)?;
8849 let buffer = buffer.buffer(hunk.buffer_id)?;
8850 let buffer = buffer.read(cx);
8851 let original_text = diff
8852 .read(cx)
8853 .base_text()
8854 .as_rope()
8855 .slice(hunk.diff_base_byte_range.clone());
8856 let buffer_snapshot = buffer.snapshot();
8857 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8858 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8859 probe
8860 .0
8861 .start
8862 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8863 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8864 }) {
8865 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8866 Some(())
8867 } else {
8868 None
8869 }
8870 }
8871
8872 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8873 self.manipulate_lines(window, cx, |lines| lines.reverse())
8874 }
8875
8876 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8877 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8878 }
8879
8880 fn manipulate_lines<Fn>(
8881 &mut self,
8882 window: &mut Window,
8883 cx: &mut Context<Self>,
8884 mut callback: Fn,
8885 ) where
8886 Fn: FnMut(&mut Vec<&str>),
8887 {
8888 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8889
8890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8891 let buffer = self.buffer.read(cx).snapshot(cx);
8892
8893 let mut edits = Vec::new();
8894
8895 let selections = self.selections.all::<Point>(cx);
8896 let mut selections = selections.iter().peekable();
8897 let mut contiguous_row_selections = Vec::new();
8898 let mut new_selections = Vec::new();
8899 let mut added_lines = 0;
8900 let mut removed_lines = 0;
8901
8902 while let Some(selection) = selections.next() {
8903 let (start_row, end_row) = consume_contiguous_rows(
8904 &mut contiguous_row_selections,
8905 selection,
8906 &display_map,
8907 &mut selections,
8908 );
8909
8910 let start_point = Point::new(start_row.0, 0);
8911 let end_point = Point::new(
8912 end_row.previous_row().0,
8913 buffer.line_len(end_row.previous_row()),
8914 );
8915 let text = buffer
8916 .text_for_range(start_point..end_point)
8917 .collect::<String>();
8918
8919 let mut lines = text.split('\n').collect_vec();
8920
8921 let lines_before = lines.len();
8922 callback(&mut lines);
8923 let lines_after = lines.len();
8924
8925 edits.push((start_point..end_point, lines.join("\n")));
8926
8927 // Selections must change based on added and removed line count
8928 let start_row =
8929 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8930 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8931 new_selections.push(Selection {
8932 id: selection.id,
8933 start: start_row,
8934 end: end_row,
8935 goal: SelectionGoal::None,
8936 reversed: selection.reversed,
8937 });
8938
8939 if lines_after > lines_before {
8940 added_lines += lines_after - lines_before;
8941 } else if lines_before > lines_after {
8942 removed_lines += lines_before - lines_after;
8943 }
8944 }
8945
8946 self.transact(window, cx, |this, window, cx| {
8947 let buffer = this.buffer.update(cx, |buffer, cx| {
8948 buffer.edit(edits, None, cx);
8949 buffer.snapshot(cx)
8950 });
8951
8952 // Recalculate offsets on newly edited buffer
8953 let new_selections = new_selections
8954 .iter()
8955 .map(|s| {
8956 let start_point = Point::new(s.start.0, 0);
8957 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8958 Selection {
8959 id: s.id,
8960 start: buffer.point_to_offset(start_point),
8961 end: buffer.point_to_offset(end_point),
8962 goal: s.goal,
8963 reversed: s.reversed,
8964 }
8965 })
8966 .collect();
8967
8968 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8969 s.select(new_selections);
8970 });
8971
8972 this.request_autoscroll(Autoscroll::fit(), cx);
8973 });
8974 }
8975
8976 pub fn convert_to_upper_case(
8977 &mut self,
8978 _: &ConvertToUpperCase,
8979 window: &mut Window,
8980 cx: &mut Context<Self>,
8981 ) {
8982 self.manipulate_text(window, cx, |text| text.to_uppercase())
8983 }
8984
8985 pub fn convert_to_lower_case(
8986 &mut self,
8987 _: &ConvertToLowerCase,
8988 window: &mut Window,
8989 cx: &mut Context<Self>,
8990 ) {
8991 self.manipulate_text(window, cx, |text| text.to_lowercase())
8992 }
8993
8994 pub fn convert_to_title_case(
8995 &mut self,
8996 _: &ConvertToTitleCase,
8997 window: &mut Window,
8998 cx: &mut Context<Self>,
8999 ) {
9000 self.manipulate_text(window, cx, |text| {
9001 text.split('\n')
9002 .map(|line| line.to_case(Case::Title))
9003 .join("\n")
9004 })
9005 }
9006
9007 pub fn convert_to_snake_case(
9008 &mut self,
9009 _: &ConvertToSnakeCase,
9010 window: &mut Window,
9011 cx: &mut Context<Self>,
9012 ) {
9013 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9014 }
9015
9016 pub fn convert_to_kebab_case(
9017 &mut self,
9018 _: &ConvertToKebabCase,
9019 window: &mut Window,
9020 cx: &mut Context<Self>,
9021 ) {
9022 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9023 }
9024
9025 pub fn convert_to_upper_camel_case(
9026 &mut self,
9027 _: &ConvertToUpperCamelCase,
9028 window: &mut Window,
9029 cx: &mut Context<Self>,
9030 ) {
9031 self.manipulate_text(window, cx, |text| {
9032 text.split('\n')
9033 .map(|line| line.to_case(Case::UpperCamel))
9034 .join("\n")
9035 })
9036 }
9037
9038 pub fn convert_to_lower_camel_case(
9039 &mut self,
9040 _: &ConvertToLowerCamelCase,
9041 window: &mut Window,
9042 cx: &mut Context<Self>,
9043 ) {
9044 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9045 }
9046
9047 pub fn convert_to_opposite_case(
9048 &mut self,
9049 _: &ConvertToOppositeCase,
9050 window: &mut Window,
9051 cx: &mut Context<Self>,
9052 ) {
9053 self.manipulate_text(window, cx, |text| {
9054 text.chars()
9055 .fold(String::with_capacity(text.len()), |mut t, c| {
9056 if c.is_uppercase() {
9057 t.extend(c.to_lowercase());
9058 } else {
9059 t.extend(c.to_uppercase());
9060 }
9061 t
9062 })
9063 })
9064 }
9065
9066 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9067 where
9068 Fn: FnMut(&str) -> String,
9069 {
9070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9071 let buffer = self.buffer.read(cx).snapshot(cx);
9072
9073 let mut new_selections = Vec::new();
9074 let mut edits = Vec::new();
9075 let mut selection_adjustment = 0i32;
9076
9077 for selection in self.selections.all::<usize>(cx) {
9078 let selection_is_empty = selection.is_empty();
9079
9080 let (start, end) = if selection_is_empty {
9081 let word_range = movement::surrounding_word(
9082 &display_map,
9083 selection.start.to_display_point(&display_map),
9084 );
9085 let start = word_range.start.to_offset(&display_map, Bias::Left);
9086 let end = word_range.end.to_offset(&display_map, Bias::Left);
9087 (start, end)
9088 } else {
9089 (selection.start, selection.end)
9090 };
9091
9092 let text = buffer.text_for_range(start..end).collect::<String>();
9093 let old_length = text.len() as i32;
9094 let text = callback(&text);
9095
9096 new_selections.push(Selection {
9097 start: (start as i32 - selection_adjustment) as usize,
9098 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9099 goal: SelectionGoal::None,
9100 ..selection
9101 });
9102
9103 selection_adjustment += old_length - text.len() as i32;
9104
9105 edits.push((start..end, text));
9106 }
9107
9108 self.transact(window, cx, |this, window, cx| {
9109 this.buffer.update(cx, |buffer, cx| {
9110 buffer.edit(edits, None, cx);
9111 });
9112
9113 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9114 s.select(new_selections);
9115 });
9116
9117 this.request_autoscroll(Autoscroll::fit(), cx);
9118 });
9119 }
9120
9121 pub fn duplicate(
9122 &mut self,
9123 upwards: bool,
9124 whole_lines: bool,
9125 window: &mut Window,
9126 cx: &mut Context<Self>,
9127 ) {
9128 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9129
9130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9131 let buffer = &display_map.buffer_snapshot;
9132 let selections = self.selections.all::<Point>(cx);
9133
9134 let mut edits = Vec::new();
9135 let mut selections_iter = selections.iter().peekable();
9136 while let Some(selection) = selections_iter.next() {
9137 let mut rows = selection.spanned_rows(false, &display_map);
9138 // duplicate line-wise
9139 if whole_lines || selection.start == selection.end {
9140 // Avoid duplicating the same lines twice.
9141 while let Some(next_selection) = selections_iter.peek() {
9142 let next_rows = next_selection.spanned_rows(false, &display_map);
9143 if next_rows.start < rows.end {
9144 rows.end = next_rows.end;
9145 selections_iter.next().unwrap();
9146 } else {
9147 break;
9148 }
9149 }
9150
9151 // Copy the text from the selected row region and splice it either at the start
9152 // or end of the region.
9153 let start = Point::new(rows.start.0, 0);
9154 let end = Point::new(
9155 rows.end.previous_row().0,
9156 buffer.line_len(rows.end.previous_row()),
9157 );
9158 let text = buffer
9159 .text_for_range(start..end)
9160 .chain(Some("\n"))
9161 .collect::<String>();
9162 let insert_location = if upwards {
9163 Point::new(rows.end.0, 0)
9164 } else {
9165 start
9166 };
9167 edits.push((insert_location..insert_location, text));
9168 } else {
9169 // duplicate character-wise
9170 let start = selection.start;
9171 let end = selection.end;
9172 let text = buffer.text_for_range(start..end).collect::<String>();
9173 edits.push((selection.end..selection.end, text));
9174 }
9175 }
9176
9177 self.transact(window, cx, |this, _, cx| {
9178 this.buffer.update(cx, |buffer, cx| {
9179 buffer.edit(edits, None, cx);
9180 });
9181
9182 this.request_autoscroll(Autoscroll::fit(), cx);
9183 });
9184 }
9185
9186 pub fn duplicate_line_up(
9187 &mut self,
9188 _: &DuplicateLineUp,
9189 window: &mut Window,
9190 cx: &mut Context<Self>,
9191 ) {
9192 self.duplicate(true, true, window, cx);
9193 }
9194
9195 pub fn duplicate_line_down(
9196 &mut self,
9197 _: &DuplicateLineDown,
9198 window: &mut Window,
9199 cx: &mut Context<Self>,
9200 ) {
9201 self.duplicate(false, true, window, cx);
9202 }
9203
9204 pub fn duplicate_selection(
9205 &mut self,
9206 _: &DuplicateSelection,
9207 window: &mut Window,
9208 cx: &mut Context<Self>,
9209 ) {
9210 self.duplicate(false, false, window, cx);
9211 }
9212
9213 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9214 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9215
9216 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9217 let buffer = self.buffer.read(cx).snapshot(cx);
9218
9219 let mut edits = Vec::new();
9220 let mut unfold_ranges = Vec::new();
9221 let mut refold_creases = Vec::new();
9222
9223 let selections = self.selections.all::<Point>(cx);
9224 let mut selections = selections.iter().peekable();
9225 let mut contiguous_row_selections = Vec::new();
9226 let mut new_selections = Vec::new();
9227
9228 while let Some(selection) = selections.next() {
9229 // Find all the selections that span a contiguous row range
9230 let (start_row, end_row) = consume_contiguous_rows(
9231 &mut contiguous_row_selections,
9232 selection,
9233 &display_map,
9234 &mut selections,
9235 );
9236
9237 // Move the text spanned by the row range to be before the line preceding the row range
9238 if start_row.0 > 0 {
9239 let range_to_move = Point::new(
9240 start_row.previous_row().0,
9241 buffer.line_len(start_row.previous_row()),
9242 )
9243 ..Point::new(
9244 end_row.previous_row().0,
9245 buffer.line_len(end_row.previous_row()),
9246 );
9247 let insertion_point = display_map
9248 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9249 .0;
9250
9251 // Don't move lines across excerpts
9252 if buffer
9253 .excerpt_containing(insertion_point..range_to_move.end)
9254 .is_some()
9255 {
9256 let text = buffer
9257 .text_for_range(range_to_move.clone())
9258 .flat_map(|s| s.chars())
9259 .skip(1)
9260 .chain(['\n'])
9261 .collect::<String>();
9262
9263 edits.push((
9264 buffer.anchor_after(range_to_move.start)
9265 ..buffer.anchor_before(range_to_move.end),
9266 String::new(),
9267 ));
9268 let insertion_anchor = buffer.anchor_after(insertion_point);
9269 edits.push((insertion_anchor..insertion_anchor, text));
9270
9271 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9272
9273 // Move selections up
9274 new_selections.extend(contiguous_row_selections.drain(..).map(
9275 |mut selection| {
9276 selection.start.row -= row_delta;
9277 selection.end.row -= row_delta;
9278 selection
9279 },
9280 ));
9281
9282 // Move folds up
9283 unfold_ranges.push(range_to_move.clone());
9284 for fold in display_map.folds_in_range(
9285 buffer.anchor_before(range_to_move.start)
9286 ..buffer.anchor_after(range_to_move.end),
9287 ) {
9288 let mut start = fold.range.start.to_point(&buffer);
9289 let mut end = fold.range.end.to_point(&buffer);
9290 start.row -= row_delta;
9291 end.row -= row_delta;
9292 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9293 }
9294 }
9295 }
9296
9297 // If we didn't move line(s), preserve the existing selections
9298 new_selections.append(&mut contiguous_row_selections);
9299 }
9300
9301 self.transact(window, cx, |this, window, cx| {
9302 this.unfold_ranges(&unfold_ranges, true, true, cx);
9303 this.buffer.update(cx, |buffer, cx| {
9304 for (range, text) in edits {
9305 buffer.edit([(range, text)], None, cx);
9306 }
9307 });
9308 this.fold_creases(refold_creases, true, window, cx);
9309 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9310 s.select(new_selections);
9311 })
9312 });
9313 }
9314
9315 pub fn move_line_down(
9316 &mut self,
9317 _: &MoveLineDown,
9318 window: &mut Window,
9319 cx: &mut Context<Self>,
9320 ) {
9321 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9322
9323 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9324 let buffer = self.buffer.read(cx).snapshot(cx);
9325
9326 let mut edits = Vec::new();
9327 let mut unfold_ranges = Vec::new();
9328 let mut refold_creases = Vec::new();
9329
9330 let selections = self.selections.all::<Point>(cx);
9331 let mut selections = selections.iter().peekable();
9332 let mut contiguous_row_selections = Vec::new();
9333 let mut new_selections = Vec::new();
9334
9335 while let Some(selection) = selections.next() {
9336 // Find all the selections that span a contiguous row range
9337 let (start_row, end_row) = consume_contiguous_rows(
9338 &mut contiguous_row_selections,
9339 selection,
9340 &display_map,
9341 &mut selections,
9342 );
9343
9344 // Move the text spanned by the row range to be after the last line of the row range
9345 if end_row.0 <= buffer.max_point().row {
9346 let range_to_move =
9347 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9348 let insertion_point = display_map
9349 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9350 .0;
9351
9352 // Don't move lines across excerpt boundaries
9353 if buffer
9354 .excerpt_containing(range_to_move.start..insertion_point)
9355 .is_some()
9356 {
9357 let mut text = String::from("\n");
9358 text.extend(buffer.text_for_range(range_to_move.clone()));
9359 text.pop(); // Drop trailing newline
9360 edits.push((
9361 buffer.anchor_after(range_to_move.start)
9362 ..buffer.anchor_before(range_to_move.end),
9363 String::new(),
9364 ));
9365 let insertion_anchor = buffer.anchor_after(insertion_point);
9366 edits.push((insertion_anchor..insertion_anchor, text));
9367
9368 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9369
9370 // Move selections down
9371 new_selections.extend(contiguous_row_selections.drain(..).map(
9372 |mut selection| {
9373 selection.start.row += row_delta;
9374 selection.end.row += row_delta;
9375 selection
9376 },
9377 ));
9378
9379 // Move folds down
9380 unfold_ranges.push(range_to_move.clone());
9381 for fold in display_map.folds_in_range(
9382 buffer.anchor_before(range_to_move.start)
9383 ..buffer.anchor_after(range_to_move.end),
9384 ) {
9385 let mut start = fold.range.start.to_point(&buffer);
9386 let mut end = fold.range.end.to_point(&buffer);
9387 start.row += row_delta;
9388 end.row += row_delta;
9389 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9390 }
9391 }
9392 }
9393
9394 // If we didn't move line(s), preserve the existing selections
9395 new_selections.append(&mut contiguous_row_selections);
9396 }
9397
9398 self.transact(window, cx, |this, window, cx| {
9399 this.unfold_ranges(&unfold_ranges, true, true, cx);
9400 this.buffer.update(cx, |buffer, cx| {
9401 for (range, text) in edits {
9402 buffer.edit([(range, text)], None, cx);
9403 }
9404 });
9405 this.fold_creases(refold_creases, true, window, cx);
9406 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9407 s.select(new_selections)
9408 });
9409 });
9410 }
9411
9412 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9413 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9414 let text_layout_details = &self.text_layout_details(window);
9415 self.transact(window, cx, |this, window, cx| {
9416 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9417 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9418 s.move_with(|display_map, selection| {
9419 if !selection.is_empty() {
9420 return;
9421 }
9422
9423 let mut head = selection.head();
9424 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9425 if head.column() == display_map.line_len(head.row()) {
9426 transpose_offset = display_map
9427 .buffer_snapshot
9428 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9429 }
9430
9431 if transpose_offset == 0 {
9432 return;
9433 }
9434
9435 *head.column_mut() += 1;
9436 head = display_map.clip_point(head, Bias::Right);
9437 let goal = SelectionGoal::HorizontalPosition(
9438 display_map
9439 .x_for_display_point(head, text_layout_details)
9440 .into(),
9441 );
9442 selection.collapse_to(head, goal);
9443
9444 let transpose_start = display_map
9445 .buffer_snapshot
9446 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9447 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9448 let transpose_end = display_map
9449 .buffer_snapshot
9450 .clip_offset(transpose_offset + 1, Bias::Right);
9451 if let Some(ch) =
9452 display_map.buffer_snapshot.chars_at(transpose_start).next()
9453 {
9454 edits.push((transpose_start..transpose_offset, String::new()));
9455 edits.push((transpose_end..transpose_end, ch.to_string()));
9456 }
9457 }
9458 });
9459 edits
9460 });
9461 this.buffer
9462 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9463 let selections = this.selections.all::<usize>(cx);
9464 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9465 s.select(selections);
9466 });
9467 });
9468 }
9469
9470 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9471 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9472 self.rewrap_impl(RewrapOptions::default(), cx)
9473 }
9474
9475 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9476 let buffer = self.buffer.read(cx).snapshot(cx);
9477 let selections = self.selections.all::<Point>(cx);
9478 let mut selections = selections.iter().peekable();
9479
9480 let mut edits = Vec::new();
9481 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9482
9483 while let Some(selection) = selections.next() {
9484 let mut start_row = selection.start.row;
9485 let mut end_row = selection.end.row;
9486
9487 // Skip selections that overlap with a range that has already been rewrapped.
9488 let selection_range = start_row..end_row;
9489 if rewrapped_row_ranges
9490 .iter()
9491 .any(|range| range.overlaps(&selection_range))
9492 {
9493 continue;
9494 }
9495
9496 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9497
9498 // Since not all lines in the selection may be at the same indent
9499 // level, choose the indent size that is the most common between all
9500 // of the lines.
9501 //
9502 // If there is a tie, we use the deepest indent.
9503 let (indent_size, indent_end) = {
9504 let mut indent_size_occurrences = HashMap::default();
9505 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9506
9507 for row in start_row..=end_row {
9508 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9509 rows_by_indent_size.entry(indent).or_default().push(row);
9510 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9511 }
9512
9513 let indent_size = indent_size_occurrences
9514 .into_iter()
9515 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9516 .map(|(indent, _)| indent)
9517 .unwrap_or_default();
9518 let row = rows_by_indent_size[&indent_size][0];
9519 let indent_end = Point::new(row, indent_size.len);
9520
9521 (indent_size, indent_end)
9522 };
9523
9524 let mut line_prefix = indent_size.chars().collect::<String>();
9525
9526 let mut inside_comment = false;
9527 if let Some(comment_prefix) =
9528 buffer
9529 .language_scope_at(selection.head())
9530 .and_then(|language| {
9531 language
9532 .line_comment_prefixes()
9533 .iter()
9534 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9535 .cloned()
9536 })
9537 {
9538 line_prefix.push_str(&comment_prefix);
9539 inside_comment = true;
9540 }
9541
9542 let language_settings = buffer.language_settings_at(selection.head(), cx);
9543 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9544 RewrapBehavior::InComments => inside_comment,
9545 RewrapBehavior::InSelections => !selection.is_empty(),
9546 RewrapBehavior::Anywhere => true,
9547 };
9548
9549 let should_rewrap = options.override_language_settings
9550 || allow_rewrap_based_on_language
9551 || self.hard_wrap.is_some();
9552 if !should_rewrap {
9553 continue;
9554 }
9555
9556 if selection.is_empty() {
9557 'expand_upwards: while start_row > 0 {
9558 let prev_row = start_row - 1;
9559 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9560 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9561 {
9562 start_row = prev_row;
9563 } else {
9564 break 'expand_upwards;
9565 }
9566 }
9567
9568 'expand_downwards: while end_row < buffer.max_point().row {
9569 let next_row = end_row + 1;
9570 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9571 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9572 {
9573 end_row = next_row;
9574 } else {
9575 break 'expand_downwards;
9576 }
9577 }
9578 }
9579
9580 let start = Point::new(start_row, 0);
9581 let start_offset = start.to_offset(&buffer);
9582 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9583 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9584 let Some(lines_without_prefixes) = selection_text
9585 .lines()
9586 .map(|line| {
9587 line.strip_prefix(&line_prefix)
9588 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9589 .ok_or_else(|| {
9590 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9591 })
9592 })
9593 .collect::<Result<Vec<_>, _>>()
9594 .log_err()
9595 else {
9596 continue;
9597 };
9598
9599 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9600 buffer
9601 .language_settings_at(Point::new(start_row, 0), cx)
9602 .preferred_line_length as usize
9603 });
9604 let wrapped_text = wrap_with_prefix(
9605 line_prefix,
9606 lines_without_prefixes.join("\n"),
9607 wrap_column,
9608 tab_size,
9609 options.preserve_existing_whitespace,
9610 );
9611
9612 // TODO: should always use char-based diff while still supporting cursor behavior that
9613 // matches vim.
9614 let mut diff_options = DiffOptions::default();
9615 if options.override_language_settings {
9616 diff_options.max_word_diff_len = 0;
9617 diff_options.max_word_diff_line_count = 0;
9618 } else {
9619 diff_options.max_word_diff_len = usize::MAX;
9620 diff_options.max_word_diff_line_count = usize::MAX;
9621 }
9622
9623 for (old_range, new_text) in
9624 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9625 {
9626 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9627 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9628 edits.push((edit_start..edit_end, new_text));
9629 }
9630
9631 rewrapped_row_ranges.push(start_row..=end_row);
9632 }
9633
9634 self.buffer
9635 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9636 }
9637
9638 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9639 let mut text = String::new();
9640 let buffer = self.buffer.read(cx).snapshot(cx);
9641 let mut selections = self.selections.all::<Point>(cx);
9642 let mut clipboard_selections = Vec::with_capacity(selections.len());
9643 {
9644 let max_point = buffer.max_point();
9645 let mut is_first = true;
9646 for selection in &mut selections {
9647 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9648 if is_entire_line {
9649 selection.start = Point::new(selection.start.row, 0);
9650 if !selection.is_empty() && selection.end.column == 0 {
9651 selection.end = cmp::min(max_point, selection.end);
9652 } else {
9653 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9654 }
9655 selection.goal = SelectionGoal::None;
9656 }
9657 if is_first {
9658 is_first = false;
9659 } else {
9660 text += "\n";
9661 }
9662 let mut len = 0;
9663 for chunk in buffer.text_for_range(selection.start..selection.end) {
9664 text.push_str(chunk);
9665 len += chunk.len();
9666 }
9667 clipboard_selections.push(ClipboardSelection {
9668 len,
9669 is_entire_line,
9670 first_line_indent: buffer
9671 .indent_size_for_line(MultiBufferRow(selection.start.row))
9672 .len,
9673 });
9674 }
9675 }
9676
9677 self.transact(window, cx, |this, window, cx| {
9678 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9679 s.select(selections);
9680 });
9681 this.insert("", window, cx);
9682 });
9683 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9684 }
9685
9686 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9687 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9688 let item = self.cut_common(window, cx);
9689 cx.write_to_clipboard(item);
9690 }
9691
9692 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9693 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9694 self.change_selections(None, window, cx, |s| {
9695 s.move_with(|snapshot, sel| {
9696 if sel.is_empty() {
9697 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9698 }
9699 });
9700 });
9701 let item = self.cut_common(window, cx);
9702 cx.set_global(KillRing(item))
9703 }
9704
9705 pub fn kill_ring_yank(
9706 &mut self,
9707 _: &KillRingYank,
9708 window: &mut Window,
9709 cx: &mut Context<Self>,
9710 ) {
9711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9712 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9713 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9714 (kill_ring.text().to_string(), kill_ring.metadata_json())
9715 } else {
9716 return;
9717 }
9718 } else {
9719 return;
9720 };
9721 self.do_paste(&text, metadata, false, window, cx);
9722 }
9723
9724 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9725 self.do_copy(true, cx);
9726 }
9727
9728 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9729 self.do_copy(false, cx);
9730 }
9731
9732 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9733 let selections = self.selections.all::<Point>(cx);
9734 let buffer = self.buffer.read(cx).read(cx);
9735 let mut text = String::new();
9736
9737 let mut clipboard_selections = Vec::with_capacity(selections.len());
9738 {
9739 let max_point = buffer.max_point();
9740 let mut is_first = true;
9741 for selection in &selections {
9742 let mut start = selection.start;
9743 let mut end = selection.end;
9744 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9745 if is_entire_line {
9746 start = Point::new(start.row, 0);
9747 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9748 }
9749
9750 let mut trimmed_selections = Vec::new();
9751 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9752 let row = MultiBufferRow(start.row);
9753 let first_indent = buffer.indent_size_for_line(row);
9754 if first_indent.len == 0 || start.column > first_indent.len {
9755 trimmed_selections.push(start..end);
9756 } else {
9757 trimmed_selections.push(
9758 Point::new(row.0, first_indent.len)
9759 ..Point::new(row.0, buffer.line_len(row)),
9760 );
9761 for row in start.row + 1..=end.row {
9762 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9763 if row_indent_size.len >= first_indent.len {
9764 trimmed_selections.push(
9765 Point::new(row, first_indent.len)
9766 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9767 );
9768 } else {
9769 trimmed_selections.clear();
9770 trimmed_selections.push(start..end);
9771 break;
9772 }
9773 }
9774 }
9775 } else {
9776 trimmed_selections.push(start..end);
9777 }
9778
9779 for trimmed_range in trimmed_selections {
9780 if is_first {
9781 is_first = false;
9782 } else {
9783 text += "\n";
9784 }
9785 let mut len = 0;
9786 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9787 text.push_str(chunk);
9788 len += chunk.len();
9789 }
9790 clipboard_selections.push(ClipboardSelection {
9791 len,
9792 is_entire_line,
9793 first_line_indent: buffer
9794 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9795 .len,
9796 });
9797 }
9798 }
9799 }
9800
9801 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9802 text,
9803 clipboard_selections,
9804 ));
9805 }
9806
9807 pub fn do_paste(
9808 &mut self,
9809 text: &String,
9810 clipboard_selections: Option<Vec<ClipboardSelection>>,
9811 handle_entire_lines: bool,
9812 window: &mut Window,
9813 cx: &mut Context<Self>,
9814 ) {
9815 if self.read_only(cx) {
9816 return;
9817 }
9818
9819 let clipboard_text = Cow::Borrowed(text);
9820
9821 self.transact(window, cx, |this, window, cx| {
9822 if let Some(mut clipboard_selections) = clipboard_selections {
9823 let old_selections = this.selections.all::<usize>(cx);
9824 let all_selections_were_entire_line =
9825 clipboard_selections.iter().all(|s| s.is_entire_line);
9826 let first_selection_indent_column =
9827 clipboard_selections.first().map(|s| s.first_line_indent);
9828 if clipboard_selections.len() != old_selections.len() {
9829 clipboard_selections.drain(..);
9830 }
9831 let cursor_offset = this.selections.last::<usize>(cx).head();
9832 let mut auto_indent_on_paste = true;
9833
9834 this.buffer.update(cx, |buffer, cx| {
9835 let snapshot = buffer.read(cx);
9836 auto_indent_on_paste = snapshot
9837 .language_settings_at(cursor_offset, cx)
9838 .auto_indent_on_paste;
9839
9840 let mut start_offset = 0;
9841 let mut edits = Vec::new();
9842 let mut original_indent_columns = Vec::new();
9843 for (ix, selection) in old_selections.iter().enumerate() {
9844 let to_insert;
9845 let entire_line;
9846 let original_indent_column;
9847 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9848 let end_offset = start_offset + clipboard_selection.len;
9849 to_insert = &clipboard_text[start_offset..end_offset];
9850 entire_line = clipboard_selection.is_entire_line;
9851 start_offset = end_offset + 1;
9852 original_indent_column = Some(clipboard_selection.first_line_indent);
9853 } else {
9854 to_insert = clipboard_text.as_str();
9855 entire_line = all_selections_were_entire_line;
9856 original_indent_column = first_selection_indent_column
9857 }
9858
9859 // If the corresponding selection was empty when this slice of the
9860 // clipboard text was written, then the entire line containing the
9861 // selection was copied. If this selection is also currently empty,
9862 // then paste the line before the current line of the buffer.
9863 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9864 let column = selection.start.to_point(&snapshot).column as usize;
9865 let line_start = selection.start - column;
9866 line_start..line_start
9867 } else {
9868 selection.range()
9869 };
9870
9871 edits.push((range, to_insert));
9872 original_indent_columns.push(original_indent_column);
9873 }
9874 drop(snapshot);
9875
9876 buffer.edit(
9877 edits,
9878 if auto_indent_on_paste {
9879 Some(AutoindentMode::Block {
9880 original_indent_columns,
9881 })
9882 } else {
9883 None
9884 },
9885 cx,
9886 );
9887 });
9888
9889 let selections = this.selections.all::<usize>(cx);
9890 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9891 s.select(selections)
9892 });
9893 } else {
9894 this.insert(&clipboard_text, window, cx);
9895 }
9896 });
9897 }
9898
9899 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9900 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9901 if let Some(item) = cx.read_from_clipboard() {
9902 let entries = item.entries();
9903
9904 match entries.first() {
9905 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9906 // of all the pasted entries.
9907 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9908 .do_paste(
9909 clipboard_string.text(),
9910 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9911 true,
9912 window,
9913 cx,
9914 ),
9915 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9916 }
9917 }
9918 }
9919
9920 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9921 if self.read_only(cx) {
9922 return;
9923 }
9924
9925 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9926
9927 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9928 if let Some((selections, _)) =
9929 self.selection_history.transaction(transaction_id).cloned()
9930 {
9931 self.change_selections(None, window, cx, |s| {
9932 s.select_anchors(selections.to_vec());
9933 });
9934 } else {
9935 log::error!(
9936 "No entry in selection_history found for undo. \
9937 This may correspond to a bug where undo does not update the selection. \
9938 If this is occurring, please add details to \
9939 https://github.com/zed-industries/zed/issues/22692"
9940 );
9941 }
9942 self.request_autoscroll(Autoscroll::fit(), cx);
9943 self.unmark_text(window, cx);
9944 self.refresh_inline_completion(true, false, window, cx);
9945 cx.emit(EditorEvent::Edited { transaction_id });
9946 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9947 }
9948 }
9949
9950 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.read_only(cx) {
9952 return;
9953 }
9954
9955 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9956
9957 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9958 if let Some((_, Some(selections))) =
9959 self.selection_history.transaction(transaction_id).cloned()
9960 {
9961 self.change_selections(None, window, cx, |s| {
9962 s.select_anchors(selections.to_vec());
9963 });
9964 } else {
9965 log::error!(
9966 "No entry in selection_history found for redo. \
9967 This may correspond to a bug where undo does not update the selection. \
9968 If this is occurring, please add details to \
9969 https://github.com/zed-industries/zed/issues/22692"
9970 );
9971 }
9972 self.request_autoscroll(Autoscroll::fit(), cx);
9973 self.unmark_text(window, cx);
9974 self.refresh_inline_completion(true, false, window, cx);
9975 cx.emit(EditorEvent::Edited { transaction_id });
9976 }
9977 }
9978
9979 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9980 self.buffer
9981 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9982 }
9983
9984 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9985 self.buffer
9986 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9987 }
9988
9989 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
9991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9992 s.move_with(|map, selection| {
9993 let cursor = if selection.is_empty() {
9994 movement::left(map, selection.start)
9995 } else {
9996 selection.start
9997 };
9998 selection.collapse_to(cursor, SelectionGoal::None);
9999 });
10000 })
10001 }
10002
10003 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10006 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10007 })
10008 }
10009
10010 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10013 s.move_with(|map, selection| {
10014 let cursor = if selection.is_empty() {
10015 movement::right(map, selection.end)
10016 } else {
10017 selection.end
10018 };
10019 selection.collapse_to(cursor, SelectionGoal::None)
10020 });
10021 })
10022 }
10023
10024 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10025 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10026 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10027 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10028 })
10029 }
10030
10031 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.take_rename(true, window, cx).is_some() {
10033 return;
10034 }
10035
10036 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10037 cx.propagate();
10038 return;
10039 }
10040
10041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10042
10043 let text_layout_details = &self.text_layout_details(window);
10044 let selection_count = self.selections.count();
10045 let first_selection = self.selections.first_anchor();
10046
10047 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10048 s.move_with(|map, selection| {
10049 if !selection.is_empty() {
10050 selection.goal = SelectionGoal::None;
10051 }
10052 let (cursor, goal) = movement::up(
10053 map,
10054 selection.start,
10055 selection.goal,
10056 false,
10057 text_layout_details,
10058 );
10059 selection.collapse_to(cursor, goal);
10060 });
10061 });
10062
10063 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10064 {
10065 cx.propagate();
10066 }
10067 }
10068
10069 pub fn move_up_by_lines(
10070 &mut self,
10071 action: &MoveUpByLines,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 if self.take_rename(true, window, cx).is_some() {
10076 return;
10077 }
10078
10079 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10080 cx.propagate();
10081 return;
10082 }
10083
10084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10085
10086 let text_layout_details = &self.text_layout_details(window);
10087
10088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10089 s.move_with(|map, selection| {
10090 if !selection.is_empty() {
10091 selection.goal = SelectionGoal::None;
10092 }
10093 let (cursor, goal) = movement::up_by_rows(
10094 map,
10095 selection.start,
10096 action.lines,
10097 selection.goal,
10098 false,
10099 text_layout_details,
10100 );
10101 selection.collapse_to(cursor, goal);
10102 });
10103 })
10104 }
10105
10106 pub fn move_down_by_lines(
10107 &mut self,
10108 action: &MoveDownByLines,
10109 window: &mut Window,
10110 cx: &mut Context<Self>,
10111 ) {
10112 if self.take_rename(true, window, cx).is_some() {
10113 return;
10114 }
10115
10116 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10117 cx.propagate();
10118 return;
10119 }
10120
10121 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10122
10123 let text_layout_details = &self.text_layout_details(window);
10124
10125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10126 s.move_with(|map, selection| {
10127 if !selection.is_empty() {
10128 selection.goal = SelectionGoal::None;
10129 }
10130 let (cursor, goal) = movement::down_by_rows(
10131 map,
10132 selection.start,
10133 action.lines,
10134 selection.goal,
10135 false,
10136 text_layout_details,
10137 );
10138 selection.collapse_to(cursor, goal);
10139 });
10140 })
10141 }
10142
10143 pub fn select_down_by_lines(
10144 &mut self,
10145 action: &SelectDownByLines,
10146 window: &mut Window,
10147 cx: &mut Context<Self>,
10148 ) {
10149 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10150 let text_layout_details = &self.text_layout_details(window);
10151 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10152 s.move_heads_with(|map, head, goal| {
10153 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10154 })
10155 })
10156 }
10157
10158 pub fn select_up_by_lines(
10159 &mut self,
10160 action: &SelectUpByLines,
10161 window: &mut Window,
10162 cx: &mut Context<Self>,
10163 ) {
10164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10165 let text_layout_details = &self.text_layout_details(window);
10166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10167 s.move_heads_with(|map, head, goal| {
10168 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10169 })
10170 })
10171 }
10172
10173 pub fn select_page_up(
10174 &mut self,
10175 _: &SelectPageUp,
10176 window: &mut Window,
10177 cx: &mut Context<Self>,
10178 ) {
10179 let Some(row_count) = self.visible_row_count() else {
10180 return;
10181 };
10182
10183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10184
10185 let text_layout_details = &self.text_layout_details(window);
10186
10187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10188 s.move_heads_with(|map, head, goal| {
10189 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10190 })
10191 })
10192 }
10193
10194 pub fn move_page_up(
10195 &mut self,
10196 action: &MovePageUp,
10197 window: &mut Window,
10198 cx: &mut Context<Self>,
10199 ) {
10200 if self.take_rename(true, window, cx).is_some() {
10201 return;
10202 }
10203
10204 if self
10205 .context_menu
10206 .borrow_mut()
10207 .as_mut()
10208 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10209 .unwrap_or(false)
10210 {
10211 return;
10212 }
10213
10214 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10215 cx.propagate();
10216 return;
10217 }
10218
10219 let Some(row_count) = self.visible_row_count() else {
10220 return;
10221 };
10222
10223 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10224
10225 let autoscroll = if action.center_cursor {
10226 Autoscroll::center()
10227 } else {
10228 Autoscroll::fit()
10229 };
10230
10231 let text_layout_details = &self.text_layout_details(window);
10232
10233 self.change_selections(Some(autoscroll), window, cx, |s| {
10234 s.move_with(|map, selection| {
10235 if !selection.is_empty() {
10236 selection.goal = SelectionGoal::None;
10237 }
10238 let (cursor, goal) = movement::up_by_rows(
10239 map,
10240 selection.end,
10241 row_count,
10242 selection.goal,
10243 false,
10244 text_layout_details,
10245 );
10246 selection.collapse_to(cursor, goal);
10247 });
10248 });
10249 }
10250
10251 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10252 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10253 let text_layout_details = &self.text_layout_details(window);
10254 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10255 s.move_heads_with(|map, head, goal| {
10256 movement::up(map, head, goal, false, text_layout_details)
10257 })
10258 })
10259 }
10260
10261 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10262 self.take_rename(true, window, cx);
10263
10264 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10265 cx.propagate();
10266 return;
10267 }
10268
10269 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10270
10271 let text_layout_details = &self.text_layout_details(window);
10272 let selection_count = self.selections.count();
10273 let first_selection = self.selections.first_anchor();
10274
10275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10276 s.move_with(|map, selection| {
10277 if !selection.is_empty() {
10278 selection.goal = SelectionGoal::None;
10279 }
10280 let (cursor, goal) = movement::down(
10281 map,
10282 selection.end,
10283 selection.goal,
10284 false,
10285 text_layout_details,
10286 );
10287 selection.collapse_to(cursor, goal);
10288 });
10289 });
10290
10291 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10292 {
10293 cx.propagate();
10294 }
10295 }
10296
10297 pub fn select_page_down(
10298 &mut self,
10299 _: &SelectPageDown,
10300 window: &mut Window,
10301 cx: &mut Context<Self>,
10302 ) {
10303 let Some(row_count) = self.visible_row_count() else {
10304 return;
10305 };
10306
10307 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10308
10309 let text_layout_details = &self.text_layout_details(window);
10310
10311 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10312 s.move_heads_with(|map, head, goal| {
10313 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10314 })
10315 })
10316 }
10317
10318 pub fn move_page_down(
10319 &mut self,
10320 action: &MovePageDown,
10321 window: &mut Window,
10322 cx: &mut Context<Self>,
10323 ) {
10324 if self.take_rename(true, window, cx).is_some() {
10325 return;
10326 }
10327
10328 if self
10329 .context_menu
10330 .borrow_mut()
10331 .as_mut()
10332 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10333 .unwrap_or(false)
10334 {
10335 return;
10336 }
10337
10338 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10339 cx.propagate();
10340 return;
10341 }
10342
10343 let Some(row_count) = self.visible_row_count() else {
10344 return;
10345 };
10346
10347 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10348
10349 let autoscroll = if action.center_cursor {
10350 Autoscroll::center()
10351 } else {
10352 Autoscroll::fit()
10353 };
10354
10355 let text_layout_details = &self.text_layout_details(window);
10356 self.change_selections(Some(autoscroll), window, cx, |s| {
10357 s.move_with(|map, selection| {
10358 if !selection.is_empty() {
10359 selection.goal = SelectionGoal::None;
10360 }
10361 let (cursor, goal) = movement::down_by_rows(
10362 map,
10363 selection.end,
10364 row_count,
10365 selection.goal,
10366 false,
10367 text_layout_details,
10368 );
10369 selection.collapse_to(cursor, goal);
10370 });
10371 });
10372 }
10373
10374 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10375 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10376 let text_layout_details = &self.text_layout_details(window);
10377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10378 s.move_heads_with(|map, head, goal| {
10379 movement::down(map, head, goal, false, text_layout_details)
10380 })
10381 });
10382 }
10383
10384 pub fn context_menu_first(
10385 &mut self,
10386 _: &ContextMenuFirst,
10387 _window: &mut Window,
10388 cx: &mut Context<Self>,
10389 ) {
10390 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10391 context_menu.select_first(self.completion_provider.as_deref(), cx);
10392 }
10393 }
10394
10395 pub fn context_menu_prev(
10396 &mut self,
10397 _: &ContextMenuPrevious,
10398 _window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10402 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10403 }
10404 }
10405
10406 pub fn context_menu_next(
10407 &mut self,
10408 _: &ContextMenuNext,
10409 _window: &mut Window,
10410 cx: &mut Context<Self>,
10411 ) {
10412 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10413 context_menu.select_next(self.completion_provider.as_deref(), cx);
10414 }
10415 }
10416
10417 pub fn context_menu_last(
10418 &mut self,
10419 _: &ContextMenuLast,
10420 _window: &mut Window,
10421 cx: &mut Context<Self>,
10422 ) {
10423 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10424 context_menu.select_last(self.completion_provider.as_deref(), cx);
10425 }
10426 }
10427
10428 pub fn move_to_previous_word_start(
10429 &mut self,
10430 _: &MoveToPreviousWordStart,
10431 window: &mut Window,
10432 cx: &mut Context<Self>,
10433 ) {
10434 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10435 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10436 s.move_cursors_with(|map, head, _| {
10437 (
10438 movement::previous_word_start(map, head),
10439 SelectionGoal::None,
10440 )
10441 });
10442 })
10443 }
10444
10445 pub fn move_to_previous_subword_start(
10446 &mut self,
10447 _: &MoveToPreviousSubwordStart,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10453 s.move_cursors_with(|map, head, _| {
10454 (
10455 movement::previous_subword_start(map, head),
10456 SelectionGoal::None,
10457 )
10458 });
10459 })
10460 }
10461
10462 pub fn select_to_previous_word_start(
10463 &mut self,
10464 _: &SelectToPreviousWordStart,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10469 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10470 s.move_heads_with(|map, head, _| {
10471 (
10472 movement::previous_word_start(map, head),
10473 SelectionGoal::None,
10474 )
10475 });
10476 })
10477 }
10478
10479 pub fn select_to_previous_subword_start(
10480 &mut self,
10481 _: &SelectToPreviousSubwordStart,
10482 window: &mut Window,
10483 cx: &mut Context<Self>,
10484 ) {
10485 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10486 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10487 s.move_heads_with(|map, head, _| {
10488 (
10489 movement::previous_subword_start(map, head),
10490 SelectionGoal::None,
10491 )
10492 });
10493 })
10494 }
10495
10496 pub fn delete_to_previous_word_start(
10497 &mut self,
10498 action: &DeleteToPreviousWordStart,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10503 self.transact(window, cx, |this, window, cx| {
10504 this.select_autoclose_pair(window, cx);
10505 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10506 s.move_with(|map, selection| {
10507 if selection.is_empty() {
10508 let cursor = if action.ignore_newlines {
10509 movement::previous_word_start(map, selection.head())
10510 } else {
10511 movement::previous_word_start_or_newline(map, selection.head())
10512 };
10513 selection.set_head(cursor, SelectionGoal::None);
10514 }
10515 });
10516 });
10517 this.insert("", window, cx);
10518 });
10519 }
10520
10521 pub fn delete_to_previous_subword_start(
10522 &mut self,
10523 _: &DeleteToPreviousSubwordStart,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10528 self.transact(window, cx, |this, window, cx| {
10529 this.select_autoclose_pair(window, cx);
10530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10531 s.move_with(|map, selection| {
10532 if selection.is_empty() {
10533 let cursor = movement::previous_subword_start(map, selection.head());
10534 selection.set_head(cursor, SelectionGoal::None);
10535 }
10536 });
10537 });
10538 this.insert("", window, cx);
10539 });
10540 }
10541
10542 pub fn move_to_next_word_end(
10543 &mut self,
10544 _: &MoveToNextWordEnd,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10549 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10550 s.move_cursors_with(|map, head, _| {
10551 (movement::next_word_end(map, head), SelectionGoal::None)
10552 });
10553 })
10554 }
10555
10556 pub fn move_to_next_subword_end(
10557 &mut self,
10558 _: &MoveToNextSubwordEnd,
10559 window: &mut Window,
10560 cx: &mut Context<Self>,
10561 ) {
10562 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10563 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10564 s.move_cursors_with(|map, head, _| {
10565 (movement::next_subword_end(map, head), SelectionGoal::None)
10566 });
10567 })
10568 }
10569
10570 pub fn select_to_next_word_end(
10571 &mut self,
10572 _: &SelectToNextWordEnd,
10573 window: &mut Window,
10574 cx: &mut Context<Self>,
10575 ) {
10576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10577 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10578 s.move_heads_with(|map, head, _| {
10579 (movement::next_word_end(map, head), SelectionGoal::None)
10580 });
10581 })
10582 }
10583
10584 pub fn select_to_next_subword_end(
10585 &mut self,
10586 _: &SelectToNextSubwordEnd,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) {
10590 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10591 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10592 s.move_heads_with(|map, head, _| {
10593 (movement::next_subword_end(map, head), SelectionGoal::None)
10594 });
10595 })
10596 }
10597
10598 pub fn delete_to_next_word_end(
10599 &mut self,
10600 action: &DeleteToNextWordEnd,
10601 window: &mut Window,
10602 cx: &mut Context<Self>,
10603 ) {
10604 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10605 self.transact(window, cx, |this, window, cx| {
10606 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10607 s.move_with(|map, selection| {
10608 if selection.is_empty() {
10609 let cursor = if action.ignore_newlines {
10610 movement::next_word_end(map, selection.head())
10611 } else {
10612 movement::next_word_end_or_newline(map, selection.head())
10613 };
10614 selection.set_head(cursor, SelectionGoal::None);
10615 }
10616 });
10617 });
10618 this.insert("", window, cx);
10619 });
10620 }
10621
10622 pub fn delete_to_next_subword_end(
10623 &mut self,
10624 _: &DeleteToNextSubwordEnd,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) {
10628 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10629 self.transact(window, cx, |this, window, cx| {
10630 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10631 s.move_with(|map, selection| {
10632 if selection.is_empty() {
10633 let cursor = movement::next_subword_end(map, selection.head());
10634 selection.set_head(cursor, SelectionGoal::None);
10635 }
10636 });
10637 });
10638 this.insert("", window, cx);
10639 });
10640 }
10641
10642 pub fn move_to_beginning_of_line(
10643 &mut self,
10644 action: &MoveToBeginningOfLine,
10645 window: &mut Window,
10646 cx: &mut Context<Self>,
10647 ) {
10648 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10649 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10650 s.move_cursors_with(|map, head, _| {
10651 (
10652 movement::indented_line_beginning(
10653 map,
10654 head,
10655 action.stop_at_soft_wraps,
10656 action.stop_at_indent,
10657 ),
10658 SelectionGoal::None,
10659 )
10660 });
10661 })
10662 }
10663
10664 pub fn select_to_beginning_of_line(
10665 &mut self,
10666 action: &SelectToBeginningOfLine,
10667 window: &mut Window,
10668 cx: &mut Context<Self>,
10669 ) {
10670 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10671 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10672 s.move_heads_with(|map, head, _| {
10673 (
10674 movement::indented_line_beginning(
10675 map,
10676 head,
10677 action.stop_at_soft_wraps,
10678 action.stop_at_indent,
10679 ),
10680 SelectionGoal::None,
10681 )
10682 });
10683 });
10684 }
10685
10686 pub fn delete_to_beginning_of_line(
10687 &mut self,
10688 action: &DeleteToBeginningOfLine,
10689 window: &mut Window,
10690 cx: &mut Context<Self>,
10691 ) {
10692 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10693 self.transact(window, cx, |this, window, cx| {
10694 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10695 s.move_with(|_, selection| {
10696 selection.reversed = true;
10697 });
10698 });
10699
10700 this.select_to_beginning_of_line(
10701 &SelectToBeginningOfLine {
10702 stop_at_soft_wraps: false,
10703 stop_at_indent: action.stop_at_indent,
10704 },
10705 window,
10706 cx,
10707 );
10708 this.backspace(&Backspace, window, cx);
10709 });
10710 }
10711
10712 pub fn move_to_end_of_line(
10713 &mut self,
10714 action: &MoveToEndOfLine,
10715 window: &mut Window,
10716 cx: &mut Context<Self>,
10717 ) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10719 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10720 s.move_cursors_with(|map, head, _| {
10721 (
10722 movement::line_end(map, head, action.stop_at_soft_wraps),
10723 SelectionGoal::None,
10724 )
10725 });
10726 })
10727 }
10728
10729 pub fn select_to_end_of_line(
10730 &mut self,
10731 action: &SelectToEndOfLine,
10732 window: &mut Window,
10733 cx: &mut Context<Self>,
10734 ) {
10735 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10737 s.move_heads_with(|map, head, _| {
10738 (
10739 movement::line_end(map, head, action.stop_at_soft_wraps),
10740 SelectionGoal::None,
10741 )
10742 });
10743 })
10744 }
10745
10746 pub fn delete_to_end_of_line(
10747 &mut self,
10748 _: &DeleteToEndOfLine,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) {
10752 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10753 self.transact(window, cx, |this, window, cx| {
10754 this.select_to_end_of_line(
10755 &SelectToEndOfLine {
10756 stop_at_soft_wraps: false,
10757 },
10758 window,
10759 cx,
10760 );
10761 this.delete(&Delete, window, cx);
10762 });
10763 }
10764
10765 pub fn cut_to_end_of_line(
10766 &mut self,
10767 _: &CutToEndOfLine,
10768 window: &mut Window,
10769 cx: &mut Context<Self>,
10770 ) {
10771 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10772 self.transact(window, cx, |this, window, cx| {
10773 this.select_to_end_of_line(
10774 &SelectToEndOfLine {
10775 stop_at_soft_wraps: false,
10776 },
10777 window,
10778 cx,
10779 );
10780 this.cut(&Cut, window, cx);
10781 });
10782 }
10783
10784 pub fn move_to_start_of_paragraph(
10785 &mut self,
10786 _: &MoveToStartOfParagraph,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 ) {
10790 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10791 cx.propagate();
10792 return;
10793 }
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10796 s.move_with(|map, selection| {
10797 selection.collapse_to(
10798 movement::start_of_paragraph(map, selection.head(), 1),
10799 SelectionGoal::None,
10800 )
10801 });
10802 })
10803 }
10804
10805 pub fn move_to_end_of_paragraph(
10806 &mut self,
10807 _: &MoveToEndOfParagraph,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) {
10811 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10812 cx.propagate();
10813 return;
10814 }
10815 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10816 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10817 s.move_with(|map, selection| {
10818 selection.collapse_to(
10819 movement::end_of_paragraph(map, selection.head(), 1),
10820 SelectionGoal::None,
10821 )
10822 });
10823 })
10824 }
10825
10826 pub fn select_to_start_of_paragraph(
10827 &mut self,
10828 _: &SelectToStartOfParagraph,
10829 window: &mut Window,
10830 cx: &mut Context<Self>,
10831 ) {
10832 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10833 cx.propagate();
10834 return;
10835 }
10836 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10837 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10838 s.move_heads_with(|map, head, _| {
10839 (
10840 movement::start_of_paragraph(map, head, 1),
10841 SelectionGoal::None,
10842 )
10843 });
10844 })
10845 }
10846
10847 pub fn select_to_end_of_paragraph(
10848 &mut self,
10849 _: &SelectToEndOfParagraph,
10850 window: &mut Window,
10851 cx: &mut Context<Self>,
10852 ) {
10853 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10854 cx.propagate();
10855 return;
10856 }
10857 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10858 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10859 s.move_heads_with(|map, head, _| {
10860 (
10861 movement::end_of_paragraph(map, head, 1),
10862 SelectionGoal::None,
10863 )
10864 });
10865 })
10866 }
10867
10868 pub fn move_to_start_of_excerpt(
10869 &mut self,
10870 _: &MoveToStartOfExcerpt,
10871 window: &mut Window,
10872 cx: &mut Context<Self>,
10873 ) {
10874 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10875 cx.propagate();
10876 return;
10877 }
10878 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10879 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10880 s.move_with(|map, selection| {
10881 selection.collapse_to(
10882 movement::start_of_excerpt(
10883 map,
10884 selection.head(),
10885 workspace::searchable::Direction::Prev,
10886 ),
10887 SelectionGoal::None,
10888 )
10889 });
10890 })
10891 }
10892
10893 pub fn move_to_start_of_next_excerpt(
10894 &mut self,
10895 _: &MoveToStartOfNextExcerpt,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10900 cx.propagate();
10901 return;
10902 }
10903
10904 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10905 s.move_with(|map, selection| {
10906 selection.collapse_to(
10907 movement::start_of_excerpt(
10908 map,
10909 selection.head(),
10910 workspace::searchable::Direction::Next,
10911 ),
10912 SelectionGoal::None,
10913 )
10914 });
10915 })
10916 }
10917
10918 pub fn move_to_end_of_excerpt(
10919 &mut self,
10920 _: &MoveToEndOfExcerpt,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10925 cx.propagate();
10926 return;
10927 }
10928 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10930 s.move_with(|map, selection| {
10931 selection.collapse_to(
10932 movement::end_of_excerpt(
10933 map,
10934 selection.head(),
10935 workspace::searchable::Direction::Next,
10936 ),
10937 SelectionGoal::None,
10938 )
10939 });
10940 })
10941 }
10942
10943 pub fn move_to_end_of_previous_excerpt(
10944 &mut self,
10945 _: &MoveToEndOfPreviousExcerpt,
10946 window: &mut Window,
10947 cx: &mut Context<Self>,
10948 ) {
10949 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10950 cx.propagate();
10951 return;
10952 }
10953 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10954 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10955 s.move_with(|map, selection| {
10956 selection.collapse_to(
10957 movement::end_of_excerpt(
10958 map,
10959 selection.head(),
10960 workspace::searchable::Direction::Prev,
10961 ),
10962 SelectionGoal::None,
10963 )
10964 });
10965 })
10966 }
10967
10968 pub fn select_to_start_of_excerpt(
10969 &mut self,
10970 _: &SelectToStartOfExcerpt,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10975 cx.propagate();
10976 return;
10977 }
10978 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10980 s.move_heads_with(|map, head, _| {
10981 (
10982 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10983 SelectionGoal::None,
10984 )
10985 });
10986 })
10987 }
10988
10989 pub fn select_to_start_of_next_excerpt(
10990 &mut self,
10991 _: &SelectToStartOfNextExcerpt,
10992 window: &mut Window,
10993 cx: &mut Context<Self>,
10994 ) {
10995 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10996 cx.propagate();
10997 return;
10998 }
10999 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11001 s.move_heads_with(|map, head, _| {
11002 (
11003 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11004 SelectionGoal::None,
11005 )
11006 });
11007 })
11008 }
11009
11010 pub fn select_to_end_of_excerpt(
11011 &mut self,
11012 _: &SelectToEndOfExcerpt,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 ) {
11016 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11017 cx.propagate();
11018 return;
11019 }
11020 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11022 s.move_heads_with(|map, head, _| {
11023 (
11024 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11025 SelectionGoal::None,
11026 )
11027 });
11028 })
11029 }
11030
11031 pub fn select_to_end_of_previous_excerpt(
11032 &mut self,
11033 _: &SelectToEndOfPreviousExcerpt,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) {
11037 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11038 cx.propagate();
11039 return;
11040 }
11041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11043 s.move_heads_with(|map, head, _| {
11044 (
11045 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11046 SelectionGoal::None,
11047 )
11048 });
11049 })
11050 }
11051
11052 pub fn move_to_beginning(
11053 &mut self,
11054 _: &MoveToBeginning,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 ) {
11058 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11059 cx.propagate();
11060 return;
11061 }
11062 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11063 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11064 s.select_ranges(vec![0..0]);
11065 });
11066 }
11067
11068 pub fn select_to_beginning(
11069 &mut self,
11070 _: &SelectToBeginning,
11071 window: &mut Window,
11072 cx: &mut Context<Self>,
11073 ) {
11074 let mut selection = self.selections.last::<Point>(cx);
11075 selection.set_head(Point::zero(), SelectionGoal::None);
11076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11078 s.select(vec![selection]);
11079 });
11080 }
11081
11082 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11083 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11084 cx.propagate();
11085 return;
11086 }
11087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11088 let cursor = self.buffer.read(cx).read(cx).len();
11089 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11090 s.select_ranges(vec![cursor..cursor])
11091 });
11092 }
11093
11094 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11095 self.nav_history = nav_history;
11096 }
11097
11098 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11099 self.nav_history.as_ref()
11100 }
11101
11102 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11103 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11104 }
11105
11106 fn push_to_nav_history(
11107 &mut self,
11108 cursor_anchor: Anchor,
11109 new_position: Option<Point>,
11110 is_deactivate: bool,
11111 cx: &mut Context<Self>,
11112 ) {
11113 if let Some(nav_history) = self.nav_history.as_mut() {
11114 let buffer = self.buffer.read(cx).read(cx);
11115 let cursor_position = cursor_anchor.to_point(&buffer);
11116 let scroll_state = self.scroll_manager.anchor();
11117 let scroll_top_row = scroll_state.top_row(&buffer);
11118 drop(buffer);
11119
11120 if let Some(new_position) = new_position {
11121 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11122 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11123 return;
11124 }
11125 }
11126
11127 nav_history.push(
11128 Some(NavigationData {
11129 cursor_anchor,
11130 cursor_position,
11131 scroll_anchor: scroll_state,
11132 scroll_top_row,
11133 }),
11134 cx,
11135 );
11136 cx.emit(EditorEvent::PushedToNavHistory {
11137 anchor: cursor_anchor,
11138 is_deactivate,
11139 })
11140 }
11141 }
11142
11143 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11145 let buffer = self.buffer.read(cx).snapshot(cx);
11146 let mut selection = self.selections.first::<usize>(cx);
11147 selection.set_head(buffer.len(), SelectionGoal::None);
11148 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11149 s.select(vec![selection]);
11150 });
11151 }
11152
11153 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11154 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11155 let end = self.buffer.read(cx).read(cx).len();
11156 self.change_selections(None, window, cx, |s| {
11157 s.select_ranges(vec![0..end]);
11158 });
11159 }
11160
11161 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11162 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11164 let mut selections = self.selections.all::<Point>(cx);
11165 let max_point = display_map.buffer_snapshot.max_point();
11166 for selection in &mut selections {
11167 let rows = selection.spanned_rows(true, &display_map);
11168 selection.start = Point::new(rows.start.0, 0);
11169 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11170 selection.reversed = false;
11171 }
11172 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11173 s.select(selections);
11174 });
11175 }
11176
11177 pub fn split_selection_into_lines(
11178 &mut self,
11179 _: &SplitSelectionIntoLines,
11180 window: &mut Window,
11181 cx: &mut Context<Self>,
11182 ) {
11183 let selections = self
11184 .selections
11185 .all::<Point>(cx)
11186 .into_iter()
11187 .map(|selection| selection.start..selection.end)
11188 .collect::<Vec<_>>();
11189 self.unfold_ranges(&selections, true, true, cx);
11190
11191 let mut new_selection_ranges = Vec::new();
11192 {
11193 let buffer = self.buffer.read(cx).read(cx);
11194 for selection in selections {
11195 for row in selection.start.row..selection.end.row {
11196 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11197 new_selection_ranges.push(cursor..cursor);
11198 }
11199
11200 let is_multiline_selection = selection.start.row != selection.end.row;
11201 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11202 // so this action feels more ergonomic when paired with other selection operations
11203 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11204 if !should_skip_last {
11205 new_selection_ranges.push(selection.end..selection.end);
11206 }
11207 }
11208 }
11209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11210 s.select_ranges(new_selection_ranges);
11211 });
11212 }
11213
11214 pub fn add_selection_above(
11215 &mut self,
11216 _: &AddSelectionAbove,
11217 window: &mut Window,
11218 cx: &mut Context<Self>,
11219 ) {
11220 self.add_selection(true, window, cx);
11221 }
11222
11223 pub fn add_selection_below(
11224 &mut self,
11225 _: &AddSelectionBelow,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) {
11229 self.add_selection(false, window, cx);
11230 }
11231
11232 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11234
11235 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11236 let mut selections = self.selections.all::<Point>(cx);
11237 let text_layout_details = self.text_layout_details(window);
11238 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11239 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11240 let range = oldest_selection.display_range(&display_map).sorted();
11241
11242 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11243 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11244 let positions = start_x.min(end_x)..start_x.max(end_x);
11245
11246 selections.clear();
11247 let mut stack = Vec::new();
11248 for row in range.start.row().0..=range.end.row().0 {
11249 if let Some(selection) = self.selections.build_columnar_selection(
11250 &display_map,
11251 DisplayRow(row),
11252 &positions,
11253 oldest_selection.reversed,
11254 &text_layout_details,
11255 ) {
11256 stack.push(selection.id);
11257 selections.push(selection);
11258 }
11259 }
11260
11261 if above {
11262 stack.reverse();
11263 }
11264
11265 AddSelectionsState { above, stack }
11266 });
11267
11268 let last_added_selection = *state.stack.last().unwrap();
11269 let mut new_selections = Vec::new();
11270 if above == state.above {
11271 let end_row = if above {
11272 DisplayRow(0)
11273 } else {
11274 display_map.max_point().row()
11275 };
11276
11277 'outer: for selection in selections {
11278 if selection.id == last_added_selection {
11279 let range = selection.display_range(&display_map).sorted();
11280 debug_assert_eq!(range.start.row(), range.end.row());
11281 let mut row = range.start.row();
11282 let positions =
11283 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11284 px(start)..px(end)
11285 } else {
11286 let start_x =
11287 display_map.x_for_display_point(range.start, &text_layout_details);
11288 let end_x =
11289 display_map.x_for_display_point(range.end, &text_layout_details);
11290 start_x.min(end_x)..start_x.max(end_x)
11291 };
11292
11293 while row != end_row {
11294 if above {
11295 row.0 -= 1;
11296 } else {
11297 row.0 += 1;
11298 }
11299
11300 if let Some(new_selection) = self.selections.build_columnar_selection(
11301 &display_map,
11302 row,
11303 &positions,
11304 selection.reversed,
11305 &text_layout_details,
11306 ) {
11307 state.stack.push(new_selection.id);
11308 if above {
11309 new_selections.push(new_selection);
11310 new_selections.push(selection);
11311 } else {
11312 new_selections.push(selection);
11313 new_selections.push(new_selection);
11314 }
11315
11316 continue 'outer;
11317 }
11318 }
11319 }
11320
11321 new_selections.push(selection);
11322 }
11323 } else {
11324 new_selections = selections;
11325 new_selections.retain(|s| s.id != last_added_selection);
11326 state.stack.pop();
11327 }
11328
11329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11330 s.select(new_selections);
11331 });
11332 if state.stack.len() > 1 {
11333 self.add_selections_state = Some(state);
11334 }
11335 }
11336
11337 pub fn select_next_match_internal(
11338 &mut self,
11339 display_map: &DisplaySnapshot,
11340 replace_newest: bool,
11341 autoscroll: Option<Autoscroll>,
11342 window: &mut Window,
11343 cx: &mut Context<Self>,
11344 ) -> Result<()> {
11345 fn select_next_match_ranges(
11346 this: &mut Editor,
11347 range: Range<usize>,
11348 replace_newest: bool,
11349 auto_scroll: Option<Autoscroll>,
11350 window: &mut Window,
11351 cx: &mut Context<Editor>,
11352 ) {
11353 this.unfold_ranges(&[range.clone()], false, true, cx);
11354 this.change_selections(auto_scroll, window, cx, |s| {
11355 if replace_newest {
11356 s.delete(s.newest_anchor().id);
11357 }
11358 s.insert_range(range.clone());
11359 });
11360 }
11361
11362 let buffer = &display_map.buffer_snapshot;
11363 let mut selections = self.selections.all::<usize>(cx);
11364 if let Some(mut select_next_state) = self.select_next_state.take() {
11365 let query = &select_next_state.query;
11366 if !select_next_state.done {
11367 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11368 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11369 let mut next_selected_range = None;
11370
11371 let bytes_after_last_selection =
11372 buffer.bytes_in_range(last_selection.end..buffer.len());
11373 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11374 let query_matches = query
11375 .stream_find_iter(bytes_after_last_selection)
11376 .map(|result| (last_selection.end, result))
11377 .chain(
11378 query
11379 .stream_find_iter(bytes_before_first_selection)
11380 .map(|result| (0, result)),
11381 );
11382
11383 for (start_offset, query_match) in query_matches {
11384 let query_match = query_match.unwrap(); // can only fail due to I/O
11385 let offset_range =
11386 start_offset + query_match.start()..start_offset + query_match.end();
11387 let display_range = offset_range.start.to_display_point(display_map)
11388 ..offset_range.end.to_display_point(display_map);
11389
11390 if !select_next_state.wordwise
11391 || (!movement::is_inside_word(display_map, display_range.start)
11392 && !movement::is_inside_word(display_map, display_range.end))
11393 {
11394 // TODO: This is n^2, because we might check all the selections
11395 if !selections
11396 .iter()
11397 .any(|selection| selection.range().overlaps(&offset_range))
11398 {
11399 next_selected_range = Some(offset_range);
11400 break;
11401 }
11402 }
11403 }
11404
11405 if let Some(next_selected_range) = next_selected_range {
11406 select_next_match_ranges(
11407 self,
11408 next_selected_range,
11409 replace_newest,
11410 autoscroll,
11411 window,
11412 cx,
11413 );
11414 } else {
11415 select_next_state.done = true;
11416 }
11417 }
11418
11419 self.select_next_state = Some(select_next_state);
11420 } else {
11421 let mut only_carets = true;
11422 let mut same_text_selected = true;
11423 let mut selected_text = None;
11424
11425 let mut selections_iter = selections.iter().peekable();
11426 while let Some(selection) = selections_iter.next() {
11427 if selection.start != selection.end {
11428 only_carets = false;
11429 }
11430
11431 if same_text_selected {
11432 if selected_text.is_none() {
11433 selected_text =
11434 Some(buffer.text_for_range(selection.range()).collect::<String>());
11435 }
11436
11437 if let Some(next_selection) = selections_iter.peek() {
11438 if next_selection.range().len() == selection.range().len() {
11439 let next_selected_text = buffer
11440 .text_for_range(next_selection.range())
11441 .collect::<String>();
11442 if Some(next_selected_text) != selected_text {
11443 same_text_selected = false;
11444 selected_text = None;
11445 }
11446 } else {
11447 same_text_selected = false;
11448 selected_text = None;
11449 }
11450 }
11451 }
11452 }
11453
11454 if only_carets {
11455 for selection in &mut selections {
11456 let word_range = movement::surrounding_word(
11457 display_map,
11458 selection.start.to_display_point(display_map),
11459 );
11460 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11461 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11462 selection.goal = SelectionGoal::None;
11463 selection.reversed = false;
11464 select_next_match_ranges(
11465 self,
11466 selection.start..selection.end,
11467 replace_newest,
11468 autoscroll,
11469 window,
11470 cx,
11471 );
11472 }
11473
11474 if selections.len() == 1 {
11475 let selection = selections
11476 .last()
11477 .expect("ensured that there's only one selection");
11478 let query = buffer
11479 .text_for_range(selection.start..selection.end)
11480 .collect::<String>();
11481 let is_empty = query.is_empty();
11482 let select_state = SelectNextState {
11483 query: AhoCorasick::new(&[query])?,
11484 wordwise: true,
11485 done: is_empty,
11486 };
11487 self.select_next_state = Some(select_state);
11488 } else {
11489 self.select_next_state = None;
11490 }
11491 } else if let Some(selected_text) = selected_text {
11492 self.select_next_state = Some(SelectNextState {
11493 query: AhoCorasick::new(&[selected_text])?,
11494 wordwise: false,
11495 done: false,
11496 });
11497 self.select_next_match_internal(
11498 display_map,
11499 replace_newest,
11500 autoscroll,
11501 window,
11502 cx,
11503 )?;
11504 }
11505 }
11506 Ok(())
11507 }
11508
11509 pub fn select_all_matches(
11510 &mut self,
11511 _action: &SelectAllMatches,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) -> Result<()> {
11515 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11516
11517 self.push_to_selection_history();
11518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11519
11520 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11521 let Some(select_next_state) = self.select_next_state.as_mut() else {
11522 return Ok(());
11523 };
11524 if select_next_state.done {
11525 return Ok(());
11526 }
11527
11528 let mut new_selections = self.selections.all::<usize>(cx);
11529
11530 let buffer = &display_map.buffer_snapshot;
11531 let query_matches = select_next_state
11532 .query
11533 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11534
11535 for query_match in query_matches {
11536 let query_match = query_match.unwrap(); // can only fail due to I/O
11537 let offset_range = query_match.start()..query_match.end();
11538 let display_range = offset_range.start.to_display_point(&display_map)
11539 ..offset_range.end.to_display_point(&display_map);
11540
11541 if !select_next_state.wordwise
11542 || (!movement::is_inside_word(&display_map, display_range.start)
11543 && !movement::is_inside_word(&display_map, display_range.end))
11544 {
11545 self.selections.change_with(cx, |selections| {
11546 new_selections.push(Selection {
11547 id: selections.new_selection_id(),
11548 start: offset_range.start,
11549 end: offset_range.end,
11550 reversed: false,
11551 goal: SelectionGoal::None,
11552 });
11553 });
11554 }
11555 }
11556
11557 new_selections.sort_by_key(|selection| selection.start);
11558 let mut ix = 0;
11559 while ix + 1 < new_selections.len() {
11560 let current_selection = &new_selections[ix];
11561 let next_selection = &new_selections[ix + 1];
11562 if current_selection.range().overlaps(&next_selection.range()) {
11563 if current_selection.id < next_selection.id {
11564 new_selections.remove(ix + 1);
11565 } else {
11566 new_selections.remove(ix);
11567 }
11568 } else {
11569 ix += 1;
11570 }
11571 }
11572
11573 let reversed = self.selections.oldest::<usize>(cx).reversed;
11574
11575 for selection in new_selections.iter_mut() {
11576 selection.reversed = reversed;
11577 }
11578
11579 select_next_state.done = true;
11580 self.unfold_ranges(
11581 &new_selections
11582 .iter()
11583 .map(|selection| selection.range())
11584 .collect::<Vec<_>>(),
11585 false,
11586 false,
11587 cx,
11588 );
11589 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11590 selections.select(new_selections)
11591 });
11592
11593 Ok(())
11594 }
11595
11596 pub fn select_next(
11597 &mut self,
11598 action: &SelectNext,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) -> Result<()> {
11602 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11603 self.push_to_selection_history();
11604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11605 self.select_next_match_internal(
11606 &display_map,
11607 action.replace_newest,
11608 Some(Autoscroll::newest()),
11609 window,
11610 cx,
11611 )?;
11612 Ok(())
11613 }
11614
11615 pub fn select_previous(
11616 &mut self,
11617 action: &SelectPrevious,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) -> Result<()> {
11621 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11622 self.push_to_selection_history();
11623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11624 let buffer = &display_map.buffer_snapshot;
11625 let mut selections = self.selections.all::<usize>(cx);
11626 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11627 let query = &select_prev_state.query;
11628 if !select_prev_state.done {
11629 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11630 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11631 let mut next_selected_range = None;
11632 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11633 let bytes_before_last_selection =
11634 buffer.reversed_bytes_in_range(0..last_selection.start);
11635 let bytes_after_first_selection =
11636 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11637 let query_matches = query
11638 .stream_find_iter(bytes_before_last_selection)
11639 .map(|result| (last_selection.start, result))
11640 .chain(
11641 query
11642 .stream_find_iter(bytes_after_first_selection)
11643 .map(|result| (buffer.len(), result)),
11644 );
11645 for (end_offset, query_match) in query_matches {
11646 let query_match = query_match.unwrap(); // can only fail due to I/O
11647 let offset_range =
11648 end_offset - query_match.end()..end_offset - query_match.start();
11649 let display_range = offset_range.start.to_display_point(&display_map)
11650 ..offset_range.end.to_display_point(&display_map);
11651
11652 if !select_prev_state.wordwise
11653 || (!movement::is_inside_word(&display_map, display_range.start)
11654 && !movement::is_inside_word(&display_map, display_range.end))
11655 {
11656 next_selected_range = Some(offset_range);
11657 break;
11658 }
11659 }
11660
11661 if let Some(next_selected_range) = next_selected_range {
11662 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11663 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11664 if action.replace_newest {
11665 s.delete(s.newest_anchor().id);
11666 }
11667 s.insert_range(next_selected_range);
11668 });
11669 } else {
11670 select_prev_state.done = true;
11671 }
11672 }
11673
11674 self.select_prev_state = Some(select_prev_state);
11675 } else {
11676 let mut only_carets = true;
11677 let mut same_text_selected = true;
11678 let mut selected_text = None;
11679
11680 let mut selections_iter = selections.iter().peekable();
11681 while let Some(selection) = selections_iter.next() {
11682 if selection.start != selection.end {
11683 only_carets = false;
11684 }
11685
11686 if same_text_selected {
11687 if selected_text.is_none() {
11688 selected_text =
11689 Some(buffer.text_for_range(selection.range()).collect::<String>());
11690 }
11691
11692 if let Some(next_selection) = selections_iter.peek() {
11693 if next_selection.range().len() == selection.range().len() {
11694 let next_selected_text = buffer
11695 .text_for_range(next_selection.range())
11696 .collect::<String>();
11697 if Some(next_selected_text) != selected_text {
11698 same_text_selected = false;
11699 selected_text = None;
11700 }
11701 } else {
11702 same_text_selected = false;
11703 selected_text = None;
11704 }
11705 }
11706 }
11707 }
11708
11709 if only_carets {
11710 for selection in &mut selections {
11711 let word_range = movement::surrounding_word(
11712 &display_map,
11713 selection.start.to_display_point(&display_map),
11714 );
11715 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11716 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11717 selection.goal = SelectionGoal::None;
11718 selection.reversed = false;
11719 }
11720 if selections.len() == 1 {
11721 let selection = selections
11722 .last()
11723 .expect("ensured that there's only one selection");
11724 let query = buffer
11725 .text_for_range(selection.start..selection.end)
11726 .collect::<String>();
11727 let is_empty = query.is_empty();
11728 let select_state = SelectNextState {
11729 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11730 wordwise: true,
11731 done: is_empty,
11732 };
11733 self.select_prev_state = Some(select_state);
11734 } else {
11735 self.select_prev_state = None;
11736 }
11737
11738 self.unfold_ranges(
11739 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11740 false,
11741 true,
11742 cx,
11743 );
11744 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11745 s.select(selections);
11746 });
11747 } else if let Some(selected_text) = selected_text {
11748 self.select_prev_state = Some(SelectNextState {
11749 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11750 wordwise: false,
11751 done: false,
11752 });
11753 self.select_previous(action, window, cx)?;
11754 }
11755 }
11756 Ok(())
11757 }
11758
11759 pub fn toggle_comments(
11760 &mut self,
11761 action: &ToggleComments,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 if self.read_only(cx) {
11766 return;
11767 }
11768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11769 let text_layout_details = &self.text_layout_details(window);
11770 self.transact(window, cx, |this, window, cx| {
11771 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11772 let mut edits = Vec::new();
11773 let mut selection_edit_ranges = Vec::new();
11774 let mut last_toggled_row = None;
11775 let snapshot = this.buffer.read(cx).read(cx);
11776 let empty_str: Arc<str> = Arc::default();
11777 let mut suffixes_inserted = Vec::new();
11778 let ignore_indent = action.ignore_indent;
11779
11780 fn comment_prefix_range(
11781 snapshot: &MultiBufferSnapshot,
11782 row: MultiBufferRow,
11783 comment_prefix: &str,
11784 comment_prefix_whitespace: &str,
11785 ignore_indent: bool,
11786 ) -> Range<Point> {
11787 let indent_size = if ignore_indent {
11788 0
11789 } else {
11790 snapshot.indent_size_for_line(row).len
11791 };
11792
11793 let start = Point::new(row.0, indent_size);
11794
11795 let mut line_bytes = snapshot
11796 .bytes_in_range(start..snapshot.max_point())
11797 .flatten()
11798 .copied();
11799
11800 // If this line currently begins with the line comment prefix, then record
11801 // the range containing the prefix.
11802 if line_bytes
11803 .by_ref()
11804 .take(comment_prefix.len())
11805 .eq(comment_prefix.bytes())
11806 {
11807 // Include any whitespace that matches the comment prefix.
11808 let matching_whitespace_len = line_bytes
11809 .zip(comment_prefix_whitespace.bytes())
11810 .take_while(|(a, b)| a == b)
11811 .count() as u32;
11812 let end = Point::new(
11813 start.row,
11814 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11815 );
11816 start..end
11817 } else {
11818 start..start
11819 }
11820 }
11821
11822 fn comment_suffix_range(
11823 snapshot: &MultiBufferSnapshot,
11824 row: MultiBufferRow,
11825 comment_suffix: &str,
11826 comment_suffix_has_leading_space: bool,
11827 ) -> Range<Point> {
11828 let end = Point::new(row.0, snapshot.line_len(row));
11829 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11830
11831 let mut line_end_bytes = snapshot
11832 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11833 .flatten()
11834 .copied();
11835
11836 let leading_space_len = if suffix_start_column > 0
11837 && line_end_bytes.next() == Some(b' ')
11838 && comment_suffix_has_leading_space
11839 {
11840 1
11841 } else {
11842 0
11843 };
11844
11845 // If this line currently begins with the line comment prefix, then record
11846 // the range containing the prefix.
11847 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11848 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11849 start..end
11850 } else {
11851 end..end
11852 }
11853 }
11854
11855 // TODO: Handle selections that cross excerpts
11856 for selection in &mut selections {
11857 let start_column = snapshot
11858 .indent_size_for_line(MultiBufferRow(selection.start.row))
11859 .len;
11860 let language = if let Some(language) =
11861 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11862 {
11863 language
11864 } else {
11865 continue;
11866 };
11867
11868 selection_edit_ranges.clear();
11869
11870 // If multiple selections contain a given row, avoid processing that
11871 // row more than once.
11872 let mut start_row = MultiBufferRow(selection.start.row);
11873 if last_toggled_row == Some(start_row) {
11874 start_row = start_row.next_row();
11875 }
11876 let end_row =
11877 if selection.end.row > selection.start.row && selection.end.column == 0 {
11878 MultiBufferRow(selection.end.row - 1)
11879 } else {
11880 MultiBufferRow(selection.end.row)
11881 };
11882 last_toggled_row = Some(end_row);
11883
11884 if start_row > end_row {
11885 continue;
11886 }
11887
11888 // If the language has line comments, toggle those.
11889 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11890
11891 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11892 if ignore_indent {
11893 full_comment_prefixes = full_comment_prefixes
11894 .into_iter()
11895 .map(|s| Arc::from(s.trim_end()))
11896 .collect();
11897 }
11898
11899 if !full_comment_prefixes.is_empty() {
11900 let first_prefix = full_comment_prefixes
11901 .first()
11902 .expect("prefixes is non-empty");
11903 let prefix_trimmed_lengths = full_comment_prefixes
11904 .iter()
11905 .map(|p| p.trim_end_matches(' ').len())
11906 .collect::<SmallVec<[usize; 4]>>();
11907
11908 let mut all_selection_lines_are_comments = true;
11909
11910 for row in start_row.0..=end_row.0 {
11911 let row = MultiBufferRow(row);
11912 if start_row < end_row && snapshot.is_line_blank(row) {
11913 continue;
11914 }
11915
11916 let prefix_range = full_comment_prefixes
11917 .iter()
11918 .zip(prefix_trimmed_lengths.iter().copied())
11919 .map(|(prefix, trimmed_prefix_len)| {
11920 comment_prefix_range(
11921 snapshot.deref(),
11922 row,
11923 &prefix[..trimmed_prefix_len],
11924 &prefix[trimmed_prefix_len..],
11925 ignore_indent,
11926 )
11927 })
11928 .max_by_key(|range| range.end.column - range.start.column)
11929 .expect("prefixes is non-empty");
11930
11931 if prefix_range.is_empty() {
11932 all_selection_lines_are_comments = false;
11933 }
11934
11935 selection_edit_ranges.push(prefix_range);
11936 }
11937
11938 if all_selection_lines_are_comments {
11939 edits.extend(
11940 selection_edit_ranges
11941 .iter()
11942 .cloned()
11943 .map(|range| (range, empty_str.clone())),
11944 );
11945 } else {
11946 let min_column = selection_edit_ranges
11947 .iter()
11948 .map(|range| range.start.column)
11949 .min()
11950 .unwrap_or(0);
11951 edits.extend(selection_edit_ranges.iter().map(|range| {
11952 let position = Point::new(range.start.row, min_column);
11953 (position..position, first_prefix.clone())
11954 }));
11955 }
11956 } else if let Some((full_comment_prefix, comment_suffix)) =
11957 language.block_comment_delimiters()
11958 {
11959 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11960 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11961 let prefix_range = comment_prefix_range(
11962 snapshot.deref(),
11963 start_row,
11964 comment_prefix,
11965 comment_prefix_whitespace,
11966 ignore_indent,
11967 );
11968 let suffix_range = comment_suffix_range(
11969 snapshot.deref(),
11970 end_row,
11971 comment_suffix.trim_start_matches(' '),
11972 comment_suffix.starts_with(' '),
11973 );
11974
11975 if prefix_range.is_empty() || suffix_range.is_empty() {
11976 edits.push((
11977 prefix_range.start..prefix_range.start,
11978 full_comment_prefix.clone(),
11979 ));
11980 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11981 suffixes_inserted.push((end_row, comment_suffix.len()));
11982 } else {
11983 edits.push((prefix_range, empty_str.clone()));
11984 edits.push((suffix_range, empty_str.clone()));
11985 }
11986 } else {
11987 continue;
11988 }
11989 }
11990
11991 drop(snapshot);
11992 this.buffer.update(cx, |buffer, cx| {
11993 buffer.edit(edits, None, cx);
11994 });
11995
11996 // Adjust selections so that they end before any comment suffixes that
11997 // were inserted.
11998 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
11999 let mut selections = this.selections.all::<Point>(cx);
12000 let snapshot = this.buffer.read(cx).read(cx);
12001 for selection in &mut selections {
12002 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12003 match row.cmp(&MultiBufferRow(selection.end.row)) {
12004 Ordering::Less => {
12005 suffixes_inserted.next();
12006 continue;
12007 }
12008 Ordering::Greater => break,
12009 Ordering::Equal => {
12010 if selection.end.column == snapshot.line_len(row) {
12011 if selection.is_empty() {
12012 selection.start.column -= suffix_len as u32;
12013 }
12014 selection.end.column -= suffix_len as u32;
12015 }
12016 break;
12017 }
12018 }
12019 }
12020 }
12021
12022 drop(snapshot);
12023 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12024 s.select(selections)
12025 });
12026
12027 let selections = this.selections.all::<Point>(cx);
12028 let selections_on_single_row = selections.windows(2).all(|selections| {
12029 selections[0].start.row == selections[1].start.row
12030 && selections[0].end.row == selections[1].end.row
12031 && selections[0].start.row == selections[0].end.row
12032 });
12033 let selections_selecting = selections
12034 .iter()
12035 .any(|selection| selection.start != selection.end);
12036 let advance_downwards = action.advance_downwards
12037 && selections_on_single_row
12038 && !selections_selecting
12039 && !matches!(this.mode, EditorMode::SingleLine { .. });
12040
12041 if advance_downwards {
12042 let snapshot = this.buffer.read(cx).snapshot(cx);
12043
12044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12045 s.move_cursors_with(|display_snapshot, display_point, _| {
12046 let mut point = display_point.to_point(display_snapshot);
12047 point.row += 1;
12048 point = snapshot.clip_point(point, Bias::Left);
12049 let display_point = point.to_display_point(display_snapshot);
12050 let goal = SelectionGoal::HorizontalPosition(
12051 display_snapshot
12052 .x_for_display_point(display_point, text_layout_details)
12053 .into(),
12054 );
12055 (display_point, goal)
12056 })
12057 });
12058 }
12059 });
12060 }
12061
12062 pub fn select_enclosing_symbol(
12063 &mut self,
12064 _: &SelectEnclosingSymbol,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12069
12070 let buffer = self.buffer.read(cx).snapshot(cx);
12071 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12072
12073 fn update_selection(
12074 selection: &Selection<usize>,
12075 buffer_snap: &MultiBufferSnapshot,
12076 ) -> Option<Selection<usize>> {
12077 let cursor = selection.head();
12078 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12079 for symbol in symbols.iter().rev() {
12080 let start = symbol.range.start.to_offset(buffer_snap);
12081 let end = symbol.range.end.to_offset(buffer_snap);
12082 let new_range = start..end;
12083 if start < selection.start || end > selection.end {
12084 return Some(Selection {
12085 id: selection.id,
12086 start: new_range.start,
12087 end: new_range.end,
12088 goal: SelectionGoal::None,
12089 reversed: selection.reversed,
12090 });
12091 }
12092 }
12093 None
12094 }
12095
12096 let mut selected_larger_symbol = false;
12097 let new_selections = old_selections
12098 .iter()
12099 .map(|selection| match update_selection(selection, &buffer) {
12100 Some(new_selection) => {
12101 if new_selection.range() != selection.range() {
12102 selected_larger_symbol = true;
12103 }
12104 new_selection
12105 }
12106 None => selection.clone(),
12107 })
12108 .collect::<Vec<_>>();
12109
12110 if selected_larger_symbol {
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.select(new_selections);
12113 });
12114 }
12115 }
12116
12117 pub fn select_larger_syntax_node(
12118 &mut self,
12119 _: &SelectLargerSyntaxNode,
12120 window: &mut Window,
12121 cx: &mut Context<Self>,
12122 ) {
12123 let Some(visible_row_count) = self.visible_row_count() else {
12124 return;
12125 };
12126 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12127 if old_selections.is_empty() {
12128 return;
12129 }
12130
12131 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12132
12133 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12134 let buffer = self.buffer.read(cx).snapshot(cx);
12135
12136 let mut selected_larger_node = false;
12137 let mut new_selections = old_selections
12138 .iter()
12139 .map(|selection| {
12140 let old_range = selection.start..selection.end;
12141 let mut new_range = old_range.clone();
12142 let mut new_node = None;
12143 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12144 {
12145 new_node = Some(node);
12146 new_range = match containing_range {
12147 MultiOrSingleBufferOffsetRange::Single(_) => break,
12148 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12149 };
12150 if !display_map.intersects_fold(new_range.start)
12151 && !display_map.intersects_fold(new_range.end)
12152 {
12153 break;
12154 }
12155 }
12156
12157 if let Some(node) = new_node {
12158 // Log the ancestor, to support using this action as a way to explore TreeSitter
12159 // nodes. Parent and grandparent are also logged because this operation will not
12160 // visit nodes that have the same range as their parent.
12161 log::info!("Node: {node:?}");
12162 let parent = node.parent();
12163 log::info!("Parent: {parent:?}");
12164 let grandparent = parent.and_then(|x| x.parent());
12165 log::info!("Grandparent: {grandparent:?}");
12166 }
12167
12168 selected_larger_node |= new_range != old_range;
12169 Selection {
12170 id: selection.id,
12171 start: new_range.start,
12172 end: new_range.end,
12173 goal: SelectionGoal::None,
12174 reversed: selection.reversed,
12175 }
12176 })
12177 .collect::<Vec<_>>();
12178
12179 if !selected_larger_node {
12180 return; // don't put this call in the history
12181 }
12182
12183 // scroll based on transformation done to the last selection created by the user
12184 let (last_old, last_new) = old_selections
12185 .last()
12186 .zip(new_selections.last().cloned())
12187 .expect("old_selections isn't empty");
12188
12189 // revert selection
12190 let is_selection_reversed = {
12191 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12192 new_selections.last_mut().expect("checked above").reversed =
12193 should_newest_selection_be_reversed;
12194 should_newest_selection_be_reversed
12195 };
12196
12197 if selected_larger_node {
12198 self.select_syntax_node_history.disable_clearing = true;
12199 self.change_selections(None, window, cx, |s| {
12200 s.select(new_selections.clone());
12201 });
12202 self.select_syntax_node_history.disable_clearing = false;
12203 }
12204
12205 let start_row = last_new.start.to_display_point(&display_map).row().0;
12206 let end_row = last_new.end.to_display_point(&display_map).row().0;
12207 let selection_height = end_row - start_row + 1;
12208 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12209
12210 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
12211 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
12212 let middle_row = (end_row + start_row) / 2;
12213 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12214 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12215 SelectSyntaxNodeScrollBehavior::CenterSelection
12216 } else if is_selection_reversed {
12217 self.scroll_cursor_top(&Default::default(), window, cx);
12218 SelectSyntaxNodeScrollBehavior::CursorTop
12219 } else {
12220 self.scroll_cursor_bottom(&Default::default(), window, cx);
12221 SelectSyntaxNodeScrollBehavior::CursorBottom
12222 };
12223
12224 self.select_syntax_node_history.push((
12225 old_selections,
12226 scroll_behavior,
12227 is_selection_reversed,
12228 ));
12229 }
12230
12231 pub fn select_smaller_syntax_node(
12232 &mut self,
12233 _: &SelectSmallerSyntaxNode,
12234 window: &mut Window,
12235 cx: &mut Context<Self>,
12236 ) {
12237 let Some(visible_row_count) = self.visible_row_count() else {
12238 return;
12239 };
12240
12241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12242
12243 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12244 self.select_syntax_node_history.pop()
12245 {
12246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12247
12248 if let Some(selection) = selections.last_mut() {
12249 selection.reversed = is_selection_reversed;
12250 }
12251
12252 self.select_syntax_node_history.disable_clearing = true;
12253 self.change_selections(None, window, cx, |s| {
12254 s.select(selections.to_vec());
12255 });
12256 self.select_syntax_node_history.disable_clearing = false;
12257
12258 let newest = self.selections.newest::<usize>(cx);
12259 let start_row = newest.start.to_display_point(&display_map).row().0;
12260 let end_row = newest.end.to_display_point(&display_map).row().0;
12261
12262 match scroll_behavior {
12263 SelectSyntaxNodeScrollBehavior::CursorTop => {
12264 self.scroll_cursor_top(&Default::default(), window, cx);
12265 }
12266 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12267 let middle_row = (end_row + start_row) / 2;
12268 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12269 // centralize the selection, not the cursor
12270 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12271 }
12272 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12273 self.scroll_cursor_bottom(&Default::default(), window, cx);
12274 }
12275 }
12276 }
12277 }
12278
12279 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12280 if !EditorSettings::get_global(cx).gutter.runnables {
12281 self.clear_tasks();
12282 return Task::ready(());
12283 }
12284 let project = self.project.as_ref().map(Entity::downgrade);
12285 cx.spawn_in(window, async move |this, cx| {
12286 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12287 let Some(project) = project.and_then(|p| p.upgrade()) else {
12288 return;
12289 };
12290 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12291 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12292 }) else {
12293 return;
12294 };
12295
12296 let hide_runnables = project
12297 .update(cx, |project, cx| {
12298 // Do not display any test indicators in non-dev server remote projects.
12299 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12300 })
12301 .unwrap_or(true);
12302 if hide_runnables {
12303 return;
12304 }
12305 let new_rows =
12306 cx.background_spawn({
12307 let snapshot = display_snapshot.clone();
12308 async move {
12309 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12310 }
12311 })
12312 .await;
12313
12314 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12315 this.update(cx, |this, _| {
12316 this.clear_tasks();
12317 for (key, value) in rows {
12318 this.insert_tasks(key, value);
12319 }
12320 })
12321 .ok();
12322 })
12323 }
12324 fn fetch_runnable_ranges(
12325 snapshot: &DisplaySnapshot,
12326 range: Range<Anchor>,
12327 ) -> Vec<language::RunnableRange> {
12328 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12329 }
12330
12331 fn runnable_rows(
12332 project: Entity<Project>,
12333 snapshot: DisplaySnapshot,
12334 runnable_ranges: Vec<RunnableRange>,
12335 mut cx: AsyncWindowContext,
12336 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12337 runnable_ranges
12338 .into_iter()
12339 .filter_map(|mut runnable| {
12340 let tasks = cx
12341 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12342 .ok()?;
12343 if tasks.is_empty() {
12344 return None;
12345 }
12346
12347 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12348
12349 let row = snapshot
12350 .buffer_snapshot
12351 .buffer_line_for_row(MultiBufferRow(point.row))?
12352 .1
12353 .start
12354 .row;
12355
12356 let context_range =
12357 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12358 Some((
12359 (runnable.buffer_id, row),
12360 RunnableTasks {
12361 templates: tasks,
12362 offset: snapshot
12363 .buffer_snapshot
12364 .anchor_before(runnable.run_range.start),
12365 context_range,
12366 column: point.column,
12367 extra_variables: runnable.extra_captures,
12368 },
12369 ))
12370 })
12371 .collect()
12372 }
12373
12374 fn templates_with_tags(
12375 project: &Entity<Project>,
12376 runnable: &mut Runnable,
12377 cx: &mut App,
12378 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12379 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12380 let (worktree_id, file) = project
12381 .buffer_for_id(runnable.buffer, cx)
12382 .and_then(|buffer| buffer.read(cx).file())
12383 .map(|file| (file.worktree_id(cx), file.clone()))
12384 .unzip();
12385
12386 (
12387 project.task_store().read(cx).task_inventory().cloned(),
12388 worktree_id,
12389 file,
12390 )
12391 });
12392
12393 let tags = mem::take(&mut runnable.tags);
12394 let mut tags: Vec<_> = tags
12395 .into_iter()
12396 .flat_map(|tag| {
12397 let tag = tag.0.clone();
12398 inventory
12399 .as_ref()
12400 .into_iter()
12401 .flat_map(|inventory| {
12402 inventory.read(cx).list_tasks(
12403 file.clone(),
12404 Some(runnable.language.clone()),
12405 worktree_id,
12406 cx,
12407 )
12408 })
12409 .filter(move |(_, template)| {
12410 template.tags.iter().any(|source_tag| source_tag == &tag)
12411 })
12412 })
12413 .sorted_by_key(|(kind, _)| kind.to_owned())
12414 .collect();
12415 if let Some((leading_tag_source, _)) = tags.first() {
12416 // Strongest source wins; if we have worktree tag binding, prefer that to
12417 // global and language bindings;
12418 // if we have a global binding, prefer that to language binding.
12419 let first_mismatch = tags
12420 .iter()
12421 .position(|(tag_source, _)| tag_source != leading_tag_source);
12422 if let Some(index) = first_mismatch {
12423 tags.truncate(index);
12424 }
12425 }
12426
12427 tags
12428 }
12429
12430 pub fn move_to_enclosing_bracket(
12431 &mut self,
12432 _: &MoveToEnclosingBracket,
12433 window: &mut Window,
12434 cx: &mut Context<Self>,
12435 ) {
12436 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12438 s.move_offsets_with(|snapshot, selection| {
12439 let Some(enclosing_bracket_ranges) =
12440 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12441 else {
12442 return;
12443 };
12444
12445 let mut best_length = usize::MAX;
12446 let mut best_inside = false;
12447 let mut best_in_bracket_range = false;
12448 let mut best_destination = None;
12449 for (open, close) in enclosing_bracket_ranges {
12450 let close = close.to_inclusive();
12451 let length = close.end() - open.start;
12452 let inside = selection.start >= open.end && selection.end <= *close.start();
12453 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12454 || close.contains(&selection.head());
12455
12456 // If best is next to a bracket and current isn't, skip
12457 if !in_bracket_range && best_in_bracket_range {
12458 continue;
12459 }
12460
12461 // Prefer smaller lengths unless best is inside and current isn't
12462 if length > best_length && (best_inside || !inside) {
12463 continue;
12464 }
12465
12466 best_length = length;
12467 best_inside = inside;
12468 best_in_bracket_range = in_bracket_range;
12469 best_destination = Some(
12470 if close.contains(&selection.start) && close.contains(&selection.end) {
12471 if inside {
12472 open.end
12473 } else {
12474 open.start
12475 }
12476 } else if inside {
12477 *close.start()
12478 } else {
12479 *close.end()
12480 },
12481 );
12482 }
12483
12484 if let Some(destination) = best_destination {
12485 selection.collapse_to(destination, SelectionGoal::None);
12486 }
12487 })
12488 });
12489 }
12490
12491 pub fn undo_selection(
12492 &mut self,
12493 _: &UndoSelection,
12494 window: &mut Window,
12495 cx: &mut Context<Self>,
12496 ) {
12497 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12498 self.end_selection(window, cx);
12499 self.selection_history.mode = SelectionHistoryMode::Undoing;
12500 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12501 self.change_selections(None, window, cx, |s| {
12502 s.select_anchors(entry.selections.to_vec())
12503 });
12504 self.select_next_state = entry.select_next_state;
12505 self.select_prev_state = entry.select_prev_state;
12506 self.add_selections_state = entry.add_selections_state;
12507 self.request_autoscroll(Autoscroll::newest(), cx);
12508 }
12509 self.selection_history.mode = SelectionHistoryMode::Normal;
12510 }
12511
12512 pub fn redo_selection(
12513 &mut self,
12514 _: &RedoSelection,
12515 window: &mut Window,
12516 cx: &mut Context<Self>,
12517 ) {
12518 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12519 self.end_selection(window, cx);
12520 self.selection_history.mode = SelectionHistoryMode::Redoing;
12521 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12522 self.change_selections(None, window, cx, |s| {
12523 s.select_anchors(entry.selections.to_vec())
12524 });
12525 self.select_next_state = entry.select_next_state;
12526 self.select_prev_state = entry.select_prev_state;
12527 self.add_selections_state = entry.add_selections_state;
12528 self.request_autoscroll(Autoscroll::newest(), cx);
12529 }
12530 self.selection_history.mode = SelectionHistoryMode::Normal;
12531 }
12532
12533 pub fn expand_excerpts(
12534 &mut self,
12535 action: &ExpandExcerpts,
12536 _: &mut Window,
12537 cx: &mut Context<Self>,
12538 ) {
12539 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12540 }
12541
12542 pub fn expand_excerpts_down(
12543 &mut self,
12544 action: &ExpandExcerptsDown,
12545 _: &mut Window,
12546 cx: &mut Context<Self>,
12547 ) {
12548 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12549 }
12550
12551 pub fn expand_excerpts_up(
12552 &mut self,
12553 action: &ExpandExcerptsUp,
12554 _: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) {
12557 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12558 }
12559
12560 pub fn expand_excerpts_for_direction(
12561 &mut self,
12562 lines: u32,
12563 direction: ExpandExcerptDirection,
12564
12565 cx: &mut Context<Self>,
12566 ) {
12567 let selections = self.selections.disjoint_anchors();
12568
12569 let lines = if lines == 0 {
12570 EditorSettings::get_global(cx).expand_excerpt_lines
12571 } else {
12572 lines
12573 };
12574
12575 self.buffer.update(cx, |buffer, cx| {
12576 let snapshot = buffer.snapshot(cx);
12577 let mut excerpt_ids = selections
12578 .iter()
12579 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12580 .collect::<Vec<_>>();
12581 excerpt_ids.sort();
12582 excerpt_ids.dedup();
12583 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12584 })
12585 }
12586
12587 pub fn expand_excerpt(
12588 &mut self,
12589 excerpt: ExcerptId,
12590 direction: ExpandExcerptDirection,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 let current_scroll_position = self.scroll_position(cx);
12595 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12596 self.buffer.update(cx, |buffer, cx| {
12597 buffer.expand_excerpts([excerpt], lines, direction, cx)
12598 });
12599 if direction == ExpandExcerptDirection::Down {
12600 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12601 self.set_scroll_position(new_scroll_position, window, cx);
12602 }
12603 }
12604
12605 pub fn go_to_singleton_buffer_point(
12606 &mut self,
12607 point: Point,
12608 window: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) {
12611 self.go_to_singleton_buffer_range(point..point, window, cx);
12612 }
12613
12614 pub fn go_to_singleton_buffer_range(
12615 &mut self,
12616 range: Range<Point>,
12617 window: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 let multibuffer = self.buffer().read(cx);
12621 let Some(buffer) = multibuffer.as_singleton() else {
12622 return;
12623 };
12624 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12625 return;
12626 };
12627 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12628 return;
12629 };
12630 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12631 s.select_anchor_ranges([start..end])
12632 });
12633 }
12634
12635 fn go_to_diagnostic(
12636 &mut self,
12637 _: &GoToDiagnostic,
12638 window: &mut Window,
12639 cx: &mut Context<Self>,
12640 ) {
12641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12642 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12643 }
12644
12645 fn go_to_prev_diagnostic(
12646 &mut self,
12647 _: &GoToPreviousDiagnostic,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 ) {
12651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12652 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12653 }
12654
12655 pub fn go_to_diagnostic_impl(
12656 &mut self,
12657 direction: Direction,
12658 window: &mut Window,
12659 cx: &mut Context<Self>,
12660 ) {
12661 let buffer = self.buffer.read(cx).snapshot(cx);
12662 let selection = self.selections.newest::<usize>(cx);
12663
12664 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12665 if direction == Direction::Next {
12666 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12667 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12668 return;
12669 };
12670 self.activate_diagnostics(
12671 buffer_id,
12672 popover.local_diagnostic.diagnostic.group_id,
12673 window,
12674 cx,
12675 );
12676 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12677 let primary_range_start = active_diagnostics.primary_range.start;
12678 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12679 let mut new_selection = s.newest_anchor().clone();
12680 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12681 s.select_anchors(vec![new_selection.clone()]);
12682 });
12683 self.refresh_inline_completion(false, true, window, cx);
12684 }
12685 return;
12686 }
12687 }
12688
12689 let active_group_id = self
12690 .active_diagnostics
12691 .as_ref()
12692 .map(|active_group| active_group.group_id);
12693 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12694 active_diagnostics
12695 .primary_range
12696 .to_offset(&buffer)
12697 .to_inclusive()
12698 });
12699 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12700 if active_primary_range.contains(&selection.head()) {
12701 *active_primary_range.start()
12702 } else {
12703 selection.head()
12704 }
12705 } else {
12706 selection.head()
12707 };
12708
12709 let snapshot = self.snapshot(window, cx);
12710 let primary_diagnostics_before = buffer
12711 .diagnostics_in_range::<usize>(0..search_start)
12712 .filter(|entry| entry.diagnostic.is_primary)
12713 .filter(|entry| entry.range.start != entry.range.end)
12714 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12715 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12716 .collect::<Vec<_>>();
12717 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12718 primary_diagnostics_before
12719 .iter()
12720 .position(|entry| entry.diagnostic.group_id == active_group_id)
12721 });
12722
12723 let primary_diagnostics_after = buffer
12724 .diagnostics_in_range::<usize>(search_start..buffer.len())
12725 .filter(|entry| entry.diagnostic.is_primary)
12726 .filter(|entry| entry.range.start != entry.range.end)
12727 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12728 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12729 .collect::<Vec<_>>();
12730 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12731 primary_diagnostics_after
12732 .iter()
12733 .enumerate()
12734 .rev()
12735 .find_map(|(i, entry)| {
12736 if entry.diagnostic.group_id == active_group_id {
12737 Some(i)
12738 } else {
12739 None
12740 }
12741 })
12742 });
12743
12744 let next_primary_diagnostic = match direction {
12745 Direction::Prev => primary_diagnostics_before
12746 .iter()
12747 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12748 .rev()
12749 .next(),
12750 Direction::Next => primary_diagnostics_after
12751 .iter()
12752 .skip(
12753 last_same_group_diagnostic_after
12754 .map(|index| index + 1)
12755 .unwrap_or(0),
12756 )
12757 .next(),
12758 };
12759
12760 // Cycle around to the start of the buffer, potentially moving back to the start of
12761 // the currently active diagnostic.
12762 let cycle_around = || match direction {
12763 Direction::Prev => primary_diagnostics_after
12764 .iter()
12765 .rev()
12766 .chain(primary_diagnostics_before.iter().rev())
12767 .next(),
12768 Direction::Next => primary_diagnostics_before
12769 .iter()
12770 .chain(primary_diagnostics_after.iter())
12771 .next(),
12772 };
12773
12774 if let Some((primary_range, group_id)) = next_primary_diagnostic
12775 .or_else(cycle_around)
12776 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12777 {
12778 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12779 return;
12780 };
12781 self.activate_diagnostics(buffer_id, group_id, window, cx);
12782 if self.active_diagnostics.is_some() {
12783 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12784 s.select(vec![Selection {
12785 id: selection.id,
12786 start: primary_range.start,
12787 end: primary_range.start,
12788 reversed: false,
12789 goal: SelectionGoal::None,
12790 }]);
12791 });
12792 self.refresh_inline_completion(false, true, window, cx);
12793 }
12794 }
12795 }
12796
12797 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12799 let snapshot = self.snapshot(window, cx);
12800 let selection = self.selections.newest::<Point>(cx);
12801 self.go_to_hunk_before_or_after_position(
12802 &snapshot,
12803 selection.head(),
12804 Direction::Next,
12805 window,
12806 cx,
12807 );
12808 }
12809
12810 pub fn go_to_hunk_before_or_after_position(
12811 &mut self,
12812 snapshot: &EditorSnapshot,
12813 position: Point,
12814 direction: Direction,
12815 window: &mut Window,
12816 cx: &mut Context<Editor>,
12817 ) {
12818 let row = if direction == Direction::Next {
12819 self.hunk_after_position(snapshot, position)
12820 .map(|hunk| hunk.row_range.start)
12821 } else {
12822 self.hunk_before_position(snapshot, position)
12823 };
12824
12825 if let Some(row) = row {
12826 let destination = Point::new(row.0, 0);
12827 let autoscroll = Autoscroll::center();
12828
12829 self.unfold_ranges(&[destination..destination], false, false, cx);
12830 self.change_selections(Some(autoscroll), window, cx, |s| {
12831 s.select_ranges([destination..destination]);
12832 });
12833 }
12834 }
12835
12836 fn hunk_after_position(
12837 &mut self,
12838 snapshot: &EditorSnapshot,
12839 position: Point,
12840 ) -> Option<MultiBufferDiffHunk> {
12841 snapshot
12842 .buffer_snapshot
12843 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12844 .find(|hunk| hunk.row_range.start.0 > position.row)
12845 .or_else(|| {
12846 snapshot
12847 .buffer_snapshot
12848 .diff_hunks_in_range(Point::zero()..position)
12849 .find(|hunk| hunk.row_range.end.0 < position.row)
12850 })
12851 }
12852
12853 fn go_to_prev_hunk(
12854 &mut self,
12855 _: &GoToPreviousHunk,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12860 let snapshot = self.snapshot(window, cx);
12861 let selection = self.selections.newest::<Point>(cx);
12862 self.go_to_hunk_before_or_after_position(
12863 &snapshot,
12864 selection.head(),
12865 Direction::Prev,
12866 window,
12867 cx,
12868 );
12869 }
12870
12871 fn hunk_before_position(
12872 &mut self,
12873 snapshot: &EditorSnapshot,
12874 position: Point,
12875 ) -> Option<MultiBufferRow> {
12876 snapshot
12877 .buffer_snapshot
12878 .diff_hunk_before(position)
12879 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12880 }
12881
12882 fn go_to_line<T: 'static>(
12883 &mut self,
12884 position: Anchor,
12885 highlight_color: Option<Hsla>,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 let snapshot = self.snapshot(window, cx).display_snapshot;
12890 let position = position.to_point(&snapshot.buffer_snapshot);
12891 let start = snapshot
12892 .buffer_snapshot
12893 .clip_point(Point::new(position.row, 0), Bias::Left);
12894 let end = start + Point::new(1, 0);
12895 let start = snapshot.buffer_snapshot.anchor_before(start);
12896 let end = snapshot.buffer_snapshot.anchor_before(end);
12897
12898 self.highlight_rows::<T>(
12899 start..end,
12900 highlight_color
12901 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12902 false,
12903 cx,
12904 );
12905 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
12906 }
12907
12908 pub fn go_to_definition(
12909 &mut self,
12910 _: &GoToDefinition,
12911 window: &mut Window,
12912 cx: &mut Context<Self>,
12913 ) -> Task<Result<Navigated>> {
12914 let definition =
12915 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12916 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
12917 cx.spawn_in(window, async move |editor, cx| {
12918 if definition.await? == Navigated::Yes {
12919 return Ok(Navigated::Yes);
12920 }
12921 match fallback_strategy {
12922 GoToDefinitionFallback::None => Ok(Navigated::No),
12923 GoToDefinitionFallback::FindAllReferences => {
12924 match editor.update_in(cx, |editor, window, cx| {
12925 editor.find_all_references(&FindAllReferences, window, cx)
12926 })? {
12927 Some(references) => references.await,
12928 None => Ok(Navigated::No),
12929 }
12930 }
12931 }
12932 })
12933 }
12934
12935 pub fn go_to_declaration(
12936 &mut self,
12937 _: &GoToDeclaration,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) -> Task<Result<Navigated>> {
12941 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12942 }
12943
12944 pub fn go_to_declaration_split(
12945 &mut self,
12946 _: &GoToDeclaration,
12947 window: &mut Window,
12948 cx: &mut Context<Self>,
12949 ) -> Task<Result<Navigated>> {
12950 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12951 }
12952
12953 pub fn go_to_implementation(
12954 &mut self,
12955 _: &GoToImplementation,
12956 window: &mut Window,
12957 cx: &mut Context<Self>,
12958 ) -> Task<Result<Navigated>> {
12959 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12960 }
12961
12962 pub fn go_to_implementation_split(
12963 &mut self,
12964 _: &GoToImplementationSplit,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) -> Task<Result<Navigated>> {
12968 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12969 }
12970
12971 pub fn go_to_type_definition(
12972 &mut self,
12973 _: &GoToTypeDefinition,
12974 window: &mut Window,
12975 cx: &mut Context<Self>,
12976 ) -> Task<Result<Navigated>> {
12977 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12978 }
12979
12980 pub fn go_to_definition_split(
12981 &mut self,
12982 _: &GoToDefinitionSplit,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) -> Task<Result<Navigated>> {
12986 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
12987 }
12988
12989 pub fn go_to_type_definition_split(
12990 &mut self,
12991 _: &GoToTypeDefinitionSplit,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) -> Task<Result<Navigated>> {
12995 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
12996 }
12997
12998 fn go_to_definition_of_kind(
12999 &mut self,
13000 kind: GotoDefinitionKind,
13001 split: bool,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) -> Task<Result<Navigated>> {
13005 let Some(provider) = self.semantics_provider.clone() else {
13006 return Task::ready(Ok(Navigated::No));
13007 };
13008 let head = self.selections.newest::<usize>(cx).head();
13009 let buffer = self.buffer.read(cx);
13010 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13011 text_anchor
13012 } else {
13013 return Task::ready(Ok(Navigated::No));
13014 };
13015
13016 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13017 return Task::ready(Ok(Navigated::No));
13018 };
13019
13020 cx.spawn_in(window, async move |editor, cx| {
13021 let definitions = definitions.await?;
13022 let navigated = editor
13023 .update_in(cx, |editor, window, cx| {
13024 editor.navigate_to_hover_links(
13025 Some(kind),
13026 definitions
13027 .into_iter()
13028 .filter(|location| {
13029 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13030 })
13031 .map(HoverLink::Text)
13032 .collect::<Vec<_>>(),
13033 split,
13034 window,
13035 cx,
13036 )
13037 })?
13038 .await?;
13039 anyhow::Ok(navigated)
13040 })
13041 }
13042
13043 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13044 let selection = self.selections.newest_anchor();
13045 let head = selection.head();
13046 let tail = selection.tail();
13047
13048 let Some((buffer, start_position)) =
13049 self.buffer.read(cx).text_anchor_for_position(head, cx)
13050 else {
13051 return;
13052 };
13053
13054 let end_position = if head != tail {
13055 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13056 return;
13057 };
13058 Some(pos)
13059 } else {
13060 None
13061 };
13062
13063 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13064 let url = if let Some(end_pos) = end_position {
13065 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13066 } else {
13067 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13068 };
13069
13070 if let Some(url) = url {
13071 editor.update(cx, |_, cx| {
13072 cx.open_url(&url);
13073 })
13074 } else {
13075 Ok(())
13076 }
13077 });
13078
13079 url_finder.detach();
13080 }
13081
13082 pub fn open_selected_filename(
13083 &mut self,
13084 _: &OpenSelectedFilename,
13085 window: &mut Window,
13086 cx: &mut Context<Self>,
13087 ) {
13088 let Some(workspace) = self.workspace() else {
13089 return;
13090 };
13091
13092 let position = self.selections.newest_anchor().head();
13093
13094 let Some((buffer, buffer_position)) =
13095 self.buffer.read(cx).text_anchor_for_position(position, cx)
13096 else {
13097 return;
13098 };
13099
13100 let project = self.project.clone();
13101
13102 cx.spawn_in(window, async move |_, cx| {
13103 let result = find_file(&buffer, project, buffer_position, cx).await;
13104
13105 if let Some((_, path)) = result {
13106 workspace
13107 .update_in(cx, |workspace, window, cx| {
13108 workspace.open_resolved_path(path, window, cx)
13109 })?
13110 .await?;
13111 }
13112 anyhow::Ok(())
13113 })
13114 .detach();
13115 }
13116
13117 pub(crate) fn navigate_to_hover_links(
13118 &mut self,
13119 kind: Option<GotoDefinitionKind>,
13120 mut definitions: Vec<HoverLink>,
13121 split: bool,
13122 window: &mut Window,
13123 cx: &mut Context<Editor>,
13124 ) -> Task<Result<Navigated>> {
13125 // If there is one definition, just open it directly
13126 if definitions.len() == 1 {
13127 let definition = definitions.pop().unwrap();
13128
13129 enum TargetTaskResult {
13130 Location(Option<Location>),
13131 AlreadyNavigated,
13132 }
13133
13134 let target_task = match definition {
13135 HoverLink::Text(link) => {
13136 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13137 }
13138 HoverLink::InlayHint(lsp_location, server_id) => {
13139 let computation =
13140 self.compute_target_location(lsp_location, server_id, window, cx);
13141 cx.background_spawn(async move {
13142 let location = computation.await?;
13143 Ok(TargetTaskResult::Location(location))
13144 })
13145 }
13146 HoverLink::Url(url) => {
13147 cx.open_url(&url);
13148 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13149 }
13150 HoverLink::File(path) => {
13151 if let Some(workspace) = self.workspace() {
13152 cx.spawn_in(window, async move |_, cx| {
13153 workspace
13154 .update_in(cx, |workspace, window, cx| {
13155 workspace.open_resolved_path(path, window, cx)
13156 })?
13157 .await
13158 .map(|_| TargetTaskResult::AlreadyNavigated)
13159 })
13160 } else {
13161 Task::ready(Ok(TargetTaskResult::Location(None)))
13162 }
13163 }
13164 };
13165 cx.spawn_in(window, async move |editor, cx| {
13166 let target = match target_task.await.context("target resolution task")? {
13167 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13168 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13169 TargetTaskResult::Location(Some(target)) => target,
13170 };
13171
13172 editor.update_in(cx, |editor, window, cx| {
13173 let Some(workspace) = editor.workspace() else {
13174 return Navigated::No;
13175 };
13176 let pane = workspace.read(cx).active_pane().clone();
13177
13178 let range = target.range.to_point(target.buffer.read(cx));
13179 let range = editor.range_for_match(&range);
13180 let range = collapse_multiline_range(range);
13181
13182 if !split
13183 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13184 {
13185 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13186 } else {
13187 window.defer(cx, move |window, cx| {
13188 let target_editor: Entity<Self> =
13189 workspace.update(cx, |workspace, cx| {
13190 let pane = if split {
13191 workspace.adjacent_pane(window, cx)
13192 } else {
13193 workspace.active_pane().clone()
13194 };
13195
13196 workspace.open_project_item(
13197 pane,
13198 target.buffer.clone(),
13199 true,
13200 true,
13201 window,
13202 cx,
13203 )
13204 });
13205 target_editor.update(cx, |target_editor, cx| {
13206 // When selecting a definition in a different buffer, disable the nav history
13207 // to avoid creating a history entry at the previous cursor location.
13208 pane.update(cx, |pane, _| pane.disable_history());
13209 target_editor.go_to_singleton_buffer_range(range, window, cx);
13210 pane.update(cx, |pane, _| pane.enable_history());
13211 });
13212 });
13213 }
13214 Navigated::Yes
13215 })
13216 })
13217 } else if !definitions.is_empty() {
13218 cx.spawn_in(window, async move |editor, cx| {
13219 let (title, location_tasks, workspace) = editor
13220 .update_in(cx, |editor, window, cx| {
13221 let tab_kind = match kind {
13222 Some(GotoDefinitionKind::Implementation) => "Implementations",
13223 _ => "Definitions",
13224 };
13225 let title = definitions
13226 .iter()
13227 .find_map(|definition| match definition {
13228 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13229 let buffer = origin.buffer.read(cx);
13230 format!(
13231 "{} for {}",
13232 tab_kind,
13233 buffer
13234 .text_for_range(origin.range.clone())
13235 .collect::<String>()
13236 )
13237 }),
13238 HoverLink::InlayHint(_, _) => None,
13239 HoverLink::Url(_) => None,
13240 HoverLink::File(_) => None,
13241 })
13242 .unwrap_or(tab_kind.to_string());
13243 let location_tasks = definitions
13244 .into_iter()
13245 .map(|definition| match definition {
13246 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13247 HoverLink::InlayHint(lsp_location, server_id) => editor
13248 .compute_target_location(lsp_location, server_id, window, cx),
13249 HoverLink::Url(_) => Task::ready(Ok(None)),
13250 HoverLink::File(_) => Task::ready(Ok(None)),
13251 })
13252 .collect::<Vec<_>>();
13253 (title, location_tasks, editor.workspace().clone())
13254 })
13255 .context("location tasks preparation")?;
13256
13257 let locations = future::join_all(location_tasks)
13258 .await
13259 .into_iter()
13260 .filter_map(|location| location.transpose())
13261 .collect::<Result<_>>()
13262 .context("location tasks")?;
13263
13264 let Some(workspace) = workspace else {
13265 return Ok(Navigated::No);
13266 };
13267 let opened = workspace
13268 .update_in(cx, |workspace, window, cx| {
13269 Self::open_locations_in_multibuffer(
13270 workspace,
13271 locations,
13272 title,
13273 split,
13274 MultibufferSelectionMode::First,
13275 window,
13276 cx,
13277 )
13278 })
13279 .ok();
13280
13281 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13282 })
13283 } else {
13284 Task::ready(Ok(Navigated::No))
13285 }
13286 }
13287
13288 fn compute_target_location(
13289 &self,
13290 lsp_location: lsp::Location,
13291 server_id: LanguageServerId,
13292 window: &mut Window,
13293 cx: &mut Context<Self>,
13294 ) -> Task<anyhow::Result<Option<Location>>> {
13295 let Some(project) = self.project.clone() else {
13296 return Task::ready(Ok(None));
13297 };
13298
13299 cx.spawn_in(window, async move |editor, cx| {
13300 let location_task = editor.update(cx, |_, cx| {
13301 project.update(cx, |project, cx| {
13302 let language_server_name = project
13303 .language_server_statuses(cx)
13304 .find(|(id, _)| server_id == *id)
13305 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13306 language_server_name.map(|language_server_name| {
13307 project.open_local_buffer_via_lsp(
13308 lsp_location.uri.clone(),
13309 server_id,
13310 language_server_name,
13311 cx,
13312 )
13313 })
13314 })
13315 })?;
13316 let location = match location_task {
13317 Some(task) => Some({
13318 let target_buffer_handle = task.await.context("open local buffer")?;
13319 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13320 let target_start = target_buffer
13321 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13322 let target_end = target_buffer
13323 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13324 target_buffer.anchor_after(target_start)
13325 ..target_buffer.anchor_before(target_end)
13326 })?;
13327 Location {
13328 buffer: target_buffer_handle,
13329 range,
13330 }
13331 }),
13332 None => None,
13333 };
13334 Ok(location)
13335 })
13336 }
13337
13338 pub fn find_all_references(
13339 &mut self,
13340 _: &FindAllReferences,
13341 window: &mut Window,
13342 cx: &mut Context<Self>,
13343 ) -> Option<Task<Result<Navigated>>> {
13344 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13345
13346 let selection = self.selections.newest::<usize>(cx);
13347 let multi_buffer = self.buffer.read(cx);
13348 let head = selection.head();
13349
13350 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13351 let head_anchor = multi_buffer_snapshot.anchor_at(
13352 head,
13353 if head < selection.tail() {
13354 Bias::Right
13355 } else {
13356 Bias::Left
13357 },
13358 );
13359
13360 match self
13361 .find_all_references_task_sources
13362 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13363 {
13364 Ok(_) => {
13365 log::info!(
13366 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13367 );
13368 return None;
13369 }
13370 Err(i) => {
13371 self.find_all_references_task_sources.insert(i, head_anchor);
13372 }
13373 }
13374
13375 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13376 let workspace = self.workspace()?;
13377 let project = workspace.read(cx).project().clone();
13378 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13379 Some(cx.spawn_in(window, async move |editor, cx| {
13380 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13381 if let Ok(i) = editor
13382 .find_all_references_task_sources
13383 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13384 {
13385 editor.find_all_references_task_sources.remove(i);
13386 }
13387 });
13388
13389 let locations = references.await?;
13390 if locations.is_empty() {
13391 return anyhow::Ok(Navigated::No);
13392 }
13393
13394 workspace.update_in(cx, |workspace, window, cx| {
13395 let title = locations
13396 .first()
13397 .as_ref()
13398 .map(|location| {
13399 let buffer = location.buffer.read(cx);
13400 format!(
13401 "References to `{}`",
13402 buffer
13403 .text_for_range(location.range.clone())
13404 .collect::<String>()
13405 )
13406 })
13407 .unwrap();
13408 Self::open_locations_in_multibuffer(
13409 workspace,
13410 locations,
13411 title,
13412 false,
13413 MultibufferSelectionMode::First,
13414 window,
13415 cx,
13416 );
13417 Navigated::Yes
13418 })
13419 }))
13420 }
13421
13422 /// Opens a multibuffer with the given project locations in it
13423 pub fn open_locations_in_multibuffer(
13424 workspace: &mut Workspace,
13425 mut locations: Vec<Location>,
13426 title: String,
13427 split: bool,
13428 multibuffer_selection_mode: MultibufferSelectionMode,
13429 window: &mut Window,
13430 cx: &mut Context<Workspace>,
13431 ) {
13432 // If there are multiple definitions, open them in a multibuffer
13433 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13434 let mut locations = locations.into_iter().peekable();
13435 let mut ranges = Vec::new();
13436 let capability = workspace.project().read(cx).capability();
13437
13438 let excerpt_buffer = cx.new(|cx| {
13439 let mut multibuffer = MultiBuffer::new(capability);
13440 while let Some(location) = locations.next() {
13441 let buffer = location.buffer.read(cx);
13442 let mut ranges_for_buffer = Vec::new();
13443 let range = location.range.to_offset(buffer);
13444 ranges_for_buffer.push(range.clone());
13445
13446 while let Some(next_location) = locations.peek() {
13447 if next_location.buffer == location.buffer {
13448 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13449 locations.next();
13450 } else {
13451 break;
13452 }
13453 }
13454
13455 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13456 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13457 location.buffer.clone(),
13458 ranges_for_buffer,
13459 DEFAULT_MULTIBUFFER_CONTEXT,
13460 cx,
13461 ))
13462 }
13463
13464 multibuffer.with_title(title)
13465 });
13466
13467 let editor = cx.new(|cx| {
13468 Editor::for_multibuffer(
13469 excerpt_buffer,
13470 Some(workspace.project().clone()),
13471 window,
13472 cx,
13473 )
13474 });
13475 editor.update(cx, |editor, cx| {
13476 match multibuffer_selection_mode {
13477 MultibufferSelectionMode::First => {
13478 if let Some(first_range) = ranges.first() {
13479 editor.change_selections(None, window, cx, |selections| {
13480 selections.clear_disjoint();
13481 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13482 });
13483 }
13484 editor.highlight_background::<Self>(
13485 &ranges,
13486 |theme| theme.editor_highlighted_line_background,
13487 cx,
13488 );
13489 }
13490 MultibufferSelectionMode::All => {
13491 editor.change_selections(None, window, cx, |selections| {
13492 selections.clear_disjoint();
13493 selections.select_anchor_ranges(ranges);
13494 });
13495 }
13496 }
13497 editor.register_buffers_with_language_servers(cx);
13498 });
13499
13500 let item = Box::new(editor);
13501 let item_id = item.item_id();
13502
13503 if split {
13504 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13505 } else {
13506 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13507 let (preview_item_id, preview_item_idx) =
13508 workspace.active_pane().update(cx, |pane, _| {
13509 (pane.preview_item_id(), pane.preview_item_idx())
13510 });
13511
13512 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13513
13514 if let Some(preview_item_id) = preview_item_id {
13515 workspace.active_pane().update(cx, |pane, cx| {
13516 pane.remove_item(preview_item_id, false, false, window, cx);
13517 });
13518 }
13519 } else {
13520 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13521 }
13522 }
13523 workspace.active_pane().update(cx, |pane, cx| {
13524 pane.set_preview_item_id(Some(item_id), cx);
13525 });
13526 }
13527
13528 pub fn rename(
13529 &mut self,
13530 _: &Rename,
13531 window: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) -> Option<Task<Result<()>>> {
13534 use language::ToOffset as _;
13535
13536 let provider = self.semantics_provider.clone()?;
13537 let selection = self.selections.newest_anchor().clone();
13538 let (cursor_buffer, cursor_buffer_position) = self
13539 .buffer
13540 .read(cx)
13541 .text_anchor_for_position(selection.head(), cx)?;
13542 let (tail_buffer, cursor_buffer_position_end) = self
13543 .buffer
13544 .read(cx)
13545 .text_anchor_for_position(selection.tail(), cx)?;
13546 if tail_buffer != cursor_buffer {
13547 return None;
13548 }
13549
13550 let snapshot = cursor_buffer.read(cx).snapshot();
13551 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13552 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13553 let prepare_rename = provider
13554 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13555 .unwrap_or_else(|| Task::ready(Ok(None)));
13556 drop(snapshot);
13557
13558 Some(cx.spawn_in(window, async move |this, cx| {
13559 let rename_range = if let Some(range) = prepare_rename.await? {
13560 Some(range)
13561 } else {
13562 this.update(cx, |this, cx| {
13563 let buffer = this.buffer.read(cx).snapshot(cx);
13564 let mut buffer_highlights = this
13565 .document_highlights_for_position(selection.head(), &buffer)
13566 .filter(|highlight| {
13567 highlight.start.excerpt_id == selection.head().excerpt_id
13568 && highlight.end.excerpt_id == selection.head().excerpt_id
13569 });
13570 buffer_highlights
13571 .next()
13572 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13573 })?
13574 };
13575 if let Some(rename_range) = rename_range {
13576 this.update_in(cx, |this, window, cx| {
13577 let snapshot = cursor_buffer.read(cx).snapshot();
13578 let rename_buffer_range = rename_range.to_offset(&snapshot);
13579 let cursor_offset_in_rename_range =
13580 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13581 let cursor_offset_in_rename_range_end =
13582 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13583
13584 this.take_rename(false, window, cx);
13585 let buffer = this.buffer.read(cx).read(cx);
13586 let cursor_offset = selection.head().to_offset(&buffer);
13587 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13588 let rename_end = rename_start + rename_buffer_range.len();
13589 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13590 let mut old_highlight_id = None;
13591 let old_name: Arc<str> = buffer
13592 .chunks(rename_start..rename_end, true)
13593 .map(|chunk| {
13594 if old_highlight_id.is_none() {
13595 old_highlight_id = chunk.syntax_highlight_id;
13596 }
13597 chunk.text
13598 })
13599 .collect::<String>()
13600 .into();
13601
13602 drop(buffer);
13603
13604 // Position the selection in the rename editor so that it matches the current selection.
13605 this.show_local_selections = false;
13606 let rename_editor = cx.new(|cx| {
13607 let mut editor = Editor::single_line(window, cx);
13608 editor.buffer.update(cx, |buffer, cx| {
13609 buffer.edit([(0..0, old_name.clone())], None, cx)
13610 });
13611 let rename_selection_range = match cursor_offset_in_rename_range
13612 .cmp(&cursor_offset_in_rename_range_end)
13613 {
13614 Ordering::Equal => {
13615 editor.select_all(&SelectAll, window, cx);
13616 return editor;
13617 }
13618 Ordering::Less => {
13619 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13620 }
13621 Ordering::Greater => {
13622 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13623 }
13624 };
13625 if rename_selection_range.end > old_name.len() {
13626 editor.select_all(&SelectAll, window, cx);
13627 } else {
13628 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13629 s.select_ranges([rename_selection_range]);
13630 });
13631 }
13632 editor
13633 });
13634 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13635 if e == &EditorEvent::Focused {
13636 cx.emit(EditorEvent::FocusedIn)
13637 }
13638 })
13639 .detach();
13640
13641 let write_highlights =
13642 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13643 let read_highlights =
13644 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13645 let ranges = write_highlights
13646 .iter()
13647 .flat_map(|(_, ranges)| ranges.iter())
13648 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13649 .cloned()
13650 .collect();
13651
13652 this.highlight_text::<Rename>(
13653 ranges,
13654 HighlightStyle {
13655 fade_out: Some(0.6),
13656 ..Default::default()
13657 },
13658 cx,
13659 );
13660 let rename_focus_handle = rename_editor.focus_handle(cx);
13661 window.focus(&rename_focus_handle);
13662 let block_id = this.insert_blocks(
13663 [BlockProperties {
13664 style: BlockStyle::Flex,
13665 placement: BlockPlacement::Below(range.start),
13666 height: 1,
13667 render: Arc::new({
13668 let rename_editor = rename_editor.clone();
13669 move |cx: &mut BlockContext| {
13670 let mut text_style = cx.editor_style.text.clone();
13671 if let Some(highlight_style) = old_highlight_id
13672 .and_then(|h| h.style(&cx.editor_style.syntax))
13673 {
13674 text_style = text_style.highlight(highlight_style);
13675 }
13676 div()
13677 .block_mouse_down()
13678 .pl(cx.anchor_x)
13679 .child(EditorElement::new(
13680 &rename_editor,
13681 EditorStyle {
13682 background: cx.theme().system().transparent,
13683 local_player: cx.editor_style.local_player,
13684 text: text_style,
13685 scrollbar_width: cx.editor_style.scrollbar_width,
13686 syntax: cx.editor_style.syntax.clone(),
13687 status: cx.editor_style.status.clone(),
13688 inlay_hints_style: HighlightStyle {
13689 font_weight: Some(FontWeight::BOLD),
13690 ..make_inlay_hints_style(cx.app)
13691 },
13692 inline_completion_styles: make_suggestion_styles(
13693 cx.app,
13694 ),
13695 ..EditorStyle::default()
13696 },
13697 ))
13698 .into_any_element()
13699 }
13700 }),
13701 priority: 0,
13702 }],
13703 Some(Autoscroll::fit()),
13704 cx,
13705 )[0];
13706 this.pending_rename = Some(RenameState {
13707 range,
13708 old_name,
13709 editor: rename_editor,
13710 block_id,
13711 });
13712 })?;
13713 }
13714
13715 Ok(())
13716 }))
13717 }
13718
13719 pub fn confirm_rename(
13720 &mut self,
13721 _: &ConfirmRename,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) -> Option<Task<Result<()>>> {
13725 let rename = self.take_rename(false, window, cx)?;
13726 let workspace = self.workspace()?.downgrade();
13727 let (buffer, start) = self
13728 .buffer
13729 .read(cx)
13730 .text_anchor_for_position(rename.range.start, cx)?;
13731 let (end_buffer, _) = self
13732 .buffer
13733 .read(cx)
13734 .text_anchor_for_position(rename.range.end, cx)?;
13735 if buffer != end_buffer {
13736 return None;
13737 }
13738
13739 let old_name = rename.old_name;
13740 let new_name = rename.editor.read(cx).text(cx);
13741
13742 let rename = self.semantics_provider.as_ref()?.perform_rename(
13743 &buffer,
13744 start,
13745 new_name.clone(),
13746 cx,
13747 )?;
13748
13749 Some(cx.spawn_in(window, async move |editor, cx| {
13750 let project_transaction = rename.await?;
13751 Self::open_project_transaction(
13752 &editor,
13753 workspace,
13754 project_transaction,
13755 format!("Rename: {} → {}", old_name, new_name),
13756 cx,
13757 )
13758 .await?;
13759
13760 editor.update(cx, |editor, cx| {
13761 editor.refresh_document_highlights(cx);
13762 })?;
13763 Ok(())
13764 }))
13765 }
13766
13767 fn take_rename(
13768 &mut self,
13769 moving_cursor: bool,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) -> Option<RenameState> {
13773 let rename = self.pending_rename.take()?;
13774 if rename.editor.focus_handle(cx).is_focused(window) {
13775 window.focus(&self.focus_handle);
13776 }
13777
13778 self.remove_blocks(
13779 [rename.block_id].into_iter().collect(),
13780 Some(Autoscroll::fit()),
13781 cx,
13782 );
13783 self.clear_highlights::<Rename>(cx);
13784 self.show_local_selections = true;
13785
13786 if moving_cursor {
13787 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13788 editor.selections.newest::<usize>(cx).head()
13789 });
13790
13791 // Update the selection to match the position of the selection inside
13792 // the rename editor.
13793 let snapshot = self.buffer.read(cx).read(cx);
13794 let rename_range = rename.range.to_offset(&snapshot);
13795 let cursor_in_editor = snapshot
13796 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13797 .min(rename_range.end);
13798 drop(snapshot);
13799
13800 self.change_selections(None, window, cx, |s| {
13801 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13802 });
13803 } else {
13804 self.refresh_document_highlights(cx);
13805 }
13806
13807 Some(rename)
13808 }
13809
13810 pub fn pending_rename(&self) -> Option<&RenameState> {
13811 self.pending_rename.as_ref()
13812 }
13813
13814 fn format(
13815 &mut self,
13816 _: &Format,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) -> Option<Task<Result<()>>> {
13820 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13821
13822 let project = match &self.project {
13823 Some(project) => project.clone(),
13824 None => return None,
13825 };
13826
13827 Some(self.perform_format(
13828 project,
13829 FormatTrigger::Manual,
13830 FormatTarget::Buffers,
13831 window,
13832 cx,
13833 ))
13834 }
13835
13836 fn format_selections(
13837 &mut self,
13838 _: &FormatSelections,
13839 window: &mut Window,
13840 cx: &mut Context<Self>,
13841 ) -> Option<Task<Result<()>>> {
13842 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13843
13844 let project = match &self.project {
13845 Some(project) => project.clone(),
13846 None => return None,
13847 };
13848
13849 let ranges = self
13850 .selections
13851 .all_adjusted(cx)
13852 .into_iter()
13853 .map(|selection| selection.range())
13854 .collect_vec();
13855
13856 Some(self.perform_format(
13857 project,
13858 FormatTrigger::Manual,
13859 FormatTarget::Ranges(ranges),
13860 window,
13861 cx,
13862 ))
13863 }
13864
13865 fn perform_format(
13866 &mut self,
13867 project: Entity<Project>,
13868 trigger: FormatTrigger,
13869 target: FormatTarget,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) -> Task<Result<()>> {
13873 let buffer = self.buffer.clone();
13874 let (buffers, target) = match target {
13875 FormatTarget::Buffers => {
13876 let mut buffers = buffer.read(cx).all_buffers();
13877 if trigger == FormatTrigger::Save {
13878 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13879 }
13880 (buffers, LspFormatTarget::Buffers)
13881 }
13882 FormatTarget::Ranges(selection_ranges) => {
13883 let multi_buffer = buffer.read(cx);
13884 let snapshot = multi_buffer.read(cx);
13885 let mut buffers = HashSet::default();
13886 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13887 BTreeMap::new();
13888 for selection_range in selection_ranges {
13889 for (buffer, buffer_range, _) in
13890 snapshot.range_to_buffer_ranges(selection_range)
13891 {
13892 let buffer_id = buffer.remote_id();
13893 let start = buffer.anchor_before(buffer_range.start);
13894 let end = buffer.anchor_after(buffer_range.end);
13895 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13896 buffer_id_to_ranges
13897 .entry(buffer_id)
13898 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13899 .or_insert_with(|| vec![start..end]);
13900 }
13901 }
13902 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13903 }
13904 };
13905
13906 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13907 let format = project.update(cx, |project, cx| {
13908 project.format(buffers, target, true, trigger, cx)
13909 });
13910
13911 cx.spawn_in(window, async move |_, cx| {
13912 let transaction = futures::select_biased! {
13913 transaction = format.log_err().fuse() => transaction,
13914 () = timeout => {
13915 log::warn!("timed out waiting for formatting");
13916 None
13917 }
13918 };
13919
13920 buffer
13921 .update(cx, |buffer, cx| {
13922 if let Some(transaction) = transaction {
13923 if !buffer.is_singleton() {
13924 buffer.push_transaction(&transaction.0, cx);
13925 }
13926 }
13927 cx.notify();
13928 })
13929 .ok();
13930
13931 Ok(())
13932 })
13933 }
13934
13935 fn organize_imports(
13936 &mut self,
13937 _: &OrganizeImports,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) -> Option<Task<Result<()>>> {
13941 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13942 let project = match &self.project {
13943 Some(project) => project.clone(),
13944 None => return None,
13945 };
13946 Some(self.perform_code_action_kind(
13947 project,
13948 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13949 window,
13950 cx,
13951 ))
13952 }
13953
13954 fn perform_code_action_kind(
13955 &mut self,
13956 project: Entity<Project>,
13957 kind: CodeActionKind,
13958 window: &mut Window,
13959 cx: &mut Context<Self>,
13960 ) -> Task<Result<()>> {
13961 let buffer = self.buffer.clone();
13962 let buffers = buffer.read(cx).all_buffers();
13963 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13964 let apply_action = project.update(cx, |project, cx| {
13965 project.apply_code_action_kind(buffers, kind, true, cx)
13966 });
13967 cx.spawn_in(window, async move |_, cx| {
13968 let transaction = futures::select_biased! {
13969 () = timeout => {
13970 log::warn!("timed out waiting for executing code action");
13971 None
13972 }
13973 transaction = apply_action.log_err().fuse() => transaction,
13974 };
13975 buffer
13976 .update(cx, |buffer, cx| {
13977 // check if we need this
13978 if let Some(transaction) = transaction {
13979 if !buffer.is_singleton() {
13980 buffer.push_transaction(&transaction.0, cx);
13981 }
13982 }
13983 cx.notify();
13984 })
13985 .ok();
13986 Ok(())
13987 })
13988 }
13989
13990 fn restart_language_server(
13991 &mut self,
13992 _: &RestartLanguageServer,
13993 _: &mut Window,
13994 cx: &mut Context<Self>,
13995 ) {
13996 if let Some(project) = self.project.clone() {
13997 self.buffer.update(cx, |multi_buffer, cx| {
13998 project.update(cx, |project, cx| {
13999 project.restart_language_servers_for_buffers(
14000 multi_buffer.all_buffers().into_iter().collect(),
14001 cx,
14002 );
14003 });
14004 })
14005 }
14006 }
14007
14008 fn cancel_language_server_work(
14009 workspace: &mut Workspace,
14010 _: &actions::CancelLanguageServerWork,
14011 _: &mut Window,
14012 cx: &mut Context<Workspace>,
14013 ) {
14014 let project = workspace.project();
14015 let buffers = workspace
14016 .active_item(cx)
14017 .and_then(|item| item.act_as::<Editor>(cx))
14018 .map_or(HashSet::default(), |editor| {
14019 editor.read(cx).buffer.read(cx).all_buffers()
14020 });
14021 project.update(cx, |project, cx| {
14022 project.cancel_language_server_work_for_buffers(buffers, cx);
14023 });
14024 }
14025
14026 fn show_character_palette(
14027 &mut self,
14028 _: &ShowCharacterPalette,
14029 window: &mut Window,
14030 _: &mut Context<Self>,
14031 ) {
14032 window.show_character_palette();
14033 }
14034
14035 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14036 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14037 let buffer = self.buffer.read(cx).snapshot(cx);
14038 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14039 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14040 let is_valid = buffer
14041 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14042 .any(|entry| {
14043 entry.diagnostic.is_primary
14044 && !entry.range.is_empty()
14045 && entry.range.start == primary_range_start
14046 && entry.diagnostic.message == active_diagnostics.primary_message
14047 });
14048
14049 if is_valid != active_diagnostics.is_valid {
14050 active_diagnostics.is_valid = is_valid;
14051 if is_valid {
14052 let mut new_styles = HashMap::default();
14053 for (block_id, diagnostic) in &active_diagnostics.blocks {
14054 new_styles.insert(
14055 *block_id,
14056 diagnostic_block_renderer(diagnostic.clone(), None, true),
14057 );
14058 }
14059 self.display_map.update(cx, |display_map, _cx| {
14060 display_map.replace_blocks(new_styles);
14061 });
14062 } else {
14063 self.dismiss_diagnostics(cx);
14064 }
14065 }
14066 }
14067 }
14068
14069 fn activate_diagnostics(
14070 &mut self,
14071 buffer_id: BufferId,
14072 group_id: usize,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 self.dismiss_diagnostics(cx);
14077 let snapshot = self.snapshot(window, cx);
14078 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14079 let buffer = self.buffer.read(cx).snapshot(cx);
14080
14081 let mut primary_range = None;
14082 let mut primary_message = None;
14083 let diagnostic_group = buffer
14084 .diagnostic_group(buffer_id, group_id)
14085 .filter_map(|entry| {
14086 let start = entry.range.start;
14087 let end = entry.range.end;
14088 if snapshot.is_line_folded(MultiBufferRow(start.row))
14089 && (start.row == end.row
14090 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14091 {
14092 return None;
14093 }
14094 if entry.diagnostic.is_primary {
14095 primary_range = Some(entry.range.clone());
14096 primary_message = Some(entry.diagnostic.message.clone());
14097 }
14098 Some(entry)
14099 })
14100 .collect::<Vec<_>>();
14101 let primary_range = primary_range?;
14102 let primary_message = primary_message?;
14103
14104 let blocks = display_map
14105 .insert_blocks(
14106 diagnostic_group.iter().map(|entry| {
14107 let diagnostic = entry.diagnostic.clone();
14108 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14109 BlockProperties {
14110 style: BlockStyle::Fixed,
14111 placement: BlockPlacement::Below(
14112 buffer.anchor_after(entry.range.start),
14113 ),
14114 height: message_height,
14115 render: diagnostic_block_renderer(diagnostic, None, true),
14116 priority: 0,
14117 }
14118 }),
14119 cx,
14120 )
14121 .into_iter()
14122 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14123 .collect();
14124
14125 Some(ActiveDiagnosticGroup {
14126 primary_range: buffer.anchor_before(primary_range.start)
14127 ..buffer.anchor_after(primary_range.end),
14128 primary_message,
14129 group_id,
14130 blocks,
14131 is_valid: true,
14132 })
14133 });
14134 }
14135
14136 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14137 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14138 self.display_map.update(cx, |display_map, cx| {
14139 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14140 });
14141 cx.notify();
14142 }
14143 }
14144
14145 /// Disable inline diagnostics rendering for this editor.
14146 pub fn disable_inline_diagnostics(&mut self) {
14147 self.inline_diagnostics_enabled = false;
14148 self.inline_diagnostics_update = Task::ready(());
14149 self.inline_diagnostics.clear();
14150 }
14151
14152 pub fn inline_diagnostics_enabled(&self) -> bool {
14153 self.inline_diagnostics_enabled
14154 }
14155
14156 pub fn show_inline_diagnostics(&self) -> bool {
14157 self.show_inline_diagnostics
14158 }
14159
14160 pub fn toggle_inline_diagnostics(
14161 &mut self,
14162 _: &ToggleInlineDiagnostics,
14163 window: &mut Window,
14164 cx: &mut Context<Editor>,
14165 ) {
14166 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14167 self.refresh_inline_diagnostics(false, window, cx);
14168 }
14169
14170 fn refresh_inline_diagnostics(
14171 &mut self,
14172 debounce: bool,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14177 self.inline_diagnostics_update = Task::ready(());
14178 self.inline_diagnostics.clear();
14179 return;
14180 }
14181
14182 let debounce_ms = ProjectSettings::get_global(cx)
14183 .diagnostics
14184 .inline
14185 .update_debounce_ms;
14186 let debounce = if debounce && debounce_ms > 0 {
14187 Some(Duration::from_millis(debounce_ms))
14188 } else {
14189 None
14190 };
14191 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14192 if let Some(debounce) = debounce {
14193 cx.background_executor().timer(debounce).await;
14194 }
14195 let Some(snapshot) = editor
14196 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14197 .ok()
14198 else {
14199 return;
14200 };
14201
14202 let new_inline_diagnostics = cx
14203 .background_spawn(async move {
14204 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14205 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14206 let message = diagnostic_entry
14207 .diagnostic
14208 .message
14209 .split_once('\n')
14210 .map(|(line, _)| line)
14211 .map(SharedString::new)
14212 .unwrap_or_else(|| {
14213 SharedString::from(diagnostic_entry.diagnostic.message)
14214 });
14215 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14216 let (Ok(i) | Err(i)) = inline_diagnostics
14217 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14218 inline_diagnostics.insert(
14219 i,
14220 (
14221 start_anchor,
14222 InlineDiagnostic {
14223 message,
14224 group_id: diagnostic_entry.diagnostic.group_id,
14225 start: diagnostic_entry.range.start.to_point(&snapshot),
14226 is_primary: diagnostic_entry.diagnostic.is_primary,
14227 severity: diagnostic_entry.diagnostic.severity,
14228 },
14229 ),
14230 );
14231 }
14232 inline_diagnostics
14233 })
14234 .await;
14235
14236 editor
14237 .update(cx, |editor, cx| {
14238 editor.inline_diagnostics = new_inline_diagnostics;
14239 cx.notify();
14240 })
14241 .ok();
14242 });
14243 }
14244
14245 pub fn set_selections_from_remote(
14246 &mut self,
14247 selections: Vec<Selection<Anchor>>,
14248 pending_selection: Option<Selection<Anchor>>,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 let old_cursor_position = self.selections.newest_anchor().head();
14253 self.selections.change_with(cx, |s| {
14254 s.select_anchors(selections);
14255 if let Some(pending_selection) = pending_selection {
14256 s.set_pending(pending_selection, SelectMode::Character);
14257 } else {
14258 s.clear_pending();
14259 }
14260 });
14261 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14262 }
14263
14264 fn push_to_selection_history(&mut self) {
14265 self.selection_history.push(SelectionHistoryEntry {
14266 selections: self.selections.disjoint_anchors(),
14267 select_next_state: self.select_next_state.clone(),
14268 select_prev_state: self.select_prev_state.clone(),
14269 add_selections_state: self.add_selections_state.clone(),
14270 });
14271 }
14272
14273 pub fn transact(
14274 &mut self,
14275 window: &mut Window,
14276 cx: &mut Context<Self>,
14277 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14278 ) -> Option<TransactionId> {
14279 self.start_transaction_at(Instant::now(), window, cx);
14280 update(self, window, cx);
14281 self.end_transaction_at(Instant::now(), cx)
14282 }
14283
14284 pub fn start_transaction_at(
14285 &mut self,
14286 now: Instant,
14287 window: &mut Window,
14288 cx: &mut Context<Self>,
14289 ) {
14290 self.end_selection(window, cx);
14291 if let Some(tx_id) = self
14292 .buffer
14293 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14294 {
14295 self.selection_history
14296 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14297 cx.emit(EditorEvent::TransactionBegun {
14298 transaction_id: tx_id,
14299 })
14300 }
14301 }
14302
14303 pub fn end_transaction_at(
14304 &mut self,
14305 now: Instant,
14306 cx: &mut Context<Self>,
14307 ) -> Option<TransactionId> {
14308 if let Some(transaction_id) = self
14309 .buffer
14310 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14311 {
14312 if let Some((_, end_selections)) =
14313 self.selection_history.transaction_mut(transaction_id)
14314 {
14315 *end_selections = Some(self.selections.disjoint_anchors());
14316 } else {
14317 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14318 }
14319
14320 cx.emit(EditorEvent::Edited { transaction_id });
14321 Some(transaction_id)
14322 } else {
14323 None
14324 }
14325 }
14326
14327 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14328 if self.selection_mark_mode {
14329 self.change_selections(None, window, cx, |s| {
14330 s.move_with(|_, sel| {
14331 sel.collapse_to(sel.head(), SelectionGoal::None);
14332 });
14333 })
14334 }
14335 self.selection_mark_mode = true;
14336 cx.notify();
14337 }
14338
14339 pub fn swap_selection_ends(
14340 &mut self,
14341 _: &actions::SwapSelectionEnds,
14342 window: &mut Window,
14343 cx: &mut Context<Self>,
14344 ) {
14345 self.change_selections(None, window, cx, |s| {
14346 s.move_with(|_, sel| {
14347 if sel.start != sel.end {
14348 sel.reversed = !sel.reversed
14349 }
14350 });
14351 });
14352 self.request_autoscroll(Autoscroll::newest(), cx);
14353 cx.notify();
14354 }
14355
14356 pub fn toggle_fold(
14357 &mut self,
14358 _: &actions::ToggleFold,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) {
14362 if self.is_singleton(cx) {
14363 let selection = self.selections.newest::<Point>(cx);
14364
14365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14366 let range = if selection.is_empty() {
14367 let point = selection.head().to_display_point(&display_map);
14368 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14369 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14370 .to_point(&display_map);
14371 start..end
14372 } else {
14373 selection.range()
14374 };
14375 if display_map.folds_in_range(range).next().is_some() {
14376 self.unfold_lines(&Default::default(), window, cx)
14377 } else {
14378 self.fold(&Default::default(), window, cx)
14379 }
14380 } else {
14381 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14382 let buffer_ids: HashSet<_> = self
14383 .selections
14384 .disjoint_anchor_ranges()
14385 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14386 .collect();
14387
14388 let should_unfold = buffer_ids
14389 .iter()
14390 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14391
14392 for buffer_id in buffer_ids {
14393 if should_unfold {
14394 self.unfold_buffer(buffer_id, cx);
14395 } else {
14396 self.fold_buffer(buffer_id, cx);
14397 }
14398 }
14399 }
14400 }
14401
14402 pub fn toggle_fold_recursive(
14403 &mut self,
14404 _: &actions::ToggleFoldRecursive,
14405 window: &mut Window,
14406 cx: &mut Context<Self>,
14407 ) {
14408 let selection = self.selections.newest::<Point>(cx);
14409
14410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14411 let range = if selection.is_empty() {
14412 let point = selection.head().to_display_point(&display_map);
14413 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14414 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14415 .to_point(&display_map);
14416 start..end
14417 } else {
14418 selection.range()
14419 };
14420 if display_map.folds_in_range(range).next().is_some() {
14421 self.unfold_recursive(&Default::default(), window, cx)
14422 } else {
14423 self.fold_recursive(&Default::default(), window, cx)
14424 }
14425 }
14426
14427 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14428 if self.is_singleton(cx) {
14429 let mut to_fold = Vec::new();
14430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14431 let selections = self.selections.all_adjusted(cx);
14432
14433 for selection in selections {
14434 let range = selection.range().sorted();
14435 let buffer_start_row = range.start.row;
14436
14437 if range.start.row != range.end.row {
14438 let mut found = false;
14439 let mut row = range.start.row;
14440 while row <= range.end.row {
14441 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14442 {
14443 found = true;
14444 row = crease.range().end.row + 1;
14445 to_fold.push(crease);
14446 } else {
14447 row += 1
14448 }
14449 }
14450 if found {
14451 continue;
14452 }
14453 }
14454
14455 for row in (0..=range.start.row).rev() {
14456 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14457 if crease.range().end.row >= buffer_start_row {
14458 to_fold.push(crease);
14459 if row <= range.start.row {
14460 break;
14461 }
14462 }
14463 }
14464 }
14465 }
14466
14467 self.fold_creases(to_fold, true, window, cx);
14468 } else {
14469 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14470 let buffer_ids = self
14471 .selections
14472 .disjoint_anchor_ranges()
14473 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14474 .collect::<HashSet<_>>();
14475 for buffer_id in buffer_ids {
14476 self.fold_buffer(buffer_id, cx);
14477 }
14478 }
14479 }
14480
14481 fn fold_at_level(
14482 &mut self,
14483 fold_at: &FoldAtLevel,
14484 window: &mut Window,
14485 cx: &mut Context<Self>,
14486 ) {
14487 if !self.buffer.read(cx).is_singleton() {
14488 return;
14489 }
14490
14491 let fold_at_level = fold_at.0;
14492 let snapshot = self.buffer.read(cx).snapshot(cx);
14493 let mut to_fold = Vec::new();
14494 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14495
14496 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14497 while start_row < end_row {
14498 match self
14499 .snapshot(window, cx)
14500 .crease_for_buffer_row(MultiBufferRow(start_row))
14501 {
14502 Some(crease) => {
14503 let nested_start_row = crease.range().start.row + 1;
14504 let nested_end_row = crease.range().end.row;
14505
14506 if current_level < fold_at_level {
14507 stack.push((nested_start_row, nested_end_row, current_level + 1));
14508 } else if current_level == fold_at_level {
14509 to_fold.push(crease);
14510 }
14511
14512 start_row = nested_end_row + 1;
14513 }
14514 None => start_row += 1,
14515 }
14516 }
14517 }
14518
14519 self.fold_creases(to_fold, true, window, cx);
14520 }
14521
14522 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14523 if self.buffer.read(cx).is_singleton() {
14524 let mut fold_ranges = Vec::new();
14525 let snapshot = self.buffer.read(cx).snapshot(cx);
14526
14527 for row in 0..snapshot.max_row().0 {
14528 if let Some(foldable_range) = self
14529 .snapshot(window, cx)
14530 .crease_for_buffer_row(MultiBufferRow(row))
14531 {
14532 fold_ranges.push(foldable_range);
14533 }
14534 }
14535
14536 self.fold_creases(fold_ranges, true, window, cx);
14537 } else {
14538 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14539 editor
14540 .update_in(cx, |editor, _, cx| {
14541 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14542 editor.fold_buffer(buffer_id, cx);
14543 }
14544 })
14545 .ok();
14546 });
14547 }
14548 }
14549
14550 pub fn fold_function_bodies(
14551 &mut self,
14552 _: &actions::FoldFunctionBodies,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) {
14556 let snapshot = self.buffer.read(cx).snapshot(cx);
14557
14558 let ranges = snapshot
14559 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14560 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14561 .collect::<Vec<_>>();
14562
14563 let creases = ranges
14564 .into_iter()
14565 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14566 .collect();
14567
14568 self.fold_creases(creases, true, window, cx);
14569 }
14570
14571 pub fn fold_recursive(
14572 &mut self,
14573 _: &actions::FoldRecursive,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 let mut to_fold = Vec::new();
14578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14579 let selections = self.selections.all_adjusted(cx);
14580
14581 for selection in selections {
14582 let range = selection.range().sorted();
14583 let buffer_start_row = range.start.row;
14584
14585 if range.start.row != range.end.row {
14586 let mut found = false;
14587 for row in range.start.row..=range.end.row {
14588 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14589 found = true;
14590 to_fold.push(crease);
14591 }
14592 }
14593 if found {
14594 continue;
14595 }
14596 }
14597
14598 for row in (0..=range.start.row).rev() {
14599 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14600 if crease.range().end.row >= buffer_start_row {
14601 to_fold.push(crease);
14602 } else {
14603 break;
14604 }
14605 }
14606 }
14607 }
14608
14609 self.fold_creases(to_fold, true, window, cx);
14610 }
14611
14612 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14613 let buffer_row = fold_at.buffer_row;
14614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14615
14616 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14617 let autoscroll = self
14618 .selections
14619 .all::<Point>(cx)
14620 .iter()
14621 .any(|selection| crease.range().overlaps(&selection.range()));
14622
14623 self.fold_creases(vec![crease], autoscroll, window, cx);
14624 }
14625 }
14626
14627 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14628 if self.is_singleton(cx) {
14629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14630 let buffer = &display_map.buffer_snapshot;
14631 let selections = self.selections.all::<Point>(cx);
14632 let ranges = selections
14633 .iter()
14634 .map(|s| {
14635 let range = s.display_range(&display_map).sorted();
14636 let mut start = range.start.to_point(&display_map);
14637 let mut end = range.end.to_point(&display_map);
14638 start.column = 0;
14639 end.column = buffer.line_len(MultiBufferRow(end.row));
14640 start..end
14641 })
14642 .collect::<Vec<_>>();
14643
14644 self.unfold_ranges(&ranges, true, true, cx);
14645 } else {
14646 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14647 let buffer_ids = self
14648 .selections
14649 .disjoint_anchor_ranges()
14650 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14651 .collect::<HashSet<_>>();
14652 for buffer_id in buffer_ids {
14653 self.unfold_buffer(buffer_id, cx);
14654 }
14655 }
14656 }
14657
14658 pub fn unfold_recursive(
14659 &mut self,
14660 _: &UnfoldRecursive,
14661 _window: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) {
14664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14665 let selections = self.selections.all::<Point>(cx);
14666 let ranges = selections
14667 .iter()
14668 .map(|s| {
14669 let mut range = s.display_range(&display_map).sorted();
14670 *range.start.column_mut() = 0;
14671 *range.end.column_mut() = display_map.line_len(range.end.row());
14672 let start = range.start.to_point(&display_map);
14673 let end = range.end.to_point(&display_map);
14674 start..end
14675 })
14676 .collect::<Vec<_>>();
14677
14678 self.unfold_ranges(&ranges, true, true, cx);
14679 }
14680
14681 pub fn unfold_at(
14682 &mut self,
14683 unfold_at: &UnfoldAt,
14684 _window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) {
14687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14688
14689 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14690 ..Point::new(
14691 unfold_at.buffer_row.0,
14692 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14693 );
14694
14695 let autoscroll = self
14696 .selections
14697 .all::<Point>(cx)
14698 .iter()
14699 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14700
14701 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14702 }
14703
14704 pub fn unfold_all(
14705 &mut self,
14706 _: &actions::UnfoldAll,
14707 _window: &mut Window,
14708 cx: &mut Context<Self>,
14709 ) {
14710 if self.buffer.read(cx).is_singleton() {
14711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14712 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14713 } else {
14714 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14715 editor
14716 .update(cx, |editor, cx| {
14717 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14718 editor.unfold_buffer(buffer_id, cx);
14719 }
14720 })
14721 .ok();
14722 });
14723 }
14724 }
14725
14726 pub fn fold_selected_ranges(
14727 &mut self,
14728 _: &FoldSelectedRanges,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) {
14732 let selections = self.selections.all_adjusted(cx);
14733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14734 let ranges = selections
14735 .into_iter()
14736 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
14737 .collect::<Vec<_>>();
14738 self.fold_creases(ranges, true, window, cx);
14739 }
14740
14741 pub fn fold_ranges<T: ToOffset + Clone>(
14742 &mut self,
14743 ranges: Vec<Range<T>>,
14744 auto_scroll: bool,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14749 let ranges = ranges
14750 .into_iter()
14751 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14752 .collect::<Vec<_>>();
14753 self.fold_creases(ranges, auto_scroll, window, cx);
14754 }
14755
14756 pub fn fold_creases<T: ToOffset + Clone>(
14757 &mut self,
14758 creases: Vec<Crease<T>>,
14759 auto_scroll: bool,
14760 window: &mut Window,
14761 cx: &mut Context<Self>,
14762 ) {
14763 if creases.is_empty() {
14764 return;
14765 }
14766
14767 let mut buffers_affected = HashSet::default();
14768 let multi_buffer = self.buffer().read(cx);
14769 for crease in &creases {
14770 if let Some((_, buffer, _)) =
14771 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14772 {
14773 buffers_affected.insert(buffer.read(cx).remote_id());
14774 };
14775 }
14776
14777 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14778
14779 if auto_scroll {
14780 self.request_autoscroll(Autoscroll::fit(), cx);
14781 }
14782
14783 cx.notify();
14784
14785 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14786 // Clear diagnostics block when folding a range that contains it.
14787 let snapshot = self.snapshot(window, cx);
14788 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14789 drop(snapshot);
14790 self.active_diagnostics = Some(active_diagnostics);
14791 self.dismiss_diagnostics(cx);
14792 } else {
14793 self.active_diagnostics = Some(active_diagnostics);
14794 }
14795 }
14796
14797 self.scrollbar_marker_state.dirty = true;
14798 self.folds_did_change(cx);
14799 }
14800
14801 /// Removes any folds whose ranges intersect any of the given ranges.
14802 pub fn unfold_ranges<T: ToOffset + Clone>(
14803 &mut self,
14804 ranges: &[Range<T>],
14805 inclusive: bool,
14806 auto_scroll: bool,
14807 cx: &mut Context<Self>,
14808 ) {
14809 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14810 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14811 });
14812 self.folds_did_change(cx);
14813 }
14814
14815 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14816 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14817 return;
14818 }
14819 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14820 self.display_map.update(cx, |display_map, cx| {
14821 display_map.fold_buffers([buffer_id], cx)
14822 });
14823 cx.emit(EditorEvent::BufferFoldToggled {
14824 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14825 folded: true,
14826 });
14827 cx.notify();
14828 }
14829
14830 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14831 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14832 return;
14833 }
14834 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14835 self.display_map.update(cx, |display_map, cx| {
14836 display_map.unfold_buffers([buffer_id], cx);
14837 });
14838 cx.emit(EditorEvent::BufferFoldToggled {
14839 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14840 folded: false,
14841 });
14842 cx.notify();
14843 }
14844
14845 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14846 self.display_map.read(cx).is_buffer_folded(buffer)
14847 }
14848
14849 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14850 self.display_map.read(cx).folded_buffers()
14851 }
14852
14853 /// Removes any folds with the given ranges.
14854 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14855 &mut self,
14856 ranges: &[Range<T>],
14857 type_id: TypeId,
14858 auto_scroll: bool,
14859 cx: &mut Context<Self>,
14860 ) {
14861 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14862 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14863 });
14864 self.folds_did_change(cx);
14865 }
14866
14867 fn remove_folds_with<T: ToOffset + Clone>(
14868 &mut self,
14869 ranges: &[Range<T>],
14870 auto_scroll: bool,
14871 cx: &mut Context<Self>,
14872 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14873 ) {
14874 if ranges.is_empty() {
14875 return;
14876 }
14877
14878 let mut buffers_affected = HashSet::default();
14879 let multi_buffer = self.buffer().read(cx);
14880 for range in ranges {
14881 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14882 buffers_affected.insert(buffer.read(cx).remote_id());
14883 };
14884 }
14885
14886 self.display_map.update(cx, update);
14887
14888 if auto_scroll {
14889 self.request_autoscroll(Autoscroll::fit(), cx);
14890 }
14891
14892 cx.notify();
14893 self.scrollbar_marker_state.dirty = true;
14894 self.active_indent_guides_state.dirty = true;
14895 }
14896
14897 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14898 self.display_map.read(cx).fold_placeholder.clone()
14899 }
14900
14901 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14902 self.buffer.update(cx, |buffer, cx| {
14903 buffer.set_all_diff_hunks_expanded(cx);
14904 });
14905 }
14906
14907 pub fn expand_all_diff_hunks(
14908 &mut self,
14909 _: &ExpandAllDiffHunks,
14910 _window: &mut Window,
14911 cx: &mut Context<Self>,
14912 ) {
14913 self.buffer.update(cx, |buffer, cx| {
14914 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14915 });
14916 }
14917
14918 pub fn toggle_selected_diff_hunks(
14919 &mut self,
14920 _: &ToggleSelectedDiffHunks,
14921 _window: &mut Window,
14922 cx: &mut Context<Self>,
14923 ) {
14924 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14925 self.toggle_diff_hunks_in_ranges(ranges, cx);
14926 }
14927
14928 pub fn diff_hunks_in_ranges<'a>(
14929 &'a self,
14930 ranges: &'a [Range<Anchor>],
14931 buffer: &'a MultiBufferSnapshot,
14932 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14933 ranges.iter().flat_map(move |range| {
14934 let end_excerpt_id = range.end.excerpt_id;
14935 let range = range.to_point(buffer);
14936 let mut peek_end = range.end;
14937 if range.end.row < buffer.max_row().0 {
14938 peek_end = Point::new(range.end.row + 1, 0);
14939 }
14940 buffer
14941 .diff_hunks_in_range(range.start..peek_end)
14942 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14943 })
14944 }
14945
14946 pub fn has_stageable_diff_hunks_in_ranges(
14947 &self,
14948 ranges: &[Range<Anchor>],
14949 snapshot: &MultiBufferSnapshot,
14950 ) -> bool {
14951 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14952 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14953 }
14954
14955 pub fn toggle_staged_selected_diff_hunks(
14956 &mut self,
14957 _: &::git::ToggleStaged,
14958 _: &mut Window,
14959 cx: &mut Context<Self>,
14960 ) {
14961 let snapshot = self.buffer.read(cx).snapshot(cx);
14962 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14963 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14964 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14965 }
14966
14967 pub fn set_render_diff_hunk_controls(
14968 &mut self,
14969 render_diff_hunk_controls: RenderDiffHunkControlsFn,
14970 cx: &mut Context<Self>,
14971 ) {
14972 self.render_diff_hunk_controls = render_diff_hunk_controls;
14973 cx.notify();
14974 }
14975
14976 pub fn stage_and_next(
14977 &mut self,
14978 _: &::git::StageAndNext,
14979 window: &mut Window,
14980 cx: &mut Context<Self>,
14981 ) {
14982 self.do_stage_or_unstage_and_next(true, window, cx);
14983 }
14984
14985 pub fn unstage_and_next(
14986 &mut self,
14987 _: &::git::UnstageAndNext,
14988 window: &mut Window,
14989 cx: &mut Context<Self>,
14990 ) {
14991 self.do_stage_or_unstage_and_next(false, window, cx);
14992 }
14993
14994 pub fn stage_or_unstage_diff_hunks(
14995 &mut self,
14996 stage: bool,
14997 ranges: Vec<Range<Anchor>>,
14998 cx: &mut Context<Self>,
14999 ) {
15000 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15001 cx.spawn(async move |this, cx| {
15002 task.await?;
15003 this.update(cx, |this, cx| {
15004 let snapshot = this.buffer.read(cx).snapshot(cx);
15005 let chunk_by = this
15006 .diff_hunks_in_ranges(&ranges, &snapshot)
15007 .chunk_by(|hunk| hunk.buffer_id);
15008 for (buffer_id, hunks) in &chunk_by {
15009 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15010 }
15011 })
15012 })
15013 .detach_and_log_err(cx);
15014 }
15015
15016 fn save_buffers_for_ranges_if_needed(
15017 &mut self,
15018 ranges: &[Range<Anchor>],
15019 cx: &mut Context<Editor>,
15020 ) -> Task<Result<()>> {
15021 let multibuffer = self.buffer.read(cx);
15022 let snapshot = multibuffer.read(cx);
15023 let buffer_ids: HashSet<_> = ranges
15024 .iter()
15025 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15026 .collect();
15027 drop(snapshot);
15028
15029 let mut buffers = HashSet::default();
15030 for buffer_id in buffer_ids {
15031 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15032 let buffer = buffer_entity.read(cx);
15033 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15034 {
15035 buffers.insert(buffer_entity);
15036 }
15037 }
15038 }
15039
15040 if let Some(project) = &self.project {
15041 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15042 } else {
15043 Task::ready(Ok(()))
15044 }
15045 }
15046
15047 fn do_stage_or_unstage_and_next(
15048 &mut self,
15049 stage: bool,
15050 window: &mut Window,
15051 cx: &mut Context<Self>,
15052 ) {
15053 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15054
15055 if ranges.iter().any(|range| range.start != range.end) {
15056 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15057 return;
15058 }
15059
15060 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15061 let snapshot = self.snapshot(window, cx);
15062 let position = self.selections.newest::<Point>(cx).head();
15063 let mut row = snapshot
15064 .buffer_snapshot
15065 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15066 .find(|hunk| hunk.row_range.start.0 > position.row)
15067 .map(|hunk| hunk.row_range.start);
15068
15069 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15070 // Outside of the project diff editor, wrap around to the beginning.
15071 if !all_diff_hunks_expanded {
15072 row = row.or_else(|| {
15073 snapshot
15074 .buffer_snapshot
15075 .diff_hunks_in_range(Point::zero()..position)
15076 .find(|hunk| hunk.row_range.end.0 < position.row)
15077 .map(|hunk| hunk.row_range.start)
15078 });
15079 }
15080
15081 if let Some(row) = row {
15082 let destination = Point::new(row.0, 0);
15083 let autoscroll = Autoscroll::center();
15084
15085 self.unfold_ranges(&[destination..destination], false, false, cx);
15086 self.change_selections(Some(autoscroll), window, cx, |s| {
15087 s.select_ranges([destination..destination]);
15088 });
15089 }
15090 }
15091
15092 fn do_stage_or_unstage(
15093 &self,
15094 stage: bool,
15095 buffer_id: BufferId,
15096 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15097 cx: &mut App,
15098 ) -> Option<()> {
15099 let project = self.project.as_ref()?;
15100 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15101 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15102 let buffer_snapshot = buffer.read(cx).snapshot();
15103 let file_exists = buffer_snapshot
15104 .file()
15105 .is_some_and(|file| file.disk_state().exists());
15106 diff.update(cx, |diff, cx| {
15107 diff.stage_or_unstage_hunks(
15108 stage,
15109 &hunks
15110 .map(|hunk| buffer_diff::DiffHunk {
15111 buffer_range: hunk.buffer_range,
15112 diff_base_byte_range: hunk.diff_base_byte_range,
15113 secondary_status: hunk.secondary_status,
15114 range: Point::zero()..Point::zero(), // unused
15115 })
15116 .collect::<Vec<_>>(),
15117 &buffer_snapshot,
15118 file_exists,
15119 cx,
15120 )
15121 });
15122 None
15123 }
15124
15125 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15126 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15127 self.buffer
15128 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15129 }
15130
15131 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15132 self.buffer.update(cx, |buffer, cx| {
15133 let ranges = vec![Anchor::min()..Anchor::max()];
15134 if !buffer.all_diff_hunks_expanded()
15135 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15136 {
15137 buffer.collapse_diff_hunks(ranges, cx);
15138 true
15139 } else {
15140 false
15141 }
15142 })
15143 }
15144
15145 fn toggle_diff_hunks_in_ranges(
15146 &mut self,
15147 ranges: Vec<Range<Anchor>>,
15148 cx: &mut Context<Editor>,
15149 ) {
15150 self.buffer.update(cx, |buffer, cx| {
15151 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15152 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15153 })
15154 }
15155
15156 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15157 self.buffer.update(cx, |buffer, cx| {
15158 let snapshot = buffer.snapshot(cx);
15159 let excerpt_id = range.end.excerpt_id;
15160 let point_range = range.to_point(&snapshot);
15161 let expand = !buffer.single_hunk_is_expanded(range, cx);
15162 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15163 })
15164 }
15165
15166 pub(crate) fn apply_all_diff_hunks(
15167 &mut self,
15168 _: &ApplyAllDiffHunks,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15173
15174 let buffers = self.buffer.read(cx).all_buffers();
15175 for branch_buffer in buffers {
15176 branch_buffer.update(cx, |branch_buffer, cx| {
15177 branch_buffer.merge_into_base(Vec::new(), cx);
15178 });
15179 }
15180
15181 if let Some(project) = self.project.clone() {
15182 self.save(true, project, window, cx).detach_and_log_err(cx);
15183 }
15184 }
15185
15186 pub(crate) fn apply_selected_diff_hunks(
15187 &mut self,
15188 _: &ApplyDiffHunk,
15189 window: &mut Window,
15190 cx: &mut Context<Self>,
15191 ) {
15192 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15193 let snapshot = self.snapshot(window, cx);
15194 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15195 let mut ranges_by_buffer = HashMap::default();
15196 self.transact(window, cx, |editor, _window, cx| {
15197 for hunk in hunks {
15198 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15199 ranges_by_buffer
15200 .entry(buffer.clone())
15201 .or_insert_with(Vec::new)
15202 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15203 }
15204 }
15205
15206 for (buffer, ranges) in ranges_by_buffer {
15207 buffer.update(cx, |buffer, cx| {
15208 buffer.merge_into_base(ranges, cx);
15209 });
15210 }
15211 });
15212
15213 if let Some(project) = self.project.clone() {
15214 self.save(true, project, window, cx).detach_and_log_err(cx);
15215 }
15216 }
15217
15218 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15219 if hovered != self.gutter_hovered {
15220 self.gutter_hovered = hovered;
15221 cx.notify();
15222 }
15223 }
15224
15225 pub fn insert_blocks(
15226 &mut self,
15227 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15228 autoscroll: Option<Autoscroll>,
15229 cx: &mut Context<Self>,
15230 ) -> Vec<CustomBlockId> {
15231 let blocks = self
15232 .display_map
15233 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15234 if let Some(autoscroll) = autoscroll {
15235 self.request_autoscroll(autoscroll, cx);
15236 }
15237 cx.notify();
15238 blocks
15239 }
15240
15241 pub fn resize_blocks(
15242 &mut self,
15243 heights: HashMap<CustomBlockId, u32>,
15244 autoscroll: Option<Autoscroll>,
15245 cx: &mut Context<Self>,
15246 ) {
15247 self.display_map
15248 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15249 if let Some(autoscroll) = autoscroll {
15250 self.request_autoscroll(autoscroll, cx);
15251 }
15252 cx.notify();
15253 }
15254
15255 pub fn replace_blocks(
15256 &mut self,
15257 renderers: HashMap<CustomBlockId, RenderBlock>,
15258 autoscroll: Option<Autoscroll>,
15259 cx: &mut Context<Self>,
15260 ) {
15261 self.display_map
15262 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15263 if let Some(autoscroll) = autoscroll {
15264 self.request_autoscroll(autoscroll, cx);
15265 }
15266 cx.notify();
15267 }
15268
15269 pub fn remove_blocks(
15270 &mut self,
15271 block_ids: HashSet<CustomBlockId>,
15272 autoscroll: Option<Autoscroll>,
15273 cx: &mut Context<Self>,
15274 ) {
15275 self.display_map.update(cx, |display_map, cx| {
15276 display_map.remove_blocks(block_ids, cx)
15277 });
15278 if let Some(autoscroll) = autoscroll {
15279 self.request_autoscroll(autoscroll, cx);
15280 }
15281 cx.notify();
15282 }
15283
15284 pub fn row_for_block(
15285 &self,
15286 block_id: CustomBlockId,
15287 cx: &mut Context<Self>,
15288 ) -> Option<DisplayRow> {
15289 self.display_map
15290 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15291 }
15292
15293 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15294 self.focused_block = Some(focused_block);
15295 }
15296
15297 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15298 self.focused_block.take()
15299 }
15300
15301 pub fn insert_creases(
15302 &mut self,
15303 creases: impl IntoIterator<Item = Crease<Anchor>>,
15304 cx: &mut Context<Self>,
15305 ) -> Vec<CreaseId> {
15306 self.display_map
15307 .update(cx, |map, cx| map.insert_creases(creases, cx))
15308 }
15309
15310 pub fn remove_creases(
15311 &mut self,
15312 ids: impl IntoIterator<Item = CreaseId>,
15313 cx: &mut Context<Self>,
15314 ) {
15315 self.display_map
15316 .update(cx, |map, cx| map.remove_creases(ids, cx));
15317 }
15318
15319 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15320 self.display_map
15321 .update(cx, |map, cx| map.snapshot(cx))
15322 .longest_row()
15323 }
15324
15325 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15326 self.display_map
15327 .update(cx, |map, cx| map.snapshot(cx))
15328 .max_point()
15329 }
15330
15331 pub fn text(&self, cx: &App) -> String {
15332 self.buffer.read(cx).read(cx).text()
15333 }
15334
15335 pub fn is_empty(&self, cx: &App) -> bool {
15336 self.buffer.read(cx).read(cx).is_empty()
15337 }
15338
15339 pub fn text_option(&self, cx: &App) -> Option<String> {
15340 let text = self.text(cx);
15341 let text = text.trim();
15342
15343 if text.is_empty() {
15344 return None;
15345 }
15346
15347 Some(text.to_string())
15348 }
15349
15350 pub fn set_text(
15351 &mut self,
15352 text: impl Into<Arc<str>>,
15353 window: &mut Window,
15354 cx: &mut Context<Self>,
15355 ) {
15356 self.transact(window, cx, |this, _, cx| {
15357 this.buffer
15358 .read(cx)
15359 .as_singleton()
15360 .expect("you can only call set_text on editors for singleton buffers")
15361 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15362 });
15363 }
15364
15365 pub fn display_text(&self, cx: &mut App) -> String {
15366 self.display_map
15367 .update(cx, |map, cx| map.snapshot(cx))
15368 .text()
15369 }
15370
15371 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15372 let mut wrap_guides = smallvec::smallvec![];
15373
15374 if self.show_wrap_guides == Some(false) {
15375 return wrap_guides;
15376 }
15377
15378 let settings = self.buffer.read(cx).language_settings(cx);
15379 if settings.show_wrap_guides {
15380 match self.soft_wrap_mode(cx) {
15381 SoftWrap::Column(soft_wrap) => {
15382 wrap_guides.push((soft_wrap as usize, true));
15383 }
15384 SoftWrap::Bounded(soft_wrap) => {
15385 wrap_guides.push((soft_wrap as usize, true));
15386 }
15387 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15388 }
15389 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15390 }
15391
15392 wrap_guides
15393 }
15394
15395 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15396 let settings = self.buffer.read(cx).language_settings(cx);
15397 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15398 match mode {
15399 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15400 SoftWrap::None
15401 }
15402 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15403 language_settings::SoftWrap::PreferredLineLength => {
15404 SoftWrap::Column(settings.preferred_line_length)
15405 }
15406 language_settings::SoftWrap::Bounded => {
15407 SoftWrap::Bounded(settings.preferred_line_length)
15408 }
15409 }
15410 }
15411
15412 pub fn set_soft_wrap_mode(
15413 &mut self,
15414 mode: language_settings::SoftWrap,
15415
15416 cx: &mut Context<Self>,
15417 ) {
15418 self.soft_wrap_mode_override = Some(mode);
15419 cx.notify();
15420 }
15421
15422 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15423 self.hard_wrap = hard_wrap;
15424 cx.notify();
15425 }
15426
15427 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15428 self.text_style_refinement = Some(style);
15429 }
15430
15431 /// called by the Element so we know what style we were most recently rendered with.
15432 pub(crate) fn set_style(
15433 &mut self,
15434 style: EditorStyle,
15435 window: &mut Window,
15436 cx: &mut Context<Self>,
15437 ) {
15438 let rem_size = window.rem_size();
15439 self.display_map.update(cx, |map, cx| {
15440 map.set_font(
15441 style.text.font(),
15442 style.text.font_size.to_pixels(rem_size),
15443 cx,
15444 )
15445 });
15446 self.style = Some(style);
15447 }
15448
15449 pub fn style(&self) -> Option<&EditorStyle> {
15450 self.style.as_ref()
15451 }
15452
15453 // Called by the element. This method is not designed to be called outside of the editor
15454 // element's layout code because it does not notify when rewrapping is computed synchronously.
15455 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15456 self.display_map
15457 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15458 }
15459
15460 pub fn set_soft_wrap(&mut self) {
15461 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15462 }
15463
15464 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15465 if self.soft_wrap_mode_override.is_some() {
15466 self.soft_wrap_mode_override.take();
15467 } else {
15468 let soft_wrap = match self.soft_wrap_mode(cx) {
15469 SoftWrap::GitDiff => return,
15470 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15471 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15472 language_settings::SoftWrap::None
15473 }
15474 };
15475 self.soft_wrap_mode_override = Some(soft_wrap);
15476 }
15477 cx.notify();
15478 }
15479
15480 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15481 let Some(workspace) = self.workspace() else {
15482 return;
15483 };
15484 let fs = workspace.read(cx).app_state().fs.clone();
15485 let current_show = TabBarSettings::get_global(cx).show;
15486 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15487 setting.show = Some(!current_show);
15488 });
15489 }
15490
15491 pub fn toggle_indent_guides(
15492 &mut self,
15493 _: &ToggleIndentGuides,
15494 _: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) {
15497 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15498 self.buffer
15499 .read(cx)
15500 .language_settings(cx)
15501 .indent_guides
15502 .enabled
15503 });
15504 self.show_indent_guides = Some(!currently_enabled);
15505 cx.notify();
15506 }
15507
15508 fn should_show_indent_guides(&self) -> Option<bool> {
15509 self.show_indent_guides
15510 }
15511
15512 pub fn toggle_line_numbers(
15513 &mut self,
15514 _: &ToggleLineNumbers,
15515 _: &mut Window,
15516 cx: &mut Context<Self>,
15517 ) {
15518 let mut editor_settings = EditorSettings::get_global(cx).clone();
15519 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15520 EditorSettings::override_global(editor_settings, cx);
15521 }
15522
15523 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15524 if let Some(show_line_numbers) = self.show_line_numbers {
15525 return show_line_numbers;
15526 }
15527 EditorSettings::get_global(cx).gutter.line_numbers
15528 }
15529
15530 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15531 self.use_relative_line_numbers
15532 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15533 }
15534
15535 pub fn toggle_relative_line_numbers(
15536 &mut self,
15537 _: &ToggleRelativeLineNumbers,
15538 _: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) {
15541 let is_relative = self.should_use_relative_line_numbers(cx);
15542 self.set_relative_line_number(Some(!is_relative), cx)
15543 }
15544
15545 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15546 self.use_relative_line_numbers = is_relative;
15547 cx.notify();
15548 }
15549
15550 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15551 self.show_gutter = show_gutter;
15552 cx.notify();
15553 }
15554
15555 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15556 self.show_scrollbars = show_scrollbars;
15557 cx.notify();
15558 }
15559
15560 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15561 self.show_line_numbers = Some(show_line_numbers);
15562 cx.notify();
15563 }
15564
15565 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15566 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15567 cx.notify();
15568 }
15569
15570 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15571 self.show_code_actions = Some(show_code_actions);
15572 cx.notify();
15573 }
15574
15575 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15576 self.show_runnables = Some(show_runnables);
15577 cx.notify();
15578 }
15579
15580 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15581 self.show_breakpoints = Some(show_breakpoints);
15582 cx.notify();
15583 }
15584
15585 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15586 if self.display_map.read(cx).masked != masked {
15587 self.display_map.update(cx, |map, _| map.masked = masked);
15588 }
15589 cx.notify()
15590 }
15591
15592 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15593 self.show_wrap_guides = Some(show_wrap_guides);
15594 cx.notify();
15595 }
15596
15597 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15598 self.show_indent_guides = Some(show_indent_guides);
15599 cx.notify();
15600 }
15601
15602 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15603 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15604 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15605 if let Some(dir) = file.abs_path(cx).parent() {
15606 return Some(dir.to_owned());
15607 }
15608 }
15609
15610 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15611 return Some(project_path.path.to_path_buf());
15612 }
15613 }
15614
15615 None
15616 }
15617
15618 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15619 self.active_excerpt(cx)?
15620 .1
15621 .read(cx)
15622 .file()
15623 .and_then(|f| f.as_local())
15624 }
15625
15626 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15627 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15628 let buffer = buffer.read(cx);
15629 if let Some(project_path) = buffer.project_path(cx) {
15630 let project = self.project.as_ref()?.read(cx);
15631 project.absolute_path(&project_path, cx)
15632 } else {
15633 buffer
15634 .file()
15635 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15636 }
15637 })
15638 }
15639
15640 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15641 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15642 let project_path = buffer.read(cx).project_path(cx)?;
15643 let project = self.project.as_ref()?.read(cx);
15644 let entry = project.entry_for_path(&project_path, cx)?;
15645 let path = entry.path.to_path_buf();
15646 Some(path)
15647 })
15648 }
15649
15650 pub fn reveal_in_finder(
15651 &mut self,
15652 _: &RevealInFileManager,
15653 _window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 if let Some(target) = self.target_file(cx) {
15657 cx.reveal_path(&target.abs_path(cx));
15658 }
15659 }
15660
15661 pub fn copy_path(
15662 &mut self,
15663 _: &zed_actions::workspace::CopyPath,
15664 _window: &mut Window,
15665 cx: &mut Context<Self>,
15666 ) {
15667 if let Some(path) = self.target_file_abs_path(cx) {
15668 if let Some(path) = path.to_str() {
15669 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15670 }
15671 }
15672 }
15673
15674 pub fn copy_relative_path(
15675 &mut self,
15676 _: &zed_actions::workspace::CopyRelativePath,
15677 _window: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) {
15680 if let Some(path) = self.target_file_path(cx) {
15681 if let Some(path) = path.to_str() {
15682 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15683 }
15684 }
15685 }
15686
15687 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15688 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15689 buffer.read(cx).project_path(cx)
15690 } else {
15691 None
15692 }
15693 }
15694
15695 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15696 let _ = maybe!({
15697 let breakpoint_store = self.breakpoint_store.as_ref()?;
15698
15699 let Some((_, _, active_position)) =
15700 breakpoint_store.read(cx).active_position().cloned()
15701 else {
15702 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15703 return None;
15704 };
15705
15706 let snapshot = self
15707 .project
15708 .as_ref()?
15709 .read(cx)
15710 .buffer_for_id(active_position.buffer_id?, cx)?
15711 .read(cx)
15712 .snapshot();
15713
15714 for (id, ExcerptRange { context, .. }) in self
15715 .buffer
15716 .read(cx)
15717 .excerpts_for_buffer(active_position.buffer_id?, cx)
15718 {
15719 if context.start.cmp(&active_position, &snapshot).is_ge()
15720 || context.end.cmp(&active_position, &snapshot).is_lt()
15721 {
15722 continue;
15723 }
15724 let snapshot = self.buffer.read(cx).snapshot(cx);
15725 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15726
15727 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15728 self.go_to_line::<DebugCurrentRowHighlight>(
15729 multibuffer_anchor,
15730 Some(cx.theme().colors().editor_debugger_active_line_background),
15731 window,
15732 cx,
15733 );
15734
15735 cx.notify();
15736 }
15737
15738 Some(())
15739 });
15740 }
15741
15742 pub fn copy_file_name_without_extension(
15743 &mut self,
15744 _: &CopyFileNameWithoutExtension,
15745 _: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) {
15748 if let Some(file) = self.target_file(cx) {
15749 if let Some(file_stem) = file.path().file_stem() {
15750 if let Some(name) = file_stem.to_str() {
15751 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15752 }
15753 }
15754 }
15755 }
15756
15757 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15758 if let Some(file) = self.target_file(cx) {
15759 if let Some(file_name) = file.path().file_name() {
15760 if let Some(name) = file_name.to_str() {
15761 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15762 }
15763 }
15764 }
15765 }
15766
15767 pub fn toggle_git_blame(
15768 &mut self,
15769 _: &::git::Blame,
15770 window: &mut Window,
15771 cx: &mut Context<Self>,
15772 ) {
15773 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15774
15775 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15776 self.start_git_blame(true, window, cx);
15777 }
15778
15779 cx.notify();
15780 }
15781
15782 pub fn toggle_git_blame_inline(
15783 &mut self,
15784 _: &ToggleGitBlameInline,
15785 window: &mut Window,
15786 cx: &mut Context<Self>,
15787 ) {
15788 self.toggle_git_blame_inline_internal(true, window, cx);
15789 cx.notify();
15790 }
15791
15792 pub fn git_blame_inline_enabled(&self) -> bool {
15793 self.git_blame_inline_enabled
15794 }
15795
15796 pub fn toggle_selection_menu(
15797 &mut self,
15798 _: &ToggleSelectionMenu,
15799 _: &mut Window,
15800 cx: &mut Context<Self>,
15801 ) {
15802 self.show_selection_menu = self
15803 .show_selection_menu
15804 .map(|show_selections_menu| !show_selections_menu)
15805 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15806
15807 cx.notify();
15808 }
15809
15810 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15811 self.show_selection_menu
15812 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15813 }
15814
15815 fn start_git_blame(
15816 &mut self,
15817 user_triggered: bool,
15818 window: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 if let Some(project) = self.project.as_ref() {
15822 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15823 return;
15824 };
15825
15826 if buffer.read(cx).file().is_none() {
15827 return;
15828 }
15829
15830 let focused = self.focus_handle(cx).contains_focused(window, cx);
15831
15832 let project = project.clone();
15833 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15834 self.blame_subscription =
15835 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15836 self.blame = Some(blame);
15837 }
15838 }
15839
15840 fn toggle_git_blame_inline_internal(
15841 &mut self,
15842 user_triggered: bool,
15843 window: &mut Window,
15844 cx: &mut Context<Self>,
15845 ) {
15846 if self.git_blame_inline_enabled {
15847 self.git_blame_inline_enabled = false;
15848 self.show_git_blame_inline = false;
15849 self.show_git_blame_inline_delay_task.take();
15850 } else {
15851 self.git_blame_inline_enabled = true;
15852 self.start_git_blame_inline(user_triggered, window, cx);
15853 }
15854
15855 cx.notify();
15856 }
15857
15858 fn start_git_blame_inline(
15859 &mut self,
15860 user_triggered: bool,
15861 window: &mut Window,
15862 cx: &mut Context<Self>,
15863 ) {
15864 self.start_git_blame(user_triggered, window, cx);
15865
15866 if ProjectSettings::get_global(cx)
15867 .git
15868 .inline_blame_delay()
15869 .is_some()
15870 {
15871 self.start_inline_blame_timer(window, cx);
15872 } else {
15873 self.show_git_blame_inline = true
15874 }
15875 }
15876
15877 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15878 self.blame.as_ref()
15879 }
15880
15881 pub fn show_git_blame_gutter(&self) -> bool {
15882 self.show_git_blame_gutter
15883 }
15884
15885 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15886 self.show_git_blame_gutter && self.has_blame_entries(cx)
15887 }
15888
15889 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15890 self.show_git_blame_inline
15891 && (self.focus_handle.is_focused(window)
15892 || self
15893 .git_blame_inline_tooltip
15894 .as_ref()
15895 .and_then(|t| t.upgrade())
15896 .is_some())
15897 && !self.newest_selection_head_on_empty_line(cx)
15898 && self.has_blame_entries(cx)
15899 }
15900
15901 fn has_blame_entries(&self, cx: &App) -> bool {
15902 self.blame()
15903 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15904 }
15905
15906 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15907 let cursor_anchor = self.selections.newest_anchor().head();
15908
15909 let snapshot = self.buffer.read(cx).snapshot(cx);
15910 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15911
15912 snapshot.line_len(buffer_row) == 0
15913 }
15914
15915 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15916 let buffer_and_selection = maybe!({
15917 let selection = self.selections.newest::<Point>(cx);
15918 let selection_range = selection.range();
15919
15920 let multi_buffer = self.buffer().read(cx);
15921 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15922 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15923
15924 let (buffer, range, _) = if selection.reversed {
15925 buffer_ranges.first()
15926 } else {
15927 buffer_ranges.last()
15928 }?;
15929
15930 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15931 ..text::ToPoint::to_point(&range.end, &buffer).row;
15932 Some((
15933 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15934 selection,
15935 ))
15936 });
15937
15938 let Some((buffer, selection)) = buffer_and_selection else {
15939 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15940 };
15941
15942 let Some(project) = self.project.as_ref() else {
15943 return Task::ready(Err(anyhow!("editor does not have project")));
15944 };
15945
15946 project.update(cx, |project, cx| {
15947 project.get_permalink_to_line(&buffer, selection, cx)
15948 })
15949 }
15950
15951 pub fn copy_permalink_to_line(
15952 &mut self,
15953 _: &CopyPermalinkToLine,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 let permalink_task = self.get_permalink_to_line(cx);
15958 let workspace = self.workspace();
15959
15960 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15961 Ok(permalink) => {
15962 cx.update(|_, cx| {
15963 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15964 })
15965 .ok();
15966 }
15967 Err(err) => {
15968 let message = format!("Failed to copy permalink: {err}");
15969
15970 Err::<(), anyhow::Error>(err).log_err();
15971
15972 if let Some(workspace) = workspace {
15973 workspace
15974 .update_in(cx, |workspace, _, cx| {
15975 struct CopyPermalinkToLine;
15976
15977 workspace.show_toast(
15978 Toast::new(
15979 NotificationId::unique::<CopyPermalinkToLine>(),
15980 message,
15981 ),
15982 cx,
15983 )
15984 })
15985 .ok();
15986 }
15987 }
15988 })
15989 .detach();
15990 }
15991
15992 pub fn copy_file_location(
15993 &mut self,
15994 _: &CopyFileLocation,
15995 _: &mut Window,
15996 cx: &mut Context<Self>,
15997 ) {
15998 let selection = self.selections.newest::<Point>(cx).start.row + 1;
15999 if let Some(file) = self.target_file(cx) {
16000 if let Some(path) = file.path().to_str() {
16001 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16002 }
16003 }
16004 }
16005
16006 pub fn open_permalink_to_line(
16007 &mut self,
16008 _: &OpenPermalinkToLine,
16009 window: &mut Window,
16010 cx: &mut Context<Self>,
16011 ) {
16012 let permalink_task = self.get_permalink_to_line(cx);
16013 let workspace = self.workspace();
16014
16015 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16016 Ok(permalink) => {
16017 cx.update(|_, cx| {
16018 cx.open_url(permalink.as_ref());
16019 })
16020 .ok();
16021 }
16022 Err(err) => {
16023 let message = format!("Failed to open permalink: {err}");
16024
16025 Err::<(), anyhow::Error>(err).log_err();
16026
16027 if let Some(workspace) = workspace {
16028 workspace
16029 .update(cx, |workspace, cx| {
16030 struct OpenPermalinkToLine;
16031
16032 workspace.show_toast(
16033 Toast::new(
16034 NotificationId::unique::<OpenPermalinkToLine>(),
16035 message,
16036 ),
16037 cx,
16038 )
16039 })
16040 .ok();
16041 }
16042 }
16043 })
16044 .detach();
16045 }
16046
16047 pub fn insert_uuid_v4(
16048 &mut self,
16049 _: &InsertUuidV4,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 self.insert_uuid(UuidVersion::V4, window, cx);
16054 }
16055
16056 pub fn insert_uuid_v7(
16057 &mut self,
16058 _: &InsertUuidV7,
16059 window: &mut Window,
16060 cx: &mut Context<Self>,
16061 ) {
16062 self.insert_uuid(UuidVersion::V7, window, cx);
16063 }
16064
16065 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16067 self.transact(window, cx, |this, window, cx| {
16068 let edits = this
16069 .selections
16070 .all::<Point>(cx)
16071 .into_iter()
16072 .map(|selection| {
16073 let uuid = match version {
16074 UuidVersion::V4 => uuid::Uuid::new_v4(),
16075 UuidVersion::V7 => uuid::Uuid::now_v7(),
16076 };
16077
16078 (selection.range(), uuid.to_string())
16079 });
16080 this.edit(edits, cx);
16081 this.refresh_inline_completion(true, false, window, cx);
16082 });
16083 }
16084
16085 pub fn open_selections_in_multibuffer(
16086 &mut self,
16087 _: &OpenSelectionsInMultibuffer,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 let multibuffer = self.buffer.read(cx);
16092
16093 let Some(buffer) = multibuffer.as_singleton() else {
16094 return;
16095 };
16096
16097 let Some(workspace) = self.workspace() else {
16098 return;
16099 };
16100
16101 let locations = self
16102 .selections
16103 .disjoint_anchors()
16104 .iter()
16105 .map(|range| Location {
16106 buffer: buffer.clone(),
16107 range: range.start.text_anchor..range.end.text_anchor,
16108 })
16109 .collect::<Vec<_>>();
16110
16111 let title = multibuffer.title(cx).to_string();
16112
16113 cx.spawn_in(window, async move |_, cx| {
16114 workspace.update_in(cx, |workspace, window, cx| {
16115 Self::open_locations_in_multibuffer(
16116 workspace,
16117 locations,
16118 format!("Selections for '{title}'"),
16119 false,
16120 MultibufferSelectionMode::All,
16121 window,
16122 cx,
16123 );
16124 })
16125 })
16126 .detach();
16127 }
16128
16129 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16130 /// last highlight added will be used.
16131 ///
16132 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16133 pub fn highlight_rows<T: 'static>(
16134 &mut self,
16135 range: Range<Anchor>,
16136 color: Hsla,
16137 should_autoscroll: bool,
16138 cx: &mut Context<Self>,
16139 ) {
16140 let snapshot = self.buffer().read(cx).snapshot(cx);
16141 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16142 let ix = row_highlights.binary_search_by(|highlight| {
16143 Ordering::Equal
16144 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16145 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16146 });
16147
16148 if let Err(mut ix) = ix {
16149 let index = post_inc(&mut self.highlight_order);
16150
16151 // If this range intersects with the preceding highlight, then merge it with
16152 // the preceding highlight. Otherwise insert a new highlight.
16153 let mut merged = false;
16154 if ix > 0 {
16155 let prev_highlight = &mut row_highlights[ix - 1];
16156 if prev_highlight
16157 .range
16158 .end
16159 .cmp(&range.start, &snapshot)
16160 .is_ge()
16161 {
16162 ix -= 1;
16163 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16164 prev_highlight.range.end = range.end;
16165 }
16166 merged = true;
16167 prev_highlight.index = index;
16168 prev_highlight.color = color;
16169 prev_highlight.should_autoscroll = should_autoscroll;
16170 }
16171 }
16172
16173 if !merged {
16174 row_highlights.insert(
16175 ix,
16176 RowHighlight {
16177 range: range.clone(),
16178 index,
16179 color,
16180 should_autoscroll,
16181 },
16182 );
16183 }
16184
16185 // If any of the following highlights intersect with this one, merge them.
16186 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16187 let highlight = &row_highlights[ix];
16188 if next_highlight
16189 .range
16190 .start
16191 .cmp(&highlight.range.end, &snapshot)
16192 .is_le()
16193 {
16194 if next_highlight
16195 .range
16196 .end
16197 .cmp(&highlight.range.end, &snapshot)
16198 .is_gt()
16199 {
16200 row_highlights[ix].range.end = next_highlight.range.end;
16201 }
16202 row_highlights.remove(ix + 1);
16203 } else {
16204 break;
16205 }
16206 }
16207 }
16208 }
16209
16210 /// Remove any highlighted row ranges of the given type that intersect the
16211 /// given ranges.
16212 pub fn remove_highlighted_rows<T: 'static>(
16213 &mut self,
16214 ranges_to_remove: Vec<Range<Anchor>>,
16215 cx: &mut Context<Self>,
16216 ) {
16217 let snapshot = self.buffer().read(cx).snapshot(cx);
16218 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16219 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16220 row_highlights.retain(|highlight| {
16221 while let Some(range_to_remove) = ranges_to_remove.peek() {
16222 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16223 Ordering::Less | Ordering::Equal => {
16224 ranges_to_remove.next();
16225 }
16226 Ordering::Greater => {
16227 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16228 Ordering::Less | Ordering::Equal => {
16229 return false;
16230 }
16231 Ordering::Greater => break,
16232 }
16233 }
16234 }
16235 }
16236
16237 true
16238 })
16239 }
16240
16241 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16242 pub fn clear_row_highlights<T: 'static>(&mut self) {
16243 self.highlighted_rows.remove(&TypeId::of::<T>());
16244 }
16245
16246 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16247 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16248 self.highlighted_rows
16249 .get(&TypeId::of::<T>())
16250 .map_or(&[] as &[_], |vec| vec.as_slice())
16251 .iter()
16252 .map(|highlight| (highlight.range.clone(), highlight.color))
16253 }
16254
16255 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16256 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16257 /// Allows to ignore certain kinds of highlights.
16258 pub fn highlighted_display_rows(
16259 &self,
16260 window: &mut Window,
16261 cx: &mut App,
16262 ) -> BTreeMap<DisplayRow, LineHighlight> {
16263 let snapshot = self.snapshot(window, cx);
16264 let mut used_highlight_orders = HashMap::default();
16265 self.highlighted_rows
16266 .iter()
16267 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16268 .fold(
16269 BTreeMap::<DisplayRow, LineHighlight>::new(),
16270 |mut unique_rows, highlight| {
16271 let start = highlight.range.start.to_display_point(&snapshot);
16272 let end = highlight.range.end.to_display_point(&snapshot);
16273 let start_row = start.row().0;
16274 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16275 && end.column() == 0
16276 {
16277 end.row().0.saturating_sub(1)
16278 } else {
16279 end.row().0
16280 };
16281 for row in start_row..=end_row {
16282 let used_index =
16283 used_highlight_orders.entry(row).or_insert(highlight.index);
16284 if highlight.index >= *used_index {
16285 *used_index = highlight.index;
16286 unique_rows.insert(DisplayRow(row), highlight.color.into());
16287 }
16288 }
16289 unique_rows
16290 },
16291 )
16292 }
16293
16294 pub fn highlighted_display_row_for_autoscroll(
16295 &self,
16296 snapshot: &DisplaySnapshot,
16297 ) -> Option<DisplayRow> {
16298 self.highlighted_rows
16299 .values()
16300 .flat_map(|highlighted_rows| highlighted_rows.iter())
16301 .filter_map(|highlight| {
16302 if highlight.should_autoscroll {
16303 Some(highlight.range.start.to_display_point(snapshot).row())
16304 } else {
16305 None
16306 }
16307 })
16308 .min()
16309 }
16310
16311 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16312 self.highlight_background::<SearchWithinRange>(
16313 ranges,
16314 |colors| colors.editor_document_highlight_read_background,
16315 cx,
16316 )
16317 }
16318
16319 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16320 self.breadcrumb_header = Some(new_header);
16321 }
16322
16323 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16324 self.clear_background_highlights::<SearchWithinRange>(cx);
16325 }
16326
16327 pub fn highlight_background<T: 'static>(
16328 &mut self,
16329 ranges: &[Range<Anchor>],
16330 color_fetcher: fn(&ThemeColors) -> Hsla,
16331 cx: &mut Context<Self>,
16332 ) {
16333 self.background_highlights
16334 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16335 self.scrollbar_marker_state.dirty = true;
16336 cx.notify();
16337 }
16338
16339 pub fn clear_background_highlights<T: 'static>(
16340 &mut self,
16341 cx: &mut Context<Self>,
16342 ) -> Option<BackgroundHighlight> {
16343 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16344 if !text_highlights.1.is_empty() {
16345 self.scrollbar_marker_state.dirty = true;
16346 cx.notify();
16347 }
16348 Some(text_highlights)
16349 }
16350
16351 pub fn highlight_gutter<T: 'static>(
16352 &mut self,
16353 ranges: &[Range<Anchor>],
16354 color_fetcher: fn(&App) -> Hsla,
16355 cx: &mut Context<Self>,
16356 ) {
16357 self.gutter_highlights
16358 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16359 cx.notify();
16360 }
16361
16362 pub fn clear_gutter_highlights<T: 'static>(
16363 &mut self,
16364 cx: &mut Context<Self>,
16365 ) -> Option<GutterHighlight> {
16366 cx.notify();
16367 self.gutter_highlights.remove(&TypeId::of::<T>())
16368 }
16369
16370 #[cfg(feature = "test-support")]
16371 pub fn all_text_background_highlights(
16372 &self,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16376 let snapshot = self.snapshot(window, cx);
16377 let buffer = &snapshot.buffer_snapshot;
16378 let start = buffer.anchor_before(0);
16379 let end = buffer.anchor_after(buffer.len());
16380 let theme = cx.theme().colors();
16381 self.background_highlights_in_range(start..end, &snapshot, theme)
16382 }
16383
16384 #[cfg(feature = "test-support")]
16385 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16386 let snapshot = self.buffer().read(cx).snapshot(cx);
16387
16388 let highlights = self
16389 .background_highlights
16390 .get(&TypeId::of::<items::BufferSearchHighlights>());
16391
16392 if let Some((_color, ranges)) = highlights {
16393 ranges
16394 .iter()
16395 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16396 .collect_vec()
16397 } else {
16398 vec![]
16399 }
16400 }
16401
16402 fn document_highlights_for_position<'a>(
16403 &'a self,
16404 position: Anchor,
16405 buffer: &'a MultiBufferSnapshot,
16406 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16407 let read_highlights = self
16408 .background_highlights
16409 .get(&TypeId::of::<DocumentHighlightRead>())
16410 .map(|h| &h.1);
16411 let write_highlights = self
16412 .background_highlights
16413 .get(&TypeId::of::<DocumentHighlightWrite>())
16414 .map(|h| &h.1);
16415 let left_position = position.bias_left(buffer);
16416 let right_position = position.bias_right(buffer);
16417 read_highlights
16418 .into_iter()
16419 .chain(write_highlights)
16420 .flat_map(move |ranges| {
16421 let start_ix = match ranges.binary_search_by(|probe| {
16422 let cmp = probe.end.cmp(&left_position, buffer);
16423 if cmp.is_ge() {
16424 Ordering::Greater
16425 } else {
16426 Ordering::Less
16427 }
16428 }) {
16429 Ok(i) | Err(i) => i,
16430 };
16431
16432 ranges[start_ix..]
16433 .iter()
16434 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16435 })
16436 }
16437
16438 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16439 self.background_highlights
16440 .get(&TypeId::of::<T>())
16441 .map_or(false, |(_, highlights)| !highlights.is_empty())
16442 }
16443
16444 pub fn background_highlights_in_range(
16445 &self,
16446 search_range: Range<Anchor>,
16447 display_snapshot: &DisplaySnapshot,
16448 theme: &ThemeColors,
16449 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16450 let mut results = Vec::new();
16451 for (color_fetcher, ranges) in self.background_highlights.values() {
16452 let color = color_fetcher(theme);
16453 let start_ix = match ranges.binary_search_by(|probe| {
16454 let cmp = probe
16455 .end
16456 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16457 if cmp.is_gt() {
16458 Ordering::Greater
16459 } else {
16460 Ordering::Less
16461 }
16462 }) {
16463 Ok(i) | Err(i) => i,
16464 };
16465 for range in &ranges[start_ix..] {
16466 if range
16467 .start
16468 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16469 .is_ge()
16470 {
16471 break;
16472 }
16473
16474 let start = range.start.to_display_point(display_snapshot);
16475 let end = range.end.to_display_point(display_snapshot);
16476 results.push((start..end, color))
16477 }
16478 }
16479 results
16480 }
16481
16482 pub fn background_highlight_row_ranges<T: 'static>(
16483 &self,
16484 search_range: Range<Anchor>,
16485 display_snapshot: &DisplaySnapshot,
16486 count: usize,
16487 ) -> Vec<RangeInclusive<DisplayPoint>> {
16488 let mut results = Vec::new();
16489 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16490 return vec![];
16491 };
16492
16493 let start_ix = match ranges.binary_search_by(|probe| {
16494 let cmp = probe
16495 .end
16496 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16497 if cmp.is_gt() {
16498 Ordering::Greater
16499 } else {
16500 Ordering::Less
16501 }
16502 }) {
16503 Ok(i) | Err(i) => i,
16504 };
16505 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16506 if let (Some(start_display), Some(end_display)) = (start, end) {
16507 results.push(
16508 start_display.to_display_point(display_snapshot)
16509 ..=end_display.to_display_point(display_snapshot),
16510 );
16511 }
16512 };
16513 let mut start_row: Option<Point> = None;
16514 let mut end_row: Option<Point> = None;
16515 if ranges.len() > count {
16516 return Vec::new();
16517 }
16518 for range in &ranges[start_ix..] {
16519 if range
16520 .start
16521 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16522 .is_ge()
16523 {
16524 break;
16525 }
16526 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16527 if let Some(current_row) = &end_row {
16528 if end.row == current_row.row {
16529 continue;
16530 }
16531 }
16532 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16533 if start_row.is_none() {
16534 assert_eq!(end_row, None);
16535 start_row = Some(start);
16536 end_row = Some(end);
16537 continue;
16538 }
16539 if let Some(current_end) = end_row.as_mut() {
16540 if start.row > current_end.row + 1 {
16541 push_region(start_row, end_row);
16542 start_row = Some(start);
16543 end_row = Some(end);
16544 } else {
16545 // Merge two hunks.
16546 *current_end = end;
16547 }
16548 } else {
16549 unreachable!();
16550 }
16551 }
16552 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16553 push_region(start_row, end_row);
16554 results
16555 }
16556
16557 pub fn gutter_highlights_in_range(
16558 &self,
16559 search_range: Range<Anchor>,
16560 display_snapshot: &DisplaySnapshot,
16561 cx: &App,
16562 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16563 let mut results = Vec::new();
16564 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16565 let color = color_fetcher(cx);
16566 let start_ix = match ranges.binary_search_by(|probe| {
16567 let cmp = probe
16568 .end
16569 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16570 if cmp.is_gt() {
16571 Ordering::Greater
16572 } else {
16573 Ordering::Less
16574 }
16575 }) {
16576 Ok(i) | Err(i) => i,
16577 };
16578 for range in &ranges[start_ix..] {
16579 if range
16580 .start
16581 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16582 .is_ge()
16583 {
16584 break;
16585 }
16586
16587 let start = range.start.to_display_point(display_snapshot);
16588 let end = range.end.to_display_point(display_snapshot);
16589 results.push((start..end, color))
16590 }
16591 }
16592 results
16593 }
16594
16595 /// Get the text ranges corresponding to the redaction query
16596 pub fn redacted_ranges(
16597 &self,
16598 search_range: Range<Anchor>,
16599 display_snapshot: &DisplaySnapshot,
16600 cx: &App,
16601 ) -> Vec<Range<DisplayPoint>> {
16602 display_snapshot
16603 .buffer_snapshot
16604 .redacted_ranges(search_range, |file| {
16605 if let Some(file) = file {
16606 file.is_private()
16607 && EditorSettings::get(
16608 Some(SettingsLocation {
16609 worktree_id: file.worktree_id(cx),
16610 path: file.path().as_ref(),
16611 }),
16612 cx,
16613 )
16614 .redact_private_values
16615 } else {
16616 false
16617 }
16618 })
16619 .map(|range| {
16620 range.start.to_display_point(display_snapshot)
16621 ..range.end.to_display_point(display_snapshot)
16622 })
16623 .collect()
16624 }
16625
16626 pub fn highlight_text<T: 'static>(
16627 &mut self,
16628 ranges: Vec<Range<Anchor>>,
16629 style: HighlightStyle,
16630 cx: &mut Context<Self>,
16631 ) {
16632 self.display_map.update(cx, |map, _| {
16633 map.highlight_text(TypeId::of::<T>(), ranges, style)
16634 });
16635 cx.notify();
16636 }
16637
16638 pub(crate) fn highlight_inlays<T: 'static>(
16639 &mut self,
16640 highlights: Vec<InlayHighlight>,
16641 style: HighlightStyle,
16642 cx: &mut Context<Self>,
16643 ) {
16644 self.display_map.update(cx, |map, _| {
16645 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16646 });
16647 cx.notify();
16648 }
16649
16650 pub fn text_highlights<'a, T: 'static>(
16651 &'a self,
16652 cx: &'a App,
16653 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16654 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16655 }
16656
16657 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16658 let cleared = self
16659 .display_map
16660 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16661 if cleared {
16662 cx.notify();
16663 }
16664 }
16665
16666 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16667 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16668 && self.focus_handle.is_focused(window)
16669 }
16670
16671 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16672 self.show_cursor_when_unfocused = is_enabled;
16673 cx.notify();
16674 }
16675
16676 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16677 cx.notify();
16678 }
16679
16680 fn on_buffer_event(
16681 &mut self,
16682 multibuffer: &Entity<MultiBuffer>,
16683 event: &multi_buffer::Event,
16684 window: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) {
16687 match event {
16688 multi_buffer::Event::Edited {
16689 singleton_buffer_edited,
16690 edited_buffer: buffer_edited,
16691 } => {
16692 self.scrollbar_marker_state.dirty = true;
16693 self.active_indent_guides_state.dirty = true;
16694 self.refresh_active_diagnostics(cx);
16695 self.refresh_code_actions(window, cx);
16696 if self.has_active_inline_completion() {
16697 self.update_visible_inline_completion(window, cx);
16698 }
16699 if let Some(buffer) = buffer_edited {
16700 let buffer_id = buffer.read(cx).remote_id();
16701 if !self.registered_buffers.contains_key(&buffer_id) {
16702 if let Some(project) = self.project.as_ref() {
16703 project.update(cx, |project, cx| {
16704 self.registered_buffers.insert(
16705 buffer_id,
16706 project.register_buffer_with_language_servers(&buffer, cx),
16707 );
16708 })
16709 }
16710 }
16711 }
16712 cx.emit(EditorEvent::BufferEdited);
16713 cx.emit(SearchEvent::MatchesInvalidated);
16714 if *singleton_buffer_edited {
16715 if let Some(project) = &self.project {
16716 #[allow(clippy::mutable_key_type)]
16717 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16718 multibuffer
16719 .all_buffers()
16720 .into_iter()
16721 .filter_map(|buffer| {
16722 buffer.update(cx, |buffer, cx| {
16723 let language = buffer.language()?;
16724 let should_discard = project.update(cx, |project, cx| {
16725 project.is_local()
16726 && !project.has_language_servers_for(buffer, cx)
16727 });
16728 should_discard.not().then_some(language.clone())
16729 })
16730 })
16731 .collect::<HashSet<_>>()
16732 });
16733 if !languages_affected.is_empty() {
16734 self.refresh_inlay_hints(
16735 InlayHintRefreshReason::BufferEdited(languages_affected),
16736 cx,
16737 );
16738 }
16739 }
16740 }
16741
16742 let Some(project) = &self.project else { return };
16743 let (telemetry, is_via_ssh) = {
16744 let project = project.read(cx);
16745 let telemetry = project.client().telemetry().clone();
16746 let is_via_ssh = project.is_via_ssh();
16747 (telemetry, is_via_ssh)
16748 };
16749 refresh_linked_ranges(self, window, cx);
16750 telemetry.log_edit_event("editor", is_via_ssh);
16751 }
16752 multi_buffer::Event::ExcerptsAdded {
16753 buffer,
16754 predecessor,
16755 excerpts,
16756 } => {
16757 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16758 let buffer_id = buffer.read(cx).remote_id();
16759 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16760 if let Some(project) = &self.project {
16761 get_uncommitted_diff_for_buffer(
16762 project,
16763 [buffer.clone()],
16764 self.buffer.clone(),
16765 cx,
16766 )
16767 .detach();
16768 }
16769 }
16770 cx.emit(EditorEvent::ExcerptsAdded {
16771 buffer: buffer.clone(),
16772 predecessor: *predecessor,
16773 excerpts: excerpts.clone(),
16774 });
16775 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16776 }
16777 multi_buffer::Event::ExcerptsRemoved { ids } => {
16778 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16779 let buffer = self.buffer.read(cx);
16780 self.registered_buffers
16781 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16782 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16783 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16784 }
16785 multi_buffer::Event::ExcerptsEdited {
16786 excerpt_ids,
16787 buffer_ids,
16788 } => {
16789 self.display_map.update(cx, |map, cx| {
16790 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16791 });
16792 cx.emit(EditorEvent::ExcerptsEdited {
16793 ids: excerpt_ids.clone(),
16794 })
16795 }
16796 multi_buffer::Event::ExcerptsExpanded { ids } => {
16797 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16798 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16799 }
16800 multi_buffer::Event::Reparsed(buffer_id) => {
16801 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16802 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16803
16804 cx.emit(EditorEvent::Reparsed(*buffer_id));
16805 }
16806 multi_buffer::Event::DiffHunksToggled => {
16807 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16808 }
16809 multi_buffer::Event::LanguageChanged(buffer_id) => {
16810 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16811 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16812 cx.emit(EditorEvent::Reparsed(*buffer_id));
16813 cx.notify();
16814 }
16815 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16816 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16817 multi_buffer::Event::FileHandleChanged
16818 | multi_buffer::Event::Reloaded
16819 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16820 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16821 multi_buffer::Event::DiagnosticsUpdated => {
16822 self.refresh_active_diagnostics(cx);
16823 self.refresh_inline_diagnostics(true, window, cx);
16824 self.scrollbar_marker_state.dirty = true;
16825 cx.notify();
16826 }
16827 _ => {}
16828 };
16829 }
16830
16831 fn on_display_map_changed(
16832 &mut self,
16833 _: Entity<DisplayMap>,
16834 _: &mut Window,
16835 cx: &mut Context<Self>,
16836 ) {
16837 cx.notify();
16838 }
16839
16840 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16841 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16842 self.update_edit_prediction_settings(cx);
16843 self.refresh_inline_completion(true, false, window, cx);
16844 self.refresh_inlay_hints(
16845 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16846 self.selections.newest_anchor().head(),
16847 &self.buffer.read(cx).snapshot(cx),
16848 cx,
16849 )),
16850 cx,
16851 );
16852
16853 let old_cursor_shape = self.cursor_shape;
16854
16855 {
16856 let editor_settings = EditorSettings::get_global(cx);
16857 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16858 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16859 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16860 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
16861 }
16862
16863 if old_cursor_shape != self.cursor_shape {
16864 cx.emit(EditorEvent::CursorShapeChanged);
16865 }
16866
16867 let project_settings = ProjectSettings::get_global(cx);
16868 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16869
16870 if self.mode == EditorMode::Full {
16871 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16872 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16873 if self.show_inline_diagnostics != show_inline_diagnostics {
16874 self.show_inline_diagnostics = show_inline_diagnostics;
16875 self.refresh_inline_diagnostics(false, window, cx);
16876 }
16877
16878 if self.git_blame_inline_enabled != inline_blame_enabled {
16879 self.toggle_git_blame_inline_internal(false, window, cx);
16880 }
16881 }
16882
16883 cx.notify();
16884 }
16885
16886 pub fn set_searchable(&mut self, searchable: bool) {
16887 self.searchable = searchable;
16888 }
16889
16890 pub fn searchable(&self) -> bool {
16891 self.searchable
16892 }
16893
16894 fn open_proposed_changes_editor(
16895 &mut self,
16896 _: &OpenProposedChangesEditor,
16897 window: &mut Window,
16898 cx: &mut Context<Self>,
16899 ) {
16900 let Some(workspace) = self.workspace() else {
16901 cx.propagate();
16902 return;
16903 };
16904
16905 let selections = self.selections.all::<usize>(cx);
16906 let multi_buffer = self.buffer.read(cx);
16907 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16908 let mut new_selections_by_buffer = HashMap::default();
16909 for selection in selections {
16910 for (buffer, range, _) in
16911 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16912 {
16913 let mut range = range.to_point(buffer);
16914 range.start.column = 0;
16915 range.end.column = buffer.line_len(range.end.row);
16916 new_selections_by_buffer
16917 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16918 .or_insert(Vec::new())
16919 .push(range)
16920 }
16921 }
16922
16923 let proposed_changes_buffers = new_selections_by_buffer
16924 .into_iter()
16925 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16926 .collect::<Vec<_>>();
16927 let proposed_changes_editor = cx.new(|cx| {
16928 ProposedChangesEditor::new(
16929 "Proposed changes",
16930 proposed_changes_buffers,
16931 self.project.clone(),
16932 window,
16933 cx,
16934 )
16935 });
16936
16937 window.defer(cx, move |window, cx| {
16938 workspace.update(cx, |workspace, cx| {
16939 workspace.active_pane().update(cx, |pane, cx| {
16940 pane.add_item(
16941 Box::new(proposed_changes_editor),
16942 true,
16943 true,
16944 None,
16945 window,
16946 cx,
16947 );
16948 });
16949 });
16950 });
16951 }
16952
16953 pub fn open_excerpts_in_split(
16954 &mut self,
16955 _: &OpenExcerptsSplit,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 self.open_excerpts_common(None, true, window, cx)
16960 }
16961
16962 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16963 self.open_excerpts_common(None, false, window, cx)
16964 }
16965
16966 fn open_excerpts_common(
16967 &mut self,
16968 jump_data: Option<JumpData>,
16969 split: bool,
16970 window: &mut Window,
16971 cx: &mut Context<Self>,
16972 ) {
16973 let Some(workspace) = self.workspace() else {
16974 cx.propagate();
16975 return;
16976 };
16977
16978 if self.buffer.read(cx).is_singleton() {
16979 cx.propagate();
16980 return;
16981 }
16982
16983 let mut new_selections_by_buffer = HashMap::default();
16984 match &jump_data {
16985 Some(JumpData::MultiBufferPoint {
16986 excerpt_id,
16987 position,
16988 anchor,
16989 line_offset_from_top,
16990 }) => {
16991 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16992 if let Some(buffer) = multi_buffer_snapshot
16993 .buffer_id_for_excerpt(*excerpt_id)
16994 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
16995 {
16996 let buffer_snapshot = buffer.read(cx).snapshot();
16997 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
16998 language::ToPoint::to_point(anchor, &buffer_snapshot)
16999 } else {
17000 buffer_snapshot.clip_point(*position, Bias::Left)
17001 };
17002 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17003 new_selections_by_buffer.insert(
17004 buffer,
17005 (
17006 vec![jump_to_offset..jump_to_offset],
17007 Some(*line_offset_from_top),
17008 ),
17009 );
17010 }
17011 }
17012 Some(JumpData::MultiBufferRow {
17013 row,
17014 line_offset_from_top,
17015 }) => {
17016 let point = MultiBufferPoint::new(row.0, 0);
17017 if let Some((buffer, buffer_point, _)) =
17018 self.buffer.read(cx).point_to_buffer_point(point, cx)
17019 {
17020 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17021 new_selections_by_buffer
17022 .entry(buffer)
17023 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17024 .0
17025 .push(buffer_offset..buffer_offset)
17026 }
17027 }
17028 None => {
17029 let selections = self.selections.all::<usize>(cx);
17030 let multi_buffer = self.buffer.read(cx);
17031 for selection in selections {
17032 for (snapshot, range, _, anchor) in multi_buffer
17033 .snapshot(cx)
17034 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17035 {
17036 if let Some(anchor) = anchor {
17037 // selection is in a deleted hunk
17038 let Some(buffer_id) = anchor.buffer_id else {
17039 continue;
17040 };
17041 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17042 continue;
17043 };
17044 let offset = text::ToOffset::to_offset(
17045 &anchor.text_anchor,
17046 &buffer_handle.read(cx).snapshot(),
17047 );
17048 let range = offset..offset;
17049 new_selections_by_buffer
17050 .entry(buffer_handle)
17051 .or_insert((Vec::new(), None))
17052 .0
17053 .push(range)
17054 } else {
17055 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17056 else {
17057 continue;
17058 };
17059 new_selections_by_buffer
17060 .entry(buffer_handle)
17061 .or_insert((Vec::new(), None))
17062 .0
17063 .push(range)
17064 }
17065 }
17066 }
17067 }
17068 }
17069
17070 if new_selections_by_buffer.is_empty() {
17071 return;
17072 }
17073
17074 // We defer the pane interaction because we ourselves are a workspace item
17075 // and activating a new item causes the pane to call a method on us reentrantly,
17076 // which panics if we're on the stack.
17077 window.defer(cx, move |window, cx| {
17078 workspace.update(cx, |workspace, cx| {
17079 let pane = if split {
17080 workspace.adjacent_pane(window, cx)
17081 } else {
17082 workspace.active_pane().clone()
17083 };
17084
17085 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17086 let editor = buffer
17087 .read(cx)
17088 .file()
17089 .is_none()
17090 .then(|| {
17091 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17092 // so `workspace.open_project_item` will never find them, always opening a new editor.
17093 // Instead, we try to activate the existing editor in the pane first.
17094 let (editor, pane_item_index) =
17095 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17096 let editor = item.downcast::<Editor>()?;
17097 let singleton_buffer =
17098 editor.read(cx).buffer().read(cx).as_singleton()?;
17099 if singleton_buffer == buffer {
17100 Some((editor, i))
17101 } else {
17102 None
17103 }
17104 })?;
17105 pane.update(cx, |pane, cx| {
17106 pane.activate_item(pane_item_index, true, true, window, cx)
17107 });
17108 Some(editor)
17109 })
17110 .flatten()
17111 .unwrap_or_else(|| {
17112 workspace.open_project_item::<Self>(
17113 pane.clone(),
17114 buffer,
17115 true,
17116 true,
17117 window,
17118 cx,
17119 )
17120 });
17121
17122 editor.update(cx, |editor, cx| {
17123 let autoscroll = match scroll_offset {
17124 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17125 None => Autoscroll::newest(),
17126 };
17127 let nav_history = editor.nav_history.take();
17128 editor.change_selections(Some(autoscroll), window, cx, |s| {
17129 s.select_ranges(ranges);
17130 });
17131 editor.nav_history = nav_history;
17132 });
17133 }
17134 })
17135 });
17136 }
17137
17138 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17139 let snapshot = self.buffer.read(cx).read(cx);
17140 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17141 Some(
17142 ranges
17143 .iter()
17144 .map(move |range| {
17145 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17146 })
17147 .collect(),
17148 )
17149 }
17150
17151 fn selection_replacement_ranges(
17152 &self,
17153 range: Range<OffsetUtf16>,
17154 cx: &mut App,
17155 ) -> Vec<Range<OffsetUtf16>> {
17156 let selections = self.selections.all::<OffsetUtf16>(cx);
17157 let newest_selection = selections
17158 .iter()
17159 .max_by_key(|selection| selection.id)
17160 .unwrap();
17161 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17162 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17163 let snapshot = self.buffer.read(cx).read(cx);
17164 selections
17165 .into_iter()
17166 .map(|mut selection| {
17167 selection.start.0 =
17168 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17169 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17170 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17171 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17172 })
17173 .collect()
17174 }
17175
17176 fn report_editor_event(
17177 &self,
17178 event_type: &'static str,
17179 file_extension: Option<String>,
17180 cx: &App,
17181 ) {
17182 if cfg!(any(test, feature = "test-support")) {
17183 return;
17184 }
17185
17186 let Some(project) = &self.project else { return };
17187
17188 // If None, we are in a file without an extension
17189 let file = self
17190 .buffer
17191 .read(cx)
17192 .as_singleton()
17193 .and_then(|b| b.read(cx).file());
17194 let file_extension = file_extension.or(file
17195 .as_ref()
17196 .and_then(|file| Path::new(file.file_name(cx)).extension())
17197 .and_then(|e| e.to_str())
17198 .map(|a| a.to_string()));
17199
17200 let vim_mode = cx
17201 .global::<SettingsStore>()
17202 .raw_user_settings()
17203 .get("vim_mode")
17204 == Some(&serde_json::Value::Bool(true));
17205
17206 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17207 let copilot_enabled = edit_predictions_provider
17208 == language::language_settings::EditPredictionProvider::Copilot;
17209 let copilot_enabled_for_language = self
17210 .buffer
17211 .read(cx)
17212 .language_settings(cx)
17213 .show_edit_predictions;
17214
17215 let project = project.read(cx);
17216 telemetry::event!(
17217 event_type,
17218 file_extension,
17219 vim_mode,
17220 copilot_enabled,
17221 copilot_enabled_for_language,
17222 edit_predictions_provider,
17223 is_via_ssh = project.is_via_ssh(),
17224 );
17225 }
17226
17227 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17228 /// with each line being an array of {text, highlight} objects.
17229 fn copy_highlight_json(
17230 &mut self,
17231 _: &CopyHighlightJson,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 #[derive(Serialize)]
17236 struct Chunk<'a> {
17237 text: String,
17238 highlight: Option<&'a str>,
17239 }
17240
17241 let snapshot = self.buffer.read(cx).snapshot(cx);
17242 let range = self
17243 .selected_text_range(false, window, cx)
17244 .and_then(|selection| {
17245 if selection.range.is_empty() {
17246 None
17247 } else {
17248 Some(selection.range)
17249 }
17250 })
17251 .unwrap_or_else(|| 0..snapshot.len());
17252
17253 let chunks = snapshot.chunks(range, true);
17254 let mut lines = Vec::new();
17255 let mut line: VecDeque<Chunk> = VecDeque::new();
17256
17257 let Some(style) = self.style.as_ref() else {
17258 return;
17259 };
17260
17261 for chunk in chunks {
17262 let highlight = chunk
17263 .syntax_highlight_id
17264 .and_then(|id| id.name(&style.syntax));
17265 let mut chunk_lines = chunk.text.split('\n').peekable();
17266 while let Some(text) = chunk_lines.next() {
17267 let mut merged_with_last_token = false;
17268 if let Some(last_token) = line.back_mut() {
17269 if last_token.highlight == highlight {
17270 last_token.text.push_str(text);
17271 merged_with_last_token = true;
17272 }
17273 }
17274
17275 if !merged_with_last_token {
17276 line.push_back(Chunk {
17277 text: text.into(),
17278 highlight,
17279 });
17280 }
17281
17282 if chunk_lines.peek().is_some() {
17283 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17284 line.pop_front();
17285 }
17286 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17287 line.pop_back();
17288 }
17289
17290 lines.push(mem::take(&mut line));
17291 }
17292 }
17293 }
17294
17295 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17296 return;
17297 };
17298 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17299 }
17300
17301 pub fn open_context_menu(
17302 &mut self,
17303 _: &OpenContextMenu,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 self.request_autoscroll(Autoscroll::newest(), cx);
17308 let position = self.selections.newest_display(cx).start;
17309 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17310 }
17311
17312 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17313 &self.inlay_hint_cache
17314 }
17315
17316 pub fn replay_insert_event(
17317 &mut self,
17318 text: &str,
17319 relative_utf16_range: Option<Range<isize>>,
17320 window: &mut Window,
17321 cx: &mut Context<Self>,
17322 ) {
17323 if !self.input_enabled {
17324 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17325 return;
17326 }
17327 if let Some(relative_utf16_range) = relative_utf16_range {
17328 let selections = self.selections.all::<OffsetUtf16>(cx);
17329 self.change_selections(None, window, cx, |s| {
17330 let new_ranges = selections.into_iter().map(|range| {
17331 let start = OffsetUtf16(
17332 range
17333 .head()
17334 .0
17335 .saturating_add_signed(relative_utf16_range.start),
17336 );
17337 let end = OffsetUtf16(
17338 range
17339 .head()
17340 .0
17341 .saturating_add_signed(relative_utf16_range.end),
17342 );
17343 start..end
17344 });
17345 s.select_ranges(new_ranges);
17346 });
17347 }
17348
17349 self.handle_input(text, window, cx);
17350 }
17351
17352 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17353 let Some(provider) = self.semantics_provider.as_ref() else {
17354 return false;
17355 };
17356
17357 let mut supports = false;
17358 self.buffer().update(cx, |this, cx| {
17359 this.for_each_buffer(|buffer| {
17360 supports |= provider.supports_inlay_hints(buffer, cx);
17361 });
17362 });
17363
17364 supports
17365 }
17366
17367 pub fn is_focused(&self, window: &Window) -> bool {
17368 self.focus_handle.is_focused(window)
17369 }
17370
17371 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17372 cx.emit(EditorEvent::Focused);
17373
17374 if let Some(descendant) = self
17375 .last_focused_descendant
17376 .take()
17377 .and_then(|descendant| descendant.upgrade())
17378 {
17379 window.focus(&descendant);
17380 } else {
17381 if let Some(blame) = self.blame.as_ref() {
17382 blame.update(cx, GitBlame::focus)
17383 }
17384
17385 self.blink_manager.update(cx, BlinkManager::enable);
17386 self.show_cursor_names(window, cx);
17387 self.buffer.update(cx, |buffer, cx| {
17388 buffer.finalize_last_transaction(cx);
17389 if self.leader_peer_id.is_none() {
17390 buffer.set_active_selections(
17391 &self.selections.disjoint_anchors(),
17392 self.selections.line_mode,
17393 self.cursor_shape,
17394 cx,
17395 );
17396 }
17397 });
17398 }
17399 }
17400
17401 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17402 cx.emit(EditorEvent::FocusedIn)
17403 }
17404
17405 fn handle_focus_out(
17406 &mut self,
17407 event: FocusOutEvent,
17408 _window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) {
17411 if event.blurred != self.focus_handle {
17412 self.last_focused_descendant = Some(event.blurred);
17413 }
17414 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17415 }
17416
17417 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17418 self.blink_manager.update(cx, BlinkManager::disable);
17419 self.buffer
17420 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17421
17422 if let Some(blame) = self.blame.as_ref() {
17423 blame.update(cx, GitBlame::blur)
17424 }
17425 if !self.hover_state.focused(window, cx) {
17426 hide_hover(self, cx);
17427 }
17428 if !self
17429 .context_menu
17430 .borrow()
17431 .as_ref()
17432 .is_some_and(|context_menu| context_menu.focused(window, cx))
17433 {
17434 self.hide_context_menu(window, cx);
17435 }
17436 self.discard_inline_completion(false, cx);
17437 cx.emit(EditorEvent::Blurred);
17438 cx.notify();
17439 }
17440
17441 pub fn register_action<A: Action>(
17442 &mut self,
17443 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17444 ) -> Subscription {
17445 let id = self.next_editor_action_id.post_inc();
17446 let listener = Arc::new(listener);
17447 self.editor_actions.borrow_mut().insert(
17448 id,
17449 Box::new(move |window, _| {
17450 let listener = listener.clone();
17451 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17452 let action = action.downcast_ref().unwrap();
17453 if phase == DispatchPhase::Bubble {
17454 listener(action, window, cx)
17455 }
17456 })
17457 }),
17458 );
17459
17460 let editor_actions = self.editor_actions.clone();
17461 Subscription::new(move || {
17462 editor_actions.borrow_mut().remove(&id);
17463 })
17464 }
17465
17466 pub fn file_header_size(&self) -> u32 {
17467 FILE_HEADER_HEIGHT
17468 }
17469
17470 pub fn restore(
17471 &mut self,
17472 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 let workspace = self.workspace();
17477 let project = self.project.as_ref();
17478 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17479 let mut tasks = Vec::new();
17480 for (buffer_id, changes) in revert_changes {
17481 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17482 buffer.update(cx, |buffer, cx| {
17483 buffer.edit(
17484 changes
17485 .into_iter()
17486 .map(|(range, text)| (range, text.to_string())),
17487 None,
17488 cx,
17489 );
17490 });
17491
17492 if let Some(project) =
17493 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17494 {
17495 project.update(cx, |project, cx| {
17496 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17497 })
17498 }
17499 }
17500 }
17501 tasks
17502 });
17503 cx.spawn_in(window, async move |_, cx| {
17504 for (buffer, task) in save_tasks {
17505 let result = task.await;
17506 if result.is_err() {
17507 let Some(path) = buffer
17508 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17509 .ok()
17510 else {
17511 continue;
17512 };
17513 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17514 let Some(task) = cx
17515 .update_window_entity(&workspace, |workspace, window, cx| {
17516 workspace
17517 .open_path_preview(path, None, false, false, false, window, cx)
17518 })
17519 .ok()
17520 else {
17521 continue;
17522 };
17523 task.await.log_err();
17524 }
17525 }
17526 }
17527 })
17528 .detach();
17529 self.change_selections(None, window, cx, |selections| selections.refresh());
17530 }
17531
17532 pub fn to_pixel_point(
17533 &self,
17534 source: multi_buffer::Anchor,
17535 editor_snapshot: &EditorSnapshot,
17536 window: &mut Window,
17537 ) -> Option<gpui::Point<Pixels>> {
17538 let source_point = source.to_display_point(editor_snapshot);
17539 self.display_to_pixel_point(source_point, editor_snapshot, window)
17540 }
17541
17542 pub fn display_to_pixel_point(
17543 &self,
17544 source: DisplayPoint,
17545 editor_snapshot: &EditorSnapshot,
17546 window: &mut Window,
17547 ) -> Option<gpui::Point<Pixels>> {
17548 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17549 let text_layout_details = self.text_layout_details(window);
17550 let scroll_top = text_layout_details
17551 .scroll_anchor
17552 .scroll_position(editor_snapshot)
17553 .y;
17554
17555 if source.row().as_f32() < scroll_top.floor() {
17556 return None;
17557 }
17558 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17559 let source_y = line_height * (source.row().as_f32() - scroll_top);
17560 Some(gpui::Point::new(source_x, source_y))
17561 }
17562
17563 pub fn has_visible_completions_menu(&self) -> bool {
17564 !self.edit_prediction_preview_is_active()
17565 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17566 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17567 })
17568 }
17569
17570 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17571 self.addons
17572 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17573 }
17574
17575 pub fn unregister_addon<T: Addon>(&mut self) {
17576 self.addons.remove(&std::any::TypeId::of::<T>());
17577 }
17578
17579 pub fn addon<T: Addon>(&self) -> Option<&T> {
17580 let type_id = std::any::TypeId::of::<T>();
17581 self.addons
17582 .get(&type_id)
17583 .and_then(|item| item.to_any().downcast_ref::<T>())
17584 }
17585
17586 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17587 let text_layout_details = self.text_layout_details(window);
17588 let style = &text_layout_details.editor_style;
17589 let font_id = window.text_system().resolve_font(&style.text.font());
17590 let font_size = style.text.font_size.to_pixels(window.rem_size());
17591 let line_height = style.text.line_height_in_pixels(window.rem_size());
17592 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17593
17594 gpui::Size::new(em_width, line_height)
17595 }
17596
17597 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17598 self.load_diff_task.clone()
17599 }
17600
17601 fn read_metadata_from_db(
17602 &mut self,
17603 item_id: u64,
17604 workspace_id: WorkspaceId,
17605 window: &mut Window,
17606 cx: &mut Context<Editor>,
17607 ) {
17608 if self.is_singleton(cx)
17609 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17610 {
17611 let buffer_snapshot = OnceCell::new();
17612
17613 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17614 if !folds.is_empty() {
17615 let snapshot =
17616 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17617 self.fold_ranges(
17618 folds
17619 .into_iter()
17620 .map(|(start, end)| {
17621 snapshot.clip_offset(start, Bias::Left)
17622 ..snapshot.clip_offset(end, Bias::Right)
17623 })
17624 .collect(),
17625 false,
17626 window,
17627 cx,
17628 );
17629 }
17630 }
17631
17632 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17633 if !selections.is_empty() {
17634 let snapshot =
17635 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17636 self.change_selections(None, window, cx, |s| {
17637 s.select_ranges(selections.into_iter().map(|(start, end)| {
17638 snapshot.clip_offset(start, Bias::Left)
17639 ..snapshot.clip_offset(end, Bias::Right)
17640 }));
17641 });
17642 }
17643 };
17644 }
17645
17646 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17647 }
17648}
17649
17650fn insert_extra_newline_brackets(
17651 buffer: &MultiBufferSnapshot,
17652 range: Range<usize>,
17653 language: &language::LanguageScope,
17654) -> bool {
17655 let leading_whitespace_len = buffer
17656 .reversed_chars_at(range.start)
17657 .take_while(|c| c.is_whitespace() && *c != '\n')
17658 .map(|c| c.len_utf8())
17659 .sum::<usize>();
17660 let trailing_whitespace_len = buffer
17661 .chars_at(range.end)
17662 .take_while(|c| c.is_whitespace() && *c != '\n')
17663 .map(|c| c.len_utf8())
17664 .sum::<usize>();
17665 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17666
17667 language.brackets().any(|(pair, enabled)| {
17668 let pair_start = pair.start.trim_end();
17669 let pair_end = pair.end.trim_start();
17670
17671 enabled
17672 && pair.newline
17673 && buffer.contains_str_at(range.end, pair_end)
17674 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17675 })
17676}
17677
17678fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17679 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17680 [(buffer, range, _)] => (*buffer, range.clone()),
17681 _ => return false,
17682 };
17683 let pair = {
17684 let mut result: Option<BracketMatch> = None;
17685
17686 for pair in buffer
17687 .all_bracket_ranges(range.clone())
17688 .filter(move |pair| {
17689 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17690 })
17691 {
17692 let len = pair.close_range.end - pair.open_range.start;
17693
17694 if let Some(existing) = &result {
17695 let existing_len = existing.close_range.end - existing.open_range.start;
17696 if len > existing_len {
17697 continue;
17698 }
17699 }
17700
17701 result = Some(pair);
17702 }
17703
17704 result
17705 };
17706 let Some(pair) = pair else {
17707 return false;
17708 };
17709 pair.newline_only
17710 && buffer
17711 .chars_for_range(pair.open_range.end..range.start)
17712 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17713 .all(|c| c.is_whitespace() && c != '\n')
17714}
17715
17716fn get_uncommitted_diff_for_buffer(
17717 project: &Entity<Project>,
17718 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17719 buffer: Entity<MultiBuffer>,
17720 cx: &mut App,
17721) -> Task<()> {
17722 let mut tasks = Vec::new();
17723 project.update(cx, |project, cx| {
17724 for buffer in buffers {
17725 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17726 }
17727 });
17728 cx.spawn(async move |cx| {
17729 let diffs = future::join_all(tasks).await;
17730 buffer
17731 .update(cx, |buffer, cx| {
17732 for diff in diffs.into_iter().flatten() {
17733 buffer.add_diff(diff, cx);
17734 }
17735 })
17736 .ok();
17737 })
17738}
17739
17740fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17741 let tab_size = tab_size.get() as usize;
17742 let mut width = offset;
17743
17744 for ch in text.chars() {
17745 width += if ch == '\t' {
17746 tab_size - (width % tab_size)
17747 } else {
17748 1
17749 };
17750 }
17751
17752 width - offset
17753}
17754
17755#[cfg(test)]
17756mod tests {
17757 use super::*;
17758
17759 #[test]
17760 fn test_string_size_with_expanded_tabs() {
17761 let nz = |val| NonZeroU32::new(val).unwrap();
17762 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17763 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17764 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17765 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17766 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17767 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17768 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17769 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17770 }
17771}
17772
17773/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17774struct WordBreakingTokenizer<'a> {
17775 input: &'a str,
17776}
17777
17778impl<'a> WordBreakingTokenizer<'a> {
17779 fn new(input: &'a str) -> Self {
17780 Self { input }
17781 }
17782}
17783
17784fn is_char_ideographic(ch: char) -> bool {
17785 use unicode_script::Script::*;
17786 use unicode_script::UnicodeScript;
17787 matches!(ch.script(), Han | Tangut | Yi)
17788}
17789
17790fn is_grapheme_ideographic(text: &str) -> bool {
17791 text.chars().any(is_char_ideographic)
17792}
17793
17794fn is_grapheme_whitespace(text: &str) -> bool {
17795 text.chars().any(|x| x.is_whitespace())
17796}
17797
17798fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17799 text.chars().next().map_or(false, |ch| {
17800 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17801 })
17802}
17803
17804#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17805enum WordBreakToken<'a> {
17806 Word { token: &'a str, grapheme_len: usize },
17807 InlineWhitespace { token: &'a str, grapheme_len: usize },
17808 Newline,
17809}
17810
17811impl<'a> Iterator for WordBreakingTokenizer<'a> {
17812 /// Yields a span, the count of graphemes in the token, and whether it was
17813 /// whitespace. Note that it also breaks at word boundaries.
17814 type Item = WordBreakToken<'a>;
17815
17816 fn next(&mut self) -> Option<Self::Item> {
17817 use unicode_segmentation::UnicodeSegmentation;
17818 if self.input.is_empty() {
17819 return None;
17820 }
17821
17822 let mut iter = self.input.graphemes(true).peekable();
17823 let mut offset = 0;
17824 let mut grapheme_len = 0;
17825 if let Some(first_grapheme) = iter.next() {
17826 let is_newline = first_grapheme == "\n";
17827 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17828 offset += first_grapheme.len();
17829 grapheme_len += 1;
17830 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17831 if let Some(grapheme) = iter.peek().copied() {
17832 if should_stay_with_preceding_ideograph(grapheme) {
17833 offset += grapheme.len();
17834 grapheme_len += 1;
17835 }
17836 }
17837 } else {
17838 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17839 let mut next_word_bound = words.peek().copied();
17840 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17841 next_word_bound = words.next();
17842 }
17843 while let Some(grapheme) = iter.peek().copied() {
17844 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17845 break;
17846 };
17847 if is_grapheme_whitespace(grapheme) != is_whitespace
17848 || (grapheme == "\n") != is_newline
17849 {
17850 break;
17851 };
17852 offset += grapheme.len();
17853 grapheme_len += 1;
17854 iter.next();
17855 }
17856 }
17857 let token = &self.input[..offset];
17858 self.input = &self.input[offset..];
17859 if token == "\n" {
17860 Some(WordBreakToken::Newline)
17861 } else if is_whitespace {
17862 Some(WordBreakToken::InlineWhitespace {
17863 token,
17864 grapheme_len,
17865 })
17866 } else {
17867 Some(WordBreakToken::Word {
17868 token,
17869 grapheme_len,
17870 })
17871 }
17872 } else {
17873 None
17874 }
17875 }
17876}
17877
17878#[test]
17879fn test_word_breaking_tokenizer() {
17880 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17881 ("", &[]),
17882 (" ", &[whitespace(" ", 2)]),
17883 ("Ʒ", &[word("Ʒ", 1)]),
17884 ("Ǽ", &[word("Ǽ", 1)]),
17885 ("⋑", &[word("⋑", 1)]),
17886 ("⋑⋑", &[word("⋑⋑", 2)]),
17887 (
17888 "原理,进而",
17889 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17890 ),
17891 (
17892 "hello world",
17893 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17894 ),
17895 (
17896 "hello, world",
17897 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17898 ),
17899 (
17900 " hello world",
17901 &[
17902 whitespace(" ", 2),
17903 word("hello", 5),
17904 whitespace(" ", 1),
17905 word("world", 5),
17906 ],
17907 ),
17908 (
17909 "这是什么 \n 钢笔",
17910 &[
17911 word("这", 1),
17912 word("是", 1),
17913 word("什", 1),
17914 word("么", 1),
17915 whitespace(" ", 1),
17916 newline(),
17917 whitespace(" ", 1),
17918 word("钢", 1),
17919 word("笔", 1),
17920 ],
17921 ),
17922 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17923 ];
17924
17925 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17926 WordBreakToken::Word {
17927 token,
17928 grapheme_len,
17929 }
17930 }
17931
17932 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17933 WordBreakToken::InlineWhitespace {
17934 token,
17935 grapheme_len,
17936 }
17937 }
17938
17939 fn newline() -> WordBreakToken<'static> {
17940 WordBreakToken::Newline
17941 }
17942
17943 for (input, result) in tests {
17944 assert_eq!(
17945 WordBreakingTokenizer::new(input)
17946 .collect::<Vec<_>>()
17947 .as_slice(),
17948 *result,
17949 );
17950 }
17951}
17952
17953fn wrap_with_prefix(
17954 line_prefix: String,
17955 unwrapped_text: String,
17956 wrap_column: usize,
17957 tab_size: NonZeroU32,
17958 preserve_existing_whitespace: bool,
17959) -> String {
17960 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17961 let mut wrapped_text = String::new();
17962 let mut current_line = line_prefix.clone();
17963
17964 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17965 let mut current_line_len = line_prefix_len;
17966 let mut in_whitespace = false;
17967 for token in tokenizer {
17968 let have_preceding_whitespace = in_whitespace;
17969 match token {
17970 WordBreakToken::Word {
17971 token,
17972 grapheme_len,
17973 } => {
17974 in_whitespace = false;
17975 if current_line_len + grapheme_len > wrap_column
17976 && current_line_len != line_prefix_len
17977 {
17978 wrapped_text.push_str(current_line.trim_end());
17979 wrapped_text.push('\n');
17980 current_line.truncate(line_prefix.len());
17981 current_line_len = line_prefix_len;
17982 }
17983 current_line.push_str(token);
17984 current_line_len += grapheme_len;
17985 }
17986 WordBreakToken::InlineWhitespace {
17987 mut token,
17988 mut grapheme_len,
17989 } => {
17990 in_whitespace = true;
17991 if have_preceding_whitespace && !preserve_existing_whitespace {
17992 continue;
17993 }
17994 if !preserve_existing_whitespace {
17995 token = " ";
17996 grapheme_len = 1;
17997 }
17998 if current_line_len + grapheme_len > wrap_column {
17999 wrapped_text.push_str(current_line.trim_end());
18000 wrapped_text.push('\n');
18001 current_line.truncate(line_prefix.len());
18002 current_line_len = line_prefix_len;
18003 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18004 current_line.push_str(token);
18005 current_line_len += grapheme_len;
18006 }
18007 }
18008 WordBreakToken::Newline => {
18009 in_whitespace = true;
18010 if preserve_existing_whitespace {
18011 wrapped_text.push_str(current_line.trim_end());
18012 wrapped_text.push('\n');
18013 current_line.truncate(line_prefix.len());
18014 current_line_len = line_prefix_len;
18015 } else if have_preceding_whitespace {
18016 continue;
18017 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18018 {
18019 wrapped_text.push_str(current_line.trim_end());
18020 wrapped_text.push('\n');
18021 current_line.truncate(line_prefix.len());
18022 current_line_len = line_prefix_len;
18023 } else if current_line_len != line_prefix_len {
18024 current_line.push(' ');
18025 current_line_len += 1;
18026 }
18027 }
18028 }
18029 }
18030
18031 if !current_line.is_empty() {
18032 wrapped_text.push_str(¤t_line);
18033 }
18034 wrapped_text
18035}
18036
18037#[test]
18038fn test_wrap_with_prefix() {
18039 assert_eq!(
18040 wrap_with_prefix(
18041 "# ".to_string(),
18042 "abcdefg".to_string(),
18043 4,
18044 NonZeroU32::new(4).unwrap(),
18045 false,
18046 ),
18047 "# abcdefg"
18048 );
18049 assert_eq!(
18050 wrap_with_prefix(
18051 "".to_string(),
18052 "\thello world".to_string(),
18053 8,
18054 NonZeroU32::new(4).unwrap(),
18055 false,
18056 ),
18057 "hello\nworld"
18058 );
18059 assert_eq!(
18060 wrap_with_prefix(
18061 "// ".to_string(),
18062 "xx \nyy zz aa bb cc".to_string(),
18063 12,
18064 NonZeroU32::new(4).unwrap(),
18065 false,
18066 ),
18067 "// xx yy zz\n// aa bb cc"
18068 );
18069 assert_eq!(
18070 wrap_with_prefix(
18071 String::new(),
18072 "这是什么 \n 钢笔".to_string(),
18073 3,
18074 NonZeroU32::new(4).unwrap(),
18075 false,
18076 ),
18077 "这是什\n么 钢\n笔"
18078 );
18079}
18080
18081pub trait CollaborationHub {
18082 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18083 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18084 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18085}
18086
18087impl CollaborationHub for Entity<Project> {
18088 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18089 self.read(cx).collaborators()
18090 }
18091
18092 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18093 self.read(cx).user_store().read(cx).participant_indices()
18094 }
18095
18096 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18097 let this = self.read(cx);
18098 let user_ids = this.collaborators().values().map(|c| c.user_id);
18099 this.user_store().read_with(cx, |user_store, cx| {
18100 user_store.participant_names(user_ids, cx)
18101 })
18102 }
18103}
18104
18105pub trait SemanticsProvider {
18106 fn hover(
18107 &self,
18108 buffer: &Entity<Buffer>,
18109 position: text::Anchor,
18110 cx: &mut App,
18111 ) -> Option<Task<Vec<project::Hover>>>;
18112
18113 fn inlay_hints(
18114 &self,
18115 buffer_handle: Entity<Buffer>,
18116 range: Range<text::Anchor>,
18117 cx: &mut App,
18118 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18119
18120 fn resolve_inlay_hint(
18121 &self,
18122 hint: InlayHint,
18123 buffer_handle: Entity<Buffer>,
18124 server_id: LanguageServerId,
18125 cx: &mut App,
18126 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18127
18128 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18129
18130 fn document_highlights(
18131 &self,
18132 buffer: &Entity<Buffer>,
18133 position: text::Anchor,
18134 cx: &mut App,
18135 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18136
18137 fn definitions(
18138 &self,
18139 buffer: &Entity<Buffer>,
18140 position: text::Anchor,
18141 kind: GotoDefinitionKind,
18142 cx: &mut App,
18143 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18144
18145 fn range_for_rename(
18146 &self,
18147 buffer: &Entity<Buffer>,
18148 position: text::Anchor,
18149 cx: &mut App,
18150 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18151
18152 fn perform_rename(
18153 &self,
18154 buffer: &Entity<Buffer>,
18155 position: text::Anchor,
18156 new_name: String,
18157 cx: &mut App,
18158 ) -> Option<Task<Result<ProjectTransaction>>>;
18159}
18160
18161pub trait CompletionProvider {
18162 fn completions(
18163 &self,
18164 excerpt_id: ExcerptId,
18165 buffer: &Entity<Buffer>,
18166 buffer_position: text::Anchor,
18167 trigger: CompletionContext,
18168 window: &mut Window,
18169 cx: &mut Context<Editor>,
18170 ) -> Task<Result<Option<Vec<Completion>>>>;
18171
18172 fn resolve_completions(
18173 &self,
18174 buffer: Entity<Buffer>,
18175 completion_indices: Vec<usize>,
18176 completions: Rc<RefCell<Box<[Completion]>>>,
18177 cx: &mut Context<Editor>,
18178 ) -> Task<Result<bool>>;
18179
18180 fn apply_additional_edits_for_completion(
18181 &self,
18182 _buffer: Entity<Buffer>,
18183 _completions: Rc<RefCell<Box<[Completion]>>>,
18184 _completion_index: usize,
18185 _push_to_history: bool,
18186 _cx: &mut Context<Editor>,
18187 ) -> Task<Result<Option<language::Transaction>>> {
18188 Task::ready(Ok(None))
18189 }
18190
18191 fn is_completion_trigger(
18192 &self,
18193 buffer: &Entity<Buffer>,
18194 position: language::Anchor,
18195 text: &str,
18196 trigger_in_words: bool,
18197 cx: &mut Context<Editor>,
18198 ) -> bool;
18199
18200 fn sort_completions(&self) -> bool {
18201 true
18202 }
18203
18204 fn filter_completions(&self) -> bool {
18205 true
18206 }
18207}
18208
18209pub trait CodeActionProvider {
18210 fn id(&self) -> Arc<str>;
18211
18212 fn code_actions(
18213 &self,
18214 buffer: &Entity<Buffer>,
18215 range: Range<text::Anchor>,
18216 window: &mut Window,
18217 cx: &mut App,
18218 ) -> Task<Result<Vec<CodeAction>>>;
18219
18220 fn apply_code_action(
18221 &self,
18222 buffer_handle: Entity<Buffer>,
18223 action: CodeAction,
18224 excerpt_id: ExcerptId,
18225 push_to_history: bool,
18226 window: &mut Window,
18227 cx: &mut App,
18228 ) -> Task<Result<ProjectTransaction>>;
18229}
18230
18231impl CodeActionProvider for Entity<Project> {
18232 fn id(&self) -> Arc<str> {
18233 "project".into()
18234 }
18235
18236 fn code_actions(
18237 &self,
18238 buffer: &Entity<Buffer>,
18239 range: Range<text::Anchor>,
18240 _window: &mut Window,
18241 cx: &mut App,
18242 ) -> Task<Result<Vec<CodeAction>>> {
18243 self.update(cx, |project, cx| {
18244 let code_lens = project.code_lens(buffer, range.clone(), cx);
18245 let code_actions = project.code_actions(buffer, range, None, cx);
18246 cx.background_spawn(async move {
18247 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18248 Ok(code_lens
18249 .context("code lens fetch")?
18250 .into_iter()
18251 .chain(code_actions.context("code action fetch")?)
18252 .collect())
18253 })
18254 })
18255 }
18256
18257 fn apply_code_action(
18258 &self,
18259 buffer_handle: Entity<Buffer>,
18260 action: CodeAction,
18261 _excerpt_id: ExcerptId,
18262 push_to_history: bool,
18263 _window: &mut Window,
18264 cx: &mut App,
18265 ) -> Task<Result<ProjectTransaction>> {
18266 self.update(cx, |project, cx| {
18267 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18268 })
18269 }
18270}
18271
18272fn snippet_completions(
18273 project: &Project,
18274 buffer: &Entity<Buffer>,
18275 buffer_position: text::Anchor,
18276 cx: &mut App,
18277) -> Task<Result<Vec<Completion>>> {
18278 let language = buffer.read(cx).language_at(buffer_position);
18279 let language_name = language.as_ref().map(|language| language.lsp_id());
18280 let snippet_store = project.snippets().read(cx);
18281 let snippets = snippet_store.snippets_for(language_name, cx);
18282
18283 if snippets.is_empty() {
18284 return Task::ready(Ok(vec![]));
18285 }
18286 let snapshot = buffer.read(cx).text_snapshot();
18287 let chars: String = snapshot
18288 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18289 .collect();
18290
18291 let scope = language.map(|language| language.default_scope());
18292 let executor = cx.background_executor().clone();
18293
18294 cx.background_spawn(async move {
18295 let classifier = CharClassifier::new(scope).for_completion(true);
18296 let mut last_word = chars
18297 .chars()
18298 .take_while(|c| classifier.is_word(*c))
18299 .collect::<String>();
18300 last_word = last_word.chars().rev().collect();
18301
18302 if last_word.is_empty() {
18303 return Ok(vec![]);
18304 }
18305
18306 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18307 let to_lsp = |point: &text::Anchor| {
18308 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18309 point_to_lsp(end)
18310 };
18311 let lsp_end = to_lsp(&buffer_position);
18312
18313 let candidates = snippets
18314 .iter()
18315 .enumerate()
18316 .flat_map(|(ix, snippet)| {
18317 snippet
18318 .prefix
18319 .iter()
18320 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18321 })
18322 .collect::<Vec<StringMatchCandidate>>();
18323
18324 let mut matches = fuzzy::match_strings(
18325 &candidates,
18326 &last_word,
18327 last_word.chars().any(|c| c.is_uppercase()),
18328 100,
18329 &Default::default(),
18330 executor,
18331 )
18332 .await;
18333
18334 // Remove all candidates where the query's start does not match the start of any word in the candidate
18335 if let Some(query_start) = last_word.chars().next() {
18336 matches.retain(|string_match| {
18337 split_words(&string_match.string).any(|word| {
18338 // Check that the first codepoint of the word as lowercase matches the first
18339 // codepoint of the query as lowercase
18340 word.chars()
18341 .flat_map(|codepoint| codepoint.to_lowercase())
18342 .zip(query_start.to_lowercase())
18343 .all(|(word_cp, query_cp)| word_cp == query_cp)
18344 })
18345 });
18346 }
18347
18348 let matched_strings = matches
18349 .into_iter()
18350 .map(|m| m.string)
18351 .collect::<HashSet<_>>();
18352
18353 let result: Vec<Completion> = snippets
18354 .into_iter()
18355 .filter_map(|snippet| {
18356 let matching_prefix = snippet
18357 .prefix
18358 .iter()
18359 .find(|prefix| matched_strings.contains(*prefix))?;
18360 let start = as_offset - last_word.len();
18361 let start = snapshot.anchor_before(start);
18362 let range = start..buffer_position;
18363 let lsp_start = to_lsp(&start);
18364 let lsp_range = lsp::Range {
18365 start: lsp_start,
18366 end: lsp_end,
18367 };
18368 Some(Completion {
18369 old_range: range,
18370 new_text: snippet.body.clone(),
18371 source: CompletionSource::Lsp {
18372 server_id: LanguageServerId(usize::MAX),
18373 resolved: true,
18374 lsp_completion: Box::new(lsp::CompletionItem {
18375 label: snippet.prefix.first().unwrap().clone(),
18376 kind: Some(CompletionItemKind::SNIPPET),
18377 label_details: snippet.description.as_ref().map(|description| {
18378 lsp::CompletionItemLabelDetails {
18379 detail: Some(description.clone()),
18380 description: None,
18381 }
18382 }),
18383 insert_text_format: Some(InsertTextFormat::SNIPPET),
18384 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18385 lsp::InsertReplaceEdit {
18386 new_text: snippet.body.clone(),
18387 insert: lsp_range,
18388 replace: lsp_range,
18389 },
18390 )),
18391 filter_text: Some(snippet.body.clone()),
18392 sort_text: Some(char::MAX.to_string()),
18393 ..lsp::CompletionItem::default()
18394 }),
18395 lsp_defaults: None,
18396 },
18397 label: CodeLabel {
18398 text: matching_prefix.clone(),
18399 runs: Vec::new(),
18400 filter_range: 0..matching_prefix.len(),
18401 },
18402 icon_path: None,
18403 documentation: snippet
18404 .description
18405 .clone()
18406 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18407 confirm: None,
18408 })
18409 })
18410 .collect();
18411
18412 Ok(result)
18413 })
18414}
18415
18416impl CompletionProvider for Entity<Project> {
18417 fn completions(
18418 &self,
18419 _excerpt_id: ExcerptId,
18420 buffer: &Entity<Buffer>,
18421 buffer_position: text::Anchor,
18422 options: CompletionContext,
18423 _window: &mut Window,
18424 cx: &mut Context<Editor>,
18425 ) -> Task<Result<Option<Vec<Completion>>>> {
18426 self.update(cx, |project, cx| {
18427 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18428 let project_completions = project.completions(buffer, buffer_position, options, cx);
18429 cx.background_spawn(async move {
18430 let snippets_completions = snippets.await?;
18431 match project_completions.await? {
18432 Some(mut completions) => {
18433 completions.extend(snippets_completions);
18434 Ok(Some(completions))
18435 }
18436 None => {
18437 if snippets_completions.is_empty() {
18438 Ok(None)
18439 } else {
18440 Ok(Some(snippets_completions))
18441 }
18442 }
18443 }
18444 })
18445 })
18446 }
18447
18448 fn resolve_completions(
18449 &self,
18450 buffer: Entity<Buffer>,
18451 completion_indices: Vec<usize>,
18452 completions: Rc<RefCell<Box<[Completion]>>>,
18453 cx: &mut Context<Editor>,
18454 ) -> Task<Result<bool>> {
18455 self.update(cx, |project, cx| {
18456 project.lsp_store().update(cx, |lsp_store, cx| {
18457 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18458 })
18459 })
18460 }
18461
18462 fn apply_additional_edits_for_completion(
18463 &self,
18464 buffer: Entity<Buffer>,
18465 completions: Rc<RefCell<Box<[Completion]>>>,
18466 completion_index: usize,
18467 push_to_history: bool,
18468 cx: &mut Context<Editor>,
18469 ) -> Task<Result<Option<language::Transaction>>> {
18470 self.update(cx, |project, cx| {
18471 project.lsp_store().update(cx, |lsp_store, cx| {
18472 lsp_store.apply_additional_edits_for_completion(
18473 buffer,
18474 completions,
18475 completion_index,
18476 push_to_history,
18477 cx,
18478 )
18479 })
18480 })
18481 }
18482
18483 fn is_completion_trigger(
18484 &self,
18485 buffer: &Entity<Buffer>,
18486 position: language::Anchor,
18487 text: &str,
18488 trigger_in_words: bool,
18489 cx: &mut Context<Editor>,
18490 ) -> bool {
18491 let mut chars = text.chars();
18492 let char = if let Some(char) = chars.next() {
18493 char
18494 } else {
18495 return false;
18496 };
18497 if chars.next().is_some() {
18498 return false;
18499 }
18500
18501 let buffer = buffer.read(cx);
18502 let snapshot = buffer.snapshot();
18503 if !snapshot.settings_at(position, cx).show_completions_on_input {
18504 return false;
18505 }
18506 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18507 if trigger_in_words && classifier.is_word(char) {
18508 return true;
18509 }
18510
18511 buffer.completion_triggers().contains(text)
18512 }
18513}
18514
18515impl SemanticsProvider for Entity<Project> {
18516 fn hover(
18517 &self,
18518 buffer: &Entity<Buffer>,
18519 position: text::Anchor,
18520 cx: &mut App,
18521 ) -> Option<Task<Vec<project::Hover>>> {
18522 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18523 }
18524
18525 fn document_highlights(
18526 &self,
18527 buffer: &Entity<Buffer>,
18528 position: text::Anchor,
18529 cx: &mut App,
18530 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18531 Some(self.update(cx, |project, cx| {
18532 project.document_highlights(buffer, position, cx)
18533 }))
18534 }
18535
18536 fn definitions(
18537 &self,
18538 buffer: &Entity<Buffer>,
18539 position: text::Anchor,
18540 kind: GotoDefinitionKind,
18541 cx: &mut App,
18542 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18543 Some(self.update(cx, |project, cx| match kind {
18544 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18545 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18546 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18547 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18548 }))
18549 }
18550
18551 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18552 // TODO: make this work for remote projects
18553 self.update(cx, |this, cx| {
18554 buffer.update(cx, |buffer, cx| {
18555 this.any_language_server_supports_inlay_hints(buffer, cx)
18556 })
18557 })
18558 }
18559
18560 fn inlay_hints(
18561 &self,
18562 buffer_handle: Entity<Buffer>,
18563 range: Range<text::Anchor>,
18564 cx: &mut App,
18565 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18566 Some(self.update(cx, |project, cx| {
18567 project.inlay_hints(buffer_handle, range, cx)
18568 }))
18569 }
18570
18571 fn resolve_inlay_hint(
18572 &self,
18573 hint: InlayHint,
18574 buffer_handle: Entity<Buffer>,
18575 server_id: LanguageServerId,
18576 cx: &mut App,
18577 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18578 Some(self.update(cx, |project, cx| {
18579 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18580 }))
18581 }
18582
18583 fn range_for_rename(
18584 &self,
18585 buffer: &Entity<Buffer>,
18586 position: text::Anchor,
18587 cx: &mut App,
18588 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18589 Some(self.update(cx, |project, cx| {
18590 let buffer = buffer.clone();
18591 let task = project.prepare_rename(buffer.clone(), position, cx);
18592 cx.spawn(async move |_, cx| {
18593 Ok(match task.await? {
18594 PrepareRenameResponse::Success(range) => Some(range),
18595 PrepareRenameResponse::InvalidPosition => None,
18596 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18597 // Fallback on using TreeSitter info to determine identifier range
18598 buffer.update(cx, |buffer, _| {
18599 let snapshot = buffer.snapshot();
18600 let (range, kind) = snapshot.surrounding_word(position);
18601 if kind != Some(CharKind::Word) {
18602 return None;
18603 }
18604 Some(
18605 snapshot.anchor_before(range.start)
18606 ..snapshot.anchor_after(range.end),
18607 )
18608 })?
18609 }
18610 })
18611 })
18612 }))
18613 }
18614
18615 fn perform_rename(
18616 &self,
18617 buffer: &Entity<Buffer>,
18618 position: text::Anchor,
18619 new_name: String,
18620 cx: &mut App,
18621 ) -> Option<Task<Result<ProjectTransaction>>> {
18622 Some(self.update(cx, |project, cx| {
18623 project.perform_rename(buffer.clone(), position, new_name, cx)
18624 }))
18625 }
18626}
18627
18628fn inlay_hint_settings(
18629 location: Anchor,
18630 snapshot: &MultiBufferSnapshot,
18631 cx: &mut Context<Editor>,
18632) -> InlayHintSettings {
18633 let file = snapshot.file_at(location);
18634 let language = snapshot.language_at(location).map(|l| l.name());
18635 language_settings(language, file, cx).inlay_hints
18636}
18637
18638fn consume_contiguous_rows(
18639 contiguous_row_selections: &mut Vec<Selection<Point>>,
18640 selection: &Selection<Point>,
18641 display_map: &DisplaySnapshot,
18642 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18643) -> (MultiBufferRow, MultiBufferRow) {
18644 contiguous_row_selections.push(selection.clone());
18645 let start_row = MultiBufferRow(selection.start.row);
18646 let mut end_row = ending_row(selection, display_map);
18647
18648 while let Some(next_selection) = selections.peek() {
18649 if next_selection.start.row <= end_row.0 {
18650 end_row = ending_row(next_selection, display_map);
18651 contiguous_row_selections.push(selections.next().unwrap().clone());
18652 } else {
18653 break;
18654 }
18655 }
18656 (start_row, end_row)
18657}
18658
18659fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18660 if next_selection.end.column > 0 || next_selection.is_empty() {
18661 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18662 } else {
18663 MultiBufferRow(next_selection.end.row)
18664 }
18665}
18666
18667impl EditorSnapshot {
18668 pub fn remote_selections_in_range<'a>(
18669 &'a self,
18670 range: &'a Range<Anchor>,
18671 collaboration_hub: &dyn CollaborationHub,
18672 cx: &'a App,
18673 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18674 let participant_names = collaboration_hub.user_names(cx);
18675 let participant_indices = collaboration_hub.user_participant_indices(cx);
18676 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18677 let collaborators_by_replica_id = collaborators_by_peer_id
18678 .iter()
18679 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18680 .collect::<HashMap<_, _>>();
18681 self.buffer_snapshot
18682 .selections_in_range(range, false)
18683 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18684 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18685 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18686 let user_name = participant_names.get(&collaborator.user_id).cloned();
18687 Some(RemoteSelection {
18688 replica_id,
18689 selection,
18690 cursor_shape,
18691 line_mode,
18692 participant_index,
18693 peer_id: collaborator.peer_id,
18694 user_name,
18695 })
18696 })
18697 }
18698
18699 pub fn hunks_for_ranges(
18700 &self,
18701 ranges: impl IntoIterator<Item = Range<Point>>,
18702 ) -> Vec<MultiBufferDiffHunk> {
18703 let mut hunks = Vec::new();
18704 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18705 HashMap::default();
18706 for query_range in ranges {
18707 let query_rows =
18708 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18709 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18710 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18711 ) {
18712 // Include deleted hunks that are adjacent to the query range, because
18713 // otherwise they would be missed.
18714 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18715 if hunk.status().is_deleted() {
18716 intersects_range |= hunk.row_range.start == query_rows.end;
18717 intersects_range |= hunk.row_range.end == query_rows.start;
18718 }
18719 if intersects_range {
18720 if !processed_buffer_rows
18721 .entry(hunk.buffer_id)
18722 .or_default()
18723 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18724 {
18725 continue;
18726 }
18727 hunks.push(hunk);
18728 }
18729 }
18730 }
18731
18732 hunks
18733 }
18734
18735 fn display_diff_hunks_for_rows<'a>(
18736 &'a self,
18737 display_rows: Range<DisplayRow>,
18738 folded_buffers: &'a HashSet<BufferId>,
18739 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18740 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18741 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18742
18743 self.buffer_snapshot
18744 .diff_hunks_in_range(buffer_start..buffer_end)
18745 .filter_map(|hunk| {
18746 if folded_buffers.contains(&hunk.buffer_id) {
18747 return None;
18748 }
18749
18750 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18751 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18752
18753 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18754 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18755
18756 let display_hunk = if hunk_display_start.column() != 0 {
18757 DisplayDiffHunk::Folded {
18758 display_row: hunk_display_start.row(),
18759 }
18760 } else {
18761 let mut end_row = hunk_display_end.row();
18762 if hunk_display_end.column() > 0 {
18763 end_row.0 += 1;
18764 }
18765 let is_created_file = hunk.is_created_file();
18766 DisplayDiffHunk::Unfolded {
18767 status: hunk.status(),
18768 diff_base_byte_range: hunk.diff_base_byte_range,
18769 display_row_range: hunk_display_start.row()..end_row,
18770 multi_buffer_range: Anchor::range_in_buffer(
18771 hunk.excerpt_id,
18772 hunk.buffer_id,
18773 hunk.buffer_range,
18774 ),
18775 is_created_file,
18776 }
18777 };
18778
18779 Some(display_hunk)
18780 })
18781 }
18782
18783 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18784 self.display_snapshot.buffer_snapshot.language_at(position)
18785 }
18786
18787 pub fn is_focused(&self) -> bool {
18788 self.is_focused
18789 }
18790
18791 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18792 self.placeholder_text.as_ref()
18793 }
18794
18795 pub fn scroll_position(&self) -> gpui::Point<f32> {
18796 self.scroll_anchor.scroll_position(&self.display_snapshot)
18797 }
18798
18799 fn gutter_dimensions(
18800 &self,
18801 font_id: FontId,
18802 font_size: Pixels,
18803 max_line_number_width: Pixels,
18804 cx: &App,
18805 ) -> Option<GutterDimensions> {
18806 if !self.show_gutter {
18807 return None;
18808 }
18809
18810 let descent = cx.text_system().descent(font_id, font_size);
18811 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18812 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18813
18814 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18815 matches!(
18816 ProjectSettings::get_global(cx).git.git_gutter,
18817 Some(GitGutterSetting::TrackedFiles)
18818 )
18819 });
18820 let gutter_settings = EditorSettings::get_global(cx).gutter;
18821 let show_line_numbers = self
18822 .show_line_numbers
18823 .unwrap_or(gutter_settings.line_numbers);
18824 let line_gutter_width = if show_line_numbers {
18825 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18826 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18827 max_line_number_width.max(min_width_for_number_on_gutter)
18828 } else {
18829 0.0.into()
18830 };
18831
18832 let show_code_actions = self
18833 .show_code_actions
18834 .unwrap_or(gutter_settings.code_actions);
18835
18836 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18837 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18838
18839 let git_blame_entries_width =
18840 self.git_blame_gutter_max_author_length
18841 .map(|max_author_length| {
18842 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18843
18844 /// The number of characters to dedicate to gaps and margins.
18845 const SPACING_WIDTH: usize = 4;
18846
18847 let max_char_count = max_author_length
18848 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18849 + ::git::SHORT_SHA_LENGTH
18850 + MAX_RELATIVE_TIMESTAMP.len()
18851 + SPACING_WIDTH;
18852
18853 em_advance * max_char_count
18854 });
18855
18856 let is_singleton = self.buffer_snapshot.is_singleton();
18857
18858 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18859 left_padding += if !is_singleton {
18860 em_width * 4.0
18861 } else if show_code_actions || show_runnables || show_breakpoints {
18862 em_width * 3.0
18863 } else if show_git_gutter && show_line_numbers {
18864 em_width * 2.0
18865 } else if show_git_gutter || show_line_numbers {
18866 em_width
18867 } else {
18868 px(0.)
18869 };
18870
18871 let shows_folds = is_singleton && gutter_settings.folds;
18872
18873 let right_padding = if shows_folds && show_line_numbers {
18874 em_width * 4.0
18875 } else if shows_folds || (!is_singleton && show_line_numbers) {
18876 em_width * 3.0
18877 } else if show_line_numbers {
18878 em_width
18879 } else {
18880 px(0.)
18881 };
18882
18883 Some(GutterDimensions {
18884 left_padding,
18885 right_padding,
18886 width: line_gutter_width + left_padding + right_padding,
18887 margin: -descent,
18888 git_blame_entries_width,
18889 })
18890 }
18891
18892 pub fn render_crease_toggle(
18893 &self,
18894 buffer_row: MultiBufferRow,
18895 row_contains_cursor: bool,
18896 editor: Entity<Editor>,
18897 window: &mut Window,
18898 cx: &mut App,
18899 ) -> Option<AnyElement> {
18900 let folded = self.is_line_folded(buffer_row);
18901 let mut is_foldable = false;
18902
18903 if let Some(crease) = self
18904 .crease_snapshot
18905 .query_row(buffer_row, &self.buffer_snapshot)
18906 {
18907 is_foldable = true;
18908 match crease {
18909 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18910 if let Some(render_toggle) = render_toggle {
18911 let toggle_callback =
18912 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18913 if folded {
18914 editor.update(cx, |editor, cx| {
18915 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18916 });
18917 } else {
18918 editor.update(cx, |editor, cx| {
18919 editor.unfold_at(
18920 &crate::UnfoldAt { buffer_row },
18921 window,
18922 cx,
18923 )
18924 });
18925 }
18926 });
18927 return Some((render_toggle)(
18928 buffer_row,
18929 folded,
18930 toggle_callback,
18931 window,
18932 cx,
18933 ));
18934 }
18935 }
18936 }
18937 }
18938
18939 is_foldable |= self.starts_indent(buffer_row);
18940
18941 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18942 Some(
18943 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18944 .toggle_state(folded)
18945 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18946 if folded {
18947 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18948 } else {
18949 this.fold_at(&FoldAt { buffer_row }, window, cx);
18950 }
18951 }))
18952 .into_any_element(),
18953 )
18954 } else {
18955 None
18956 }
18957 }
18958
18959 pub fn render_crease_trailer(
18960 &self,
18961 buffer_row: MultiBufferRow,
18962 window: &mut Window,
18963 cx: &mut App,
18964 ) -> Option<AnyElement> {
18965 let folded = self.is_line_folded(buffer_row);
18966 if let Crease::Inline { render_trailer, .. } = self
18967 .crease_snapshot
18968 .query_row(buffer_row, &self.buffer_snapshot)?
18969 {
18970 let render_trailer = render_trailer.as_ref()?;
18971 Some(render_trailer(buffer_row, folded, window, cx))
18972 } else {
18973 None
18974 }
18975 }
18976}
18977
18978impl Deref for EditorSnapshot {
18979 type Target = DisplaySnapshot;
18980
18981 fn deref(&self) -> &Self::Target {
18982 &self.display_snapshot
18983 }
18984}
18985
18986#[derive(Clone, Debug, PartialEq, Eq)]
18987pub enum EditorEvent {
18988 InputIgnored {
18989 text: Arc<str>,
18990 },
18991 InputHandled {
18992 utf16_range_to_replace: Option<Range<isize>>,
18993 text: Arc<str>,
18994 },
18995 ExcerptsAdded {
18996 buffer: Entity<Buffer>,
18997 predecessor: ExcerptId,
18998 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
18999 },
19000 ExcerptsRemoved {
19001 ids: Vec<ExcerptId>,
19002 },
19003 BufferFoldToggled {
19004 ids: Vec<ExcerptId>,
19005 folded: bool,
19006 },
19007 ExcerptsEdited {
19008 ids: Vec<ExcerptId>,
19009 },
19010 ExcerptsExpanded {
19011 ids: Vec<ExcerptId>,
19012 },
19013 BufferEdited,
19014 Edited {
19015 transaction_id: clock::Lamport,
19016 },
19017 Reparsed(BufferId),
19018 Focused,
19019 FocusedIn,
19020 Blurred,
19021 DirtyChanged,
19022 Saved,
19023 TitleChanged,
19024 DiffBaseChanged,
19025 SelectionsChanged {
19026 local: bool,
19027 },
19028 ScrollPositionChanged {
19029 local: bool,
19030 autoscroll: bool,
19031 },
19032 Closed,
19033 TransactionUndone {
19034 transaction_id: clock::Lamport,
19035 },
19036 TransactionBegun {
19037 transaction_id: clock::Lamport,
19038 },
19039 Reloaded,
19040 CursorShapeChanged,
19041 PushedToNavHistory {
19042 anchor: Anchor,
19043 is_deactivate: bool,
19044 },
19045}
19046
19047impl EventEmitter<EditorEvent> for Editor {}
19048
19049impl Focusable for Editor {
19050 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19051 self.focus_handle.clone()
19052 }
19053}
19054
19055impl Render for Editor {
19056 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19057 let settings = ThemeSettings::get_global(cx);
19058
19059 let mut text_style = match self.mode {
19060 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19061 color: cx.theme().colors().editor_foreground,
19062 font_family: settings.ui_font.family.clone(),
19063 font_features: settings.ui_font.features.clone(),
19064 font_fallbacks: settings.ui_font.fallbacks.clone(),
19065 font_size: rems(0.875).into(),
19066 font_weight: settings.ui_font.weight,
19067 line_height: relative(settings.buffer_line_height.value()),
19068 ..Default::default()
19069 },
19070 EditorMode::Full => TextStyle {
19071 color: cx.theme().colors().editor_foreground,
19072 font_family: settings.buffer_font.family.clone(),
19073 font_features: settings.buffer_font.features.clone(),
19074 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19075 font_size: settings.buffer_font_size(cx).into(),
19076 font_weight: settings.buffer_font.weight,
19077 line_height: relative(settings.buffer_line_height.value()),
19078 ..Default::default()
19079 },
19080 };
19081 if let Some(text_style_refinement) = &self.text_style_refinement {
19082 text_style.refine(text_style_refinement)
19083 }
19084
19085 let background = match self.mode {
19086 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19087 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19088 EditorMode::Full => cx.theme().colors().editor_background,
19089 };
19090
19091 EditorElement::new(
19092 &cx.entity(),
19093 EditorStyle {
19094 background,
19095 local_player: cx.theme().players().local(),
19096 text: text_style,
19097 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19098 syntax: cx.theme().syntax().clone(),
19099 status: cx.theme().status().clone(),
19100 inlay_hints_style: make_inlay_hints_style(cx),
19101 inline_completion_styles: make_suggestion_styles(cx),
19102 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19103 },
19104 )
19105 }
19106}
19107
19108impl EntityInputHandler for Editor {
19109 fn text_for_range(
19110 &mut self,
19111 range_utf16: Range<usize>,
19112 adjusted_range: &mut Option<Range<usize>>,
19113 _: &mut Window,
19114 cx: &mut Context<Self>,
19115 ) -> Option<String> {
19116 let snapshot = self.buffer.read(cx).read(cx);
19117 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19118 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19119 if (start.0..end.0) != range_utf16 {
19120 adjusted_range.replace(start.0..end.0);
19121 }
19122 Some(snapshot.text_for_range(start..end).collect())
19123 }
19124
19125 fn selected_text_range(
19126 &mut self,
19127 ignore_disabled_input: bool,
19128 _: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) -> Option<UTF16Selection> {
19131 // Prevent the IME menu from appearing when holding down an alphabetic key
19132 // while input is disabled.
19133 if !ignore_disabled_input && !self.input_enabled {
19134 return None;
19135 }
19136
19137 let selection = self.selections.newest::<OffsetUtf16>(cx);
19138 let range = selection.range();
19139
19140 Some(UTF16Selection {
19141 range: range.start.0..range.end.0,
19142 reversed: selection.reversed,
19143 })
19144 }
19145
19146 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19147 let snapshot = self.buffer.read(cx).read(cx);
19148 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19149 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19150 }
19151
19152 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19153 self.clear_highlights::<InputComposition>(cx);
19154 self.ime_transaction.take();
19155 }
19156
19157 fn replace_text_in_range(
19158 &mut self,
19159 range_utf16: Option<Range<usize>>,
19160 text: &str,
19161 window: &mut Window,
19162 cx: &mut Context<Self>,
19163 ) {
19164 if !self.input_enabled {
19165 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19166 return;
19167 }
19168
19169 self.transact(window, cx, |this, window, cx| {
19170 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19171 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19172 Some(this.selection_replacement_ranges(range_utf16, cx))
19173 } else {
19174 this.marked_text_ranges(cx)
19175 };
19176
19177 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19178 let newest_selection_id = this.selections.newest_anchor().id;
19179 this.selections
19180 .all::<OffsetUtf16>(cx)
19181 .iter()
19182 .zip(ranges_to_replace.iter())
19183 .find_map(|(selection, range)| {
19184 if selection.id == newest_selection_id {
19185 Some(
19186 (range.start.0 as isize - selection.head().0 as isize)
19187 ..(range.end.0 as isize - selection.head().0 as isize),
19188 )
19189 } else {
19190 None
19191 }
19192 })
19193 });
19194
19195 cx.emit(EditorEvent::InputHandled {
19196 utf16_range_to_replace: range_to_replace,
19197 text: text.into(),
19198 });
19199
19200 if let Some(new_selected_ranges) = new_selected_ranges {
19201 this.change_selections(None, window, cx, |selections| {
19202 selections.select_ranges(new_selected_ranges)
19203 });
19204 this.backspace(&Default::default(), window, cx);
19205 }
19206
19207 this.handle_input(text, window, cx);
19208 });
19209
19210 if let Some(transaction) = self.ime_transaction {
19211 self.buffer.update(cx, |buffer, cx| {
19212 buffer.group_until_transaction(transaction, cx);
19213 });
19214 }
19215
19216 self.unmark_text(window, cx);
19217 }
19218
19219 fn replace_and_mark_text_in_range(
19220 &mut self,
19221 range_utf16: Option<Range<usize>>,
19222 text: &str,
19223 new_selected_range_utf16: Option<Range<usize>>,
19224 window: &mut Window,
19225 cx: &mut Context<Self>,
19226 ) {
19227 if !self.input_enabled {
19228 return;
19229 }
19230
19231 let transaction = self.transact(window, cx, |this, window, cx| {
19232 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19233 let snapshot = this.buffer.read(cx).read(cx);
19234 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19235 for marked_range in &mut marked_ranges {
19236 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19237 marked_range.start.0 += relative_range_utf16.start;
19238 marked_range.start =
19239 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19240 marked_range.end =
19241 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19242 }
19243 }
19244 Some(marked_ranges)
19245 } else if let Some(range_utf16) = range_utf16 {
19246 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19247 Some(this.selection_replacement_ranges(range_utf16, cx))
19248 } else {
19249 None
19250 };
19251
19252 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19253 let newest_selection_id = this.selections.newest_anchor().id;
19254 this.selections
19255 .all::<OffsetUtf16>(cx)
19256 .iter()
19257 .zip(ranges_to_replace.iter())
19258 .find_map(|(selection, range)| {
19259 if selection.id == newest_selection_id {
19260 Some(
19261 (range.start.0 as isize - selection.head().0 as isize)
19262 ..(range.end.0 as isize - selection.head().0 as isize),
19263 )
19264 } else {
19265 None
19266 }
19267 })
19268 });
19269
19270 cx.emit(EditorEvent::InputHandled {
19271 utf16_range_to_replace: range_to_replace,
19272 text: text.into(),
19273 });
19274
19275 if let Some(ranges) = ranges_to_replace {
19276 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19277 }
19278
19279 let marked_ranges = {
19280 let snapshot = this.buffer.read(cx).read(cx);
19281 this.selections
19282 .disjoint_anchors()
19283 .iter()
19284 .map(|selection| {
19285 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19286 })
19287 .collect::<Vec<_>>()
19288 };
19289
19290 if text.is_empty() {
19291 this.unmark_text(window, cx);
19292 } else {
19293 this.highlight_text::<InputComposition>(
19294 marked_ranges.clone(),
19295 HighlightStyle {
19296 underline: Some(UnderlineStyle {
19297 thickness: px(1.),
19298 color: None,
19299 wavy: false,
19300 }),
19301 ..Default::default()
19302 },
19303 cx,
19304 );
19305 }
19306
19307 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19308 let use_autoclose = this.use_autoclose;
19309 let use_auto_surround = this.use_auto_surround;
19310 this.set_use_autoclose(false);
19311 this.set_use_auto_surround(false);
19312 this.handle_input(text, window, cx);
19313 this.set_use_autoclose(use_autoclose);
19314 this.set_use_auto_surround(use_auto_surround);
19315
19316 if let Some(new_selected_range) = new_selected_range_utf16 {
19317 let snapshot = this.buffer.read(cx).read(cx);
19318 let new_selected_ranges = marked_ranges
19319 .into_iter()
19320 .map(|marked_range| {
19321 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19322 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19323 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19324 snapshot.clip_offset_utf16(new_start, Bias::Left)
19325 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19326 })
19327 .collect::<Vec<_>>();
19328
19329 drop(snapshot);
19330 this.change_selections(None, window, cx, |selections| {
19331 selections.select_ranges(new_selected_ranges)
19332 });
19333 }
19334 });
19335
19336 self.ime_transaction = self.ime_transaction.or(transaction);
19337 if let Some(transaction) = self.ime_transaction {
19338 self.buffer.update(cx, |buffer, cx| {
19339 buffer.group_until_transaction(transaction, cx);
19340 });
19341 }
19342
19343 if self.text_highlights::<InputComposition>(cx).is_none() {
19344 self.ime_transaction.take();
19345 }
19346 }
19347
19348 fn bounds_for_range(
19349 &mut self,
19350 range_utf16: Range<usize>,
19351 element_bounds: gpui::Bounds<Pixels>,
19352 window: &mut Window,
19353 cx: &mut Context<Self>,
19354 ) -> Option<gpui::Bounds<Pixels>> {
19355 let text_layout_details = self.text_layout_details(window);
19356 let gpui::Size {
19357 width: em_width,
19358 height: line_height,
19359 } = self.character_size(window);
19360
19361 let snapshot = self.snapshot(window, cx);
19362 let scroll_position = snapshot.scroll_position();
19363 let scroll_left = scroll_position.x * em_width;
19364
19365 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19366 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19367 + self.gutter_dimensions.width
19368 + self.gutter_dimensions.margin;
19369 let y = line_height * (start.row().as_f32() - scroll_position.y);
19370
19371 Some(Bounds {
19372 origin: element_bounds.origin + point(x, y),
19373 size: size(em_width, line_height),
19374 })
19375 }
19376
19377 fn character_index_for_point(
19378 &mut self,
19379 point: gpui::Point<Pixels>,
19380 _window: &mut Window,
19381 _cx: &mut Context<Self>,
19382 ) -> Option<usize> {
19383 let position_map = self.last_position_map.as_ref()?;
19384 if !position_map.text_hitbox.contains(&point) {
19385 return None;
19386 }
19387 let display_point = position_map.point_for_position(point).previous_valid;
19388 let anchor = position_map
19389 .snapshot
19390 .display_point_to_anchor(display_point, Bias::Left);
19391 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19392 Some(utf16_offset.0)
19393 }
19394}
19395
19396trait SelectionExt {
19397 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19398 fn spanned_rows(
19399 &self,
19400 include_end_if_at_line_start: bool,
19401 map: &DisplaySnapshot,
19402 ) -> Range<MultiBufferRow>;
19403}
19404
19405impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19406 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19407 let start = self
19408 .start
19409 .to_point(&map.buffer_snapshot)
19410 .to_display_point(map);
19411 let end = self
19412 .end
19413 .to_point(&map.buffer_snapshot)
19414 .to_display_point(map);
19415 if self.reversed {
19416 end..start
19417 } else {
19418 start..end
19419 }
19420 }
19421
19422 fn spanned_rows(
19423 &self,
19424 include_end_if_at_line_start: bool,
19425 map: &DisplaySnapshot,
19426 ) -> Range<MultiBufferRow> {
19427 let start = self.start.to_point(&map.buffer_snapshot);
19428 let mut end = self.end.to_point(&map.buffer_snapshot);
19429 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19430 end.row -= 1;
19431 }
19432
19433 let buffer_start = map.prev_line_boundary(start).0;
19434 let buffer_end = map.next_line_boundary(end).0;
19435 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19436 }
19437}
19438
19439impl<T: InvalidationRegion> InvalidationStack<T> {
19440 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19441 where
19442 S: Clone + ToOffset,
19443 {
19444 while let Some(region) = self.last() {
19445 let all_selections_inside_invalidation_ranges =
19446 if selections.len() == region.ranges().len() {
19447 selections
19448 .iter()
19449 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19450 .all(|(selection, invalidation_range)| {
19451 let head = selection.head().to_offset(buffer);
19452 invalidation_range.start <= head && invalidation_range.end >= head
19453 })
19454 } else {
19455 false
19456 };
19457
19458 if all_selections_inside_invalidation_ranges {
19459 break;
19460 } else {
19461 self.pop();
19462 }
19463 }
19464 }
19465}
19466
19467impl<T> Default for InvalidationStack<T> {
19468 fn default() -> Self {
19469 Self(Default::default())
19470 }
19471}
19472
19473impl<T> Deref for InvalidationStack<T> {
19474 type Target = Vec<T>;
19475
19476 fn deref(&self) -> &Self::Target {
19477 &self.0
19478 }
19479}
19480
19481impl<T> DerefMut for InvalidationStack<T> {
19482 fn deref_mut(&mut self) -> &mut Self::Target {
19483 &mut self.0
19484 }
19485}
19486
19487impl InvalidationRegion for SnippetState {
19488 fn ranges(&self) -> &[Range<Anchor>] {
19489 &self.ranges[self.active_index]
19490 }
19491}
19492
19493pub fn diagnostic_block_renderer(
19494 diagnostic: Diagnostic,
19495 max_message_rows: Option<u8>,
19496 allow_closing: bool,
19497) -> RenderBlock {
19498 let (text_without_backticks, code_ranges) =
19499 highlight_diagnostic_message(&diagnostic, max_message_rows);
19500
19501 Arc::new(move |cx: &mut BlockContext| {
19502 let group_id: SharedString = cx.block_id.to_string().into();
19503
19504 let mut text_style = cx.window.text_style().clone();
19505 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19506 let theme_settings = ThemeSettings::get_global(cx);
19507 text_style.font_family = theme_settings.buffer_font.family.clone();
19508 text_style.font_style = theme_settings.buffer_font.style;
19509 text_style.font_features = theme_settings.buffer_font.features.clone();
19510 text_style.font_weight = theme_settings.buffer_font.weight;
19511
19512 let multi_line_diagnostic = diagnostic.message.contains('\n');
19513
19514 let buttons = |diagnostic: &Diagnostic| {
19515 if multi_line_diagnostic {
19516 v_flex()
19517 } else {
19518 h_flex()
19519 }
19520 .when(allow_closing, |div| {
19521 div.children(diagnostic.is_primary.then(|| {
19522 IconButton::new("close-block", IconName::XCircle)
19523 .icon_color(Color::Muted)
19524 .size(ButtonSize::Compact)
19525 .style(ButtonStyle::Transparent)
19526 .visible_on_hover(group_id.clone())
19527 .on_click(move |_click, window, cx| {
19528 window.dispatch_action(Box::new(Cancel), cx)
19529 })
19530 .tooltip(|window, cx| {
19531 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19532 })
19533 }))
19534 })
19535 .child(
19536 IconButton::new("copy-block", IconName::Copy)
19537 .icon_color(Color::Muted)
19538 .size(ButtonSize::Compact)
19539 .style(ButtonStyle::Transparent)
19540 .visible_on_hover(group_id.clone())
19541 .on_click({
19542 let message = diagnostic.message.clone();
19543 move |_click, _, cx| {
19544 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19545 }
19546 })
19547 .tooltip(Tooltip::text("Copy diagnostic message")),
19548 )
19549 };
19550
19551 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19552 AvailableSpace::min_size(),
19553 cx.window,
19554 cx.app,
19555 );
19556
19557 h_flex()
19558 .id(cx.block_id)
19559 .group(group_id.clone())
19560 .relative()
19561 .size_full()
19562 .block_mouse_down()
19563 .pl(cx.gutter_dimensions.width)
19564 .w(cx.max_width - cx.gutter_dimensions.full_width())
19565 .child(
19566 div()
19567 .flex()
19568 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19569 .flex_shrink(),
19570 )
19571 .child(buttons(&diagnostic))
19572 .child(div().flex().flex_shrink_0().child(
19573 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19574 &text_style,
19575 code_ranges.iter().map(|range| {
19576 (
19577 range.clone(),
19578 HighlightStyle {
19579 font_weight: Some(FontWeight::BOLD),
19580 ..Default::default()
19581 },
19582 )
19583 }),
19584 ),
19585 ))
19586 .into_any_element()
19587 })
19588}
19589
19590fn inline_completion_edit_text(
19591 current_snapshot: &BufferSnapshot,
19592 edits: &[(Range<Anchor>, String)],
19593 edit_preview: &EditPreview,
19594 include_deletions: bool,
19595 cx: &App,
19596) -> HighlightedText {
19597 let edits = edits
19598 .iter()
19599 .map(|(anchor, text)| {
19600 (
19601 anchor.start.text_anchor..anchor.end.text_anchor,
19602 text.clone(),
19603 )
19604 })
19605 .collect::<Vec<_>>();
19606
19607 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19608}
19609
19610pub fn highlight_diagnostic_message(
19611 diagnostic: &Diagnostic,
19612 mut max_message_rows: Option<u8>,
19613) -> (SharedString, Vec<Range<usize>>) {
19614 let mut text_without_backticks = String::new();
19615 let mut code_ranges = Vec::new();
19616
19617 if let Some(source) = &diagnostic.source {
19618 text_without_backticks.push_str(source);
19619 code_ranges.push(0..source.len());
19620 text_without_backticks.push_str(": ");
19621 }
19622
19623 let mut prev_offset = 0;
19624 let mut in_code_block = false;
19625 let has_row_limit = max_message_rows.is_some();
19626 let mut newline_indices = diagnostic
19627 .message
19628 .match_indices('\n')
19629 .filter(|_| has_row_limit)
19630 .map(|(ix, _)| ix)
19631 .fuse()
19632 .peekable();
19633
19634 for (quote_ix, _) in diagnostic
19635 .message
19636 .match_indices('`')
19637 .chain([(diagnostic.message.len(), "")])
19638 {
19639 let mut first_newline_ix = None;
19640 let mut last_newline_ix = None;
19641 while let Some(newline_ix) = newline_indices.peek() {
19642 if *newline_ix < quote_ix {
19643 if first_newline_ix.is_none() {
19644 first_newline_ix = Some(*newline_ix);
19645 }
19646 last_newline_ix = Some(*newline_ix);
19647
19648 if let Some(rows_left) = &mut max_message_rows {
19649 if *rows_left == 0 {
19650 break;
19651 } else {
19652 *rows_left -= 1;
19653 }
19654 }
19655 let _ = newline_indices.next();
19656 } else {
19657 break;
19658 }
19659 }
19660 let prev_len = text_without_backticks.len();
19661 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19662 text_without_backticks.push_str(new_text);
19663 if in_code_block {
19664 code_ranges.push(prev_len..text_without_backticks.len());
19665 }
19666 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19667 in_code_block = !in_code_block;
19668 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19669 text_without_backticks.push_str("...");
19670 break;
19671 }
19672 }
19673
19674 (text_without_backticks.into(), code_ranges)
19675}
19676
19677fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19678 match severity {
19679 DiagnosticSeverity::ERROR => colors.error,
19680 DiagnosticSeverity::WARNING => colors.warning,
19681 DiagnosticSeverity::INFORMATION => colors.info,
19682 DiagnosticSeverity::HINT => colors.info,
19683 _ => colors.ignored,
19684 }
19685}
19686
19687pub fn styled_runs_for_code_label<'a>(
19688 label: &'a CodeLabel,
19689 syntax_theme: &'a theme::SyntaxTheme,
19690) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19691 let fade_out = HighlightStyle {
19692 fade_out: Some(0.35),
19693 ..Default::default()
19694 };
19695
19696 let mut prev_end = label.filter_range.end;
19697 label
19698 .runs
19699 .iter()
19700 .enumerate()
19701 .flat_map(move |(ix, (range, highlight_id))| {
19702 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19703 style
19704 } else {
19705 return Default::default();
19706 };
19707 let mut muted_style = style;
19708 muted_style.highlight(fade_out);
19709
19710 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19711 if range.start >= label.filter_range.end {
19712 if range.start > prev_end {
19713 runs.push((prev_end..range.start, fade_out));
19714 }
19715 runs.push((range.clone(), muted_style));
19716 } else if range.end <= label.filter_range.end {
19717 runs.push((range.clone(), style));
19718 } else {
19719 runs.push((range.start..label.filter_range.end, style));
19720 runs.push((label.filter_range.end..range.end, muted_style));
19721 }
19722 prev_end = cmp::max(prev_end, range.end);
19723
19724 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19725 runs.push((prev_end..label.text.len(), fade_out));
19726 }
19727
19728 runs
19729 })
19730}
19731
19732pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19733 let mut prev_index = 0;
19734 let mut prev_codepoint: Option<char> = None;
19735 text.char_indices()
19736 .chain([(text.len(), '\0')])
19737 .filter_map(move |(index, codepoint)| {
19738 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19739 let is_boundary = index == text.len()
19740 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19741 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19742 if is_boundary {
19743 let chunk = &text[prev_index..index];
19744 prev_index = index;
19745 Some(chunk)
19746 } else {
19747 None
19748 }
19749 })
19750}
19751
19752pub trait RangeToAnchorExt: Sized {
19753 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19754
19755 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19756 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19757 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19758 }
19759}
19760
19761impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19762 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19763 let start_offset = self.start.to_offset(snapshot);
19764 let end_offset = self.end.to_offset(snapshot);
19765 if start_offset == end_offset {
19766 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19767 } else {
19768 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19769 }
19770 }
19771}
19772
19773pub trait RowExt {
19774 fn as_f32(&self) -> f32;
19775
19776 fn next_row(&self) -> Self;
19777
19778 fn previous_row(&self) -> Self;
19779
19780 fn minus(&self, other: Self) -> u32;
19781}
19782
19783impl RowExt for DisplayRow {
19784 fn as_f32(&self) -> f32 {
19785 self.0 as f32
19786 }
19787
19788 fn next_row(&self) -> Self {
19789 Self(self.0 + 1)
19790 }
19791
19792 fn previous_row(&self) -> Self {
19793 Self(self.0.saturating_sub(1))
19794 }
19795
19796 fn minus(&self, other: Self) -> u32 {
19797 self.0 - other.0
19798 }
19799}
19800
19801impl RowExt for MultiBufferRow {
19802 fn as_f32(&self) -> f32 {
19803 self.0 as f32
19804 }
19805
19806 fn next_row(&self) -> Self {
19807 Self(self.0 + 1)
19808 }
19809
19810 fn previous_row(&self) -> Self {
19811 Self(self.0.saturating_sub(1))
19812 }
19813
19814 fn minus(&self, other: Self) -> u32 {
19815 self.0 - other.0
19816 }
19817}
19818
19819trait RowRangeExt {
19820 type Row;
19821
19822 fn len(&self) -> usize;
19823
19824 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19825}
19826
19827impl RowRangeExt for Range<MultiBufferRow> {
19828 type Row = MultiBufferRow;
19829
19830 fn len(&self) -> usize {
19831 (self.end.0 - self.start.0) as usize
19832 }
19833
19834 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19835 (self.start.0..self.end.0).map(MultiBufferRow)
19836 }
19837}
19838
19839impl RowRangeExt for Range<DisplayRow> {
19840 type Row = DisplayRow;
19841
19842 fn len(&self) -> usize {
19843 (self.end.0 - self.start.0) as usize
19844 }
19845
19846 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19847 (self.start.0..self.end.0).map(DisplayRow)
19848 }
19849}
19850
19851/// If select range has more than one line, we
19852/// just point the cursor to range.start.
19853fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19854 if range.start.row == range.end.row {
19855 range
19856 } else {
19857 range.start..range.start
19858 }
19859}
19860pub struct KillRing(ClipboardItem);
19861impl Global for KillRing {}
19862
19863const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19864
19865struct BreakpointPromptEditor {
19866 pub(crate) prompt: Entity<Editor>,
19867 editor: WeakEntity<Editor>,
19868 breakpoint_anchor: Anchor,
19869 breakpoint: Breakpoint,
19870 block_ids: HashSet<CustomBlockId>,
19871 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19872 _subscriptions: Vec<Subscription>,
19873}
19874
19875impl BreakpointPromptEditor {
19876 const MAX_LINES: u8 = 4;
19877
19878 fn new(
19879 editor: WeakEntity<Editor>,
19880 breakpoint_anchor: Anchor,
19881 breakpoint: Breakpoint,
19882 window: &mut Window,
19883 cx: &mut Context<Self>,
19884 ) -> Self {
19885 let buffer = cx.new(|cx| {
19886 Buffer::local(
19887 breakpoint
19888 .message
19889 .as_ref()
19890 .map(|msg| msg.to_string())
19891 .unwrap_or_default(),
19892 cx,
19893 )
19894 });
19895 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19896
19897 let prompt = cx.new(|cx| {
19898 let mut prompt = Editor::new(
19899 EditorMode::AutoHeight {
19900 max_lines: Self::MAX_LINES as usize,
19901 },
19902 buffer,
19903 None,
19904 window,
19905 cx,
19906 );
19907 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19908 prompt.set_show_cursor_when_unfocused(false, cx);
19909 prompt.set_placeholder_text(
19910 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19911 cx,
19912 );
19913
19914 prompt
19915 });
19916
19917 Self {
19918 prompt,
19919 editor,
19920 breakpoint_anchor,
19921 breakpoint,
19922 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19923 block_ids: Default::default(),
19924 _subscriptions: vec![],
19925 }
19926 }
19927
19928 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19929 self.block_ids.extend(block_ids)
19930 }
19931
19932 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19933 if let Some(editor) = self.editor.upgrade() {
19934 let log_message = self
19935 .prompt
19936 .read(cx)
19937 .buffer
19938 .read(cx)
19939 .as_singleton()
19940 .expect("A multi buffer in breakpoint prompt isn't possible")
19941 .read(cx)
19942 .as_rope()
19943 .to_string();
19944
19945 editor.update(cx, |editor, cx| {
19946 editor.edit_breakpoint_at_anchor(
19947 self.breakpoint_anchor,
19948 self.breakpoint.clone(),
19949 BreakpointEditAction::EditLogMessage(log_message.into()),
19950 cx,
19951 );
19952
19953 editor.remove_blocks(self.block_ids.clone(), None, cx);
19954 cx.focus_self(window);
19955 });
19956 }
19957 }
19958
19959 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19960 self.editor
19961 .update(cx, |editor, cx| {
19962 editor.remove_blocks(self.block_ids.clone(), None, cx);
19963 window.focus(&editor.focus_handle);
19964 })
19965 .log_err();
19966 }
19967
19968 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19969 let settings = ThemeSettings::get_global(cx);
19970 let text_style = TextStyle {
19971 color: if self.prompt.read(cx).read_only(cx) {
19972 cx.theme().colors().text_disabled
19973 } else {
19974 cx.theme().colors().text
19975 },
19976 font_family: settings.buffer_font.family.clone(),
19977 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19978 font_size: settings.buffer_font_size(cx).into(),
19979 font_weight: settings.buffer_font.weight,
19980 line_height: relative(settings.buffer_line_height.value()),
19981 ..Default::default()
19982 };
19983 EditorElement::new(
19984 &self.prompt,
19985 EditorStyle {
19986 background: cx.theme().colors().editor_background,
19987 local_player: cx.theme().players().local(),
19988 text: text_style,
19989 ..Default::default()
19990 },
19991 )
19992 }
19993}
19994
19995impl Render for BreakpointPromptEditor {
19996 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19997 let gutter_dimensions = *self.gutter_dimensions.lock();
19998 h_flex()
19999 .key_context("Editor")
20000 .bg(cx.theme().colors().editor_background)
20001 .border_y_1()
20002 .border_color(cx.theme().status().info_border)
20003 .size_full()
20004 .py(window.line_height() / 2.5)
20005 .on_action(cx.listener(Self::confirm))
20006 .on_action(cx.listener(Self::cancel))
20007 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20008 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20009 }
20010}
20011
20012impl Focusable for BreakpointPromptEditor {
20013 fn focus_handle(&self, cx: &App) -> FocusHandle {
20014 self.prompt.focus_handle(cx)
20015 }
20016}
20017
20018fn all_edits_insertions_or_deletions(
20019 edits: &Vec<(Range<Anchor>, String)>,
20020 snapshot: &MultiBufferSnapshot,
20021) -> bool {
20022 let mut all_insertions = true;
20023 let mut all_deletions = true;
20024
20025 for (range, new_text) in edits.iter() {
20026 let range_is_empty = range.to_offset(&snapshot).is_empty();
20027 let text_is_empty = new_text.is_empty();
20028
20029 if range_is_empty != text_is_empty {
20030 if range_is_empty {
20031 all_deletions = false;
20032 } else {
20033 all_insertions = false;
20034 }
20035 } else {
20036 return false;
20037 }
20038
20039 if !all_insertions && !all_deletions {
20040 return false;
20041 }
20042 }
20043 all_insertions || all_deletions
20044}
20045
20046struct MissingEditPredictionKeybindingTooltip;
20047
20048impl Render for MissingEditPredictionKeybindingTooltip {
20049 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20050 ui::tooltip_container(window, cx, |container, _, cx| {
20051 container
20052 .flex_shrink_0()
20053 .max_w_80()
20054 .min_h(rems_from_px(124.))
20055 .justify_between()
20056 .child(
20057 v_flex()
20058 .flex_1()
20059 .text_ui_sm(cx)
20060 .child(Label::new("Conflict with Accept Keybinding"))
20061 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20062 )
20063 .child(
20064 h_flex()
20065 .pb_1()
20066 .gap_1()
20067 .items_end()
20068 .w_full()
20069 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20070 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20071 }))
20072 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20073 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20074 })),
20075 )
20076 })
20077 }
20078}
20079
20080#[derive(Debug, Clone, Copy, PartialEq)]
20081pub struct LineHighlight {
20082 pub background: Background,
20083 pub border: Option<gpui::Hsla>,
20084}
20085
20086impl From<Hsla> for LineHighlight {
20087 fn from(hsla: Hsla) -> Self {
20088 Self {
20089 background: hsla.into(),
20090 border: None,
20091 }
20092 }
20093}
20094
20095impl From<Background> for LineHighlight {
20096 fn from(background: Background) -> Self {
20097 Self {
20098 background,
20099 border: None,
20100 }
20101 }
20102}
20103
20104fn render_diff_hunk_controls(
20105 row: u32,
20106 status: &DiffHunkStatus,
20107 hunk_range: Range<Anchor>,
20108 is_created_file: bool,
20109 line_height: Pixels,
20110 editor: &Entity<Editor>,
20111 _window: &mut Window,
20112 cx: &mut App,
20113) -> AnyElement {
20114 h_flex()
20115 .h(line_height)
20116 .mr_1()
20117 .gap_1()
20118 .px_0p5()
20119 .pb_1()
20120 .border_x_1()
20121 .border_b_1()
20122 .border_color(cx.theme().colors().border_variant)
20123 .rounded_b_lg()
20124 .bg(cx.theme().colors().editor_background)
20125 .gap_1()
20126 .occlude()
20127 .shadow_md()
20128 .child(if status.has_secondary_hunk() {
20129 Button::new(("stage", row as u64), "Stage")
20130 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20131 .tooltip({
20132 let focus_handle = editor.focus_handle(cx);
20133 move |window, cx| {
20134 Tooltip::for_action_in(
20135 "Stage Hunk",
20136 &::git::ToggleStaged,
20137 &focus_handle,
20138 window,
20139 cx,
20140 )
20141 }
20142 })
20143 .on_click({
20144 let editor = editor.clone();
20145 move |_event, _window, cx| {
20146 editor.update(cx, |editor, cx| {
20147 editor.stage_or_unstage_diff_hunks(
20148 true,
20149 vec![hunk_range.start..hunk_range.start],
20150 cx,
20151 );
20152 });
20153 }
20154 })
20155 } else {
20156 Button::new(("unstage", row as u64), "Unstage")
20157 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20158 .tooltip({
20159 let focus_handle = editor.focus_handle(cx);
20160 move |window, cx| {
20161 Tooltip::for_action_in(
20162 "Unstage Hunk",
20163 &::git::ToggleStaged,
20164 &focus_handle,
20165 window,
20166 cx,
20167 )
20168 }
20169 })
20170 .on_click({
20171 let editor = editor.clone();
20172 move |_event, _window, cx| {
20173 editor.update(cx, |editor, cx| {
20174 editor.stage_or_unstage_diff_hunks(
20175 false,
20176 vec![hunk_range.start..hunk_range.start],
20177 cx,
20178 );
20179 });
20180 }
20181 })
20182 })
20183 .child(
20184 Button::new("restore", "Restore")
20185 .tooltip({
20186 let focus_handle = editor.focus_handle(cx);
20187 move |window, cx| {
20188 Tooltip::for_action_in(
20189 "Restore Hunk",
20190 &::git::Restore,
20191 &focus_handle,
20192 window,
20193 cx,
20194 )
20195 }
20196 })
20197 .on_click({
20198 let editor = editor.clone();
20199 move |_event, window, cx| {
20200 editor.update(cx, |editor, cx| {
20201 let snapshot = editor.snapshot(window, cx);
20202 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20203 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20204 });
20205 }
20206 })
20207 .disabled(is_created_file),
20208 )
20209 .when(
20210 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20211 |el| {
20212 el.child(
20213 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20214 .shape(IconButtonShape::Square)
20215 .icon_size(IconSize::Small)
20216 // .disabled(!has_multiple_hunks)
20217 .tooltip({
20218 let focus_handle = editor.focus_handle(cx);
20219 move |window, cx| {
20220 Tooltip::for_action_in(
20221 "Next Hunk",
20222 &GoToHunk,
20223 &focus_handle,
20224 window,
20225 cx,
20226 )
20227 }
20228 })
20229 .on_click({
20230 let editor = editor.clone();
20231 move |_event, window, cx| {
20232 editor.update(cx, |editor, cx| {
20233 let snapshot = editor.snapshot(window, cx);
20234 let position =
20235 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20236 editor.go_to_hunk_before_or_after_position(
20237 &snapshot,
20238 position,
20239 Direction::Next,
20240 window,
20241 cx,
20242 );
20243 editor.expand_selected_diff_hunks(cx);
20244 });
20245 }
20246 }),
20247 )
20248 .child(
20249 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20250 .shape(IconButtonShape::Square)
20251 .icon_size(IconSize::Small)
20252 // .disabled(!has_multiple_hunks)
20253 .tooltip({
20254 let focus_handle = editor.focus_handle(cx);
20255 move |window, cx| {
20256 Tooltip::for_action_in(
20257 "Previous Hunk",
20258 &GoToPreviousHunk,
20259 &focus_handle,
20260 window,
20261 cx,
20262 )
20263 }
20264 })
20265 .on_click({
20266 let editor = editor.clone();
20267 move |_event, window, cx| {
20268 editor.update(cx, |editor, cx| {
20269 let snapshot = editor.snapshot(window, cx);
20270 let point =
20271 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20272 editor.go_to_hunk_before_or_after_position(
20273 &snapshot,
20274 point,
20275 Direction::Prev,
20276 window,
20277 cx,
20278 );
20279 editor.expand_selected_diff_hunks(cx);
20280 });
20281 }
20282 }),
20283 )
20284 },
20285 )
20286 .into_any_element()
20287}