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 if !this.selections.line_mode {
7895 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7896 for selection in &mut selections {
7897 if selection.is_empty() {
7898 let old_head = selection.head();
7899 let mut new_head =
7900 movement::left(&display_map, old_head.to_display_point(&display_map))
7901 .to_point(&display_map);
7902 if let Some((buffer, line_buffer_range)) = display_map
7903 .buffer_snapshot
7904 .buffer_line_for_row(MultiBufferRow(old_head.row))
7905 {
7906 let indent_size =
7907 buffer.indent_size_for_line(line_buffer_range.start.row);
7908 let indent_len = match indent_size.kind {
7909 IndentKind::Space => {
7910 buffer.settings_at(line_buffer_range.start, cx).tab_size
7911 }
7912 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7913 };
7914 if old_head.column <= indent_size.len && old_head.column > 0 {
7915 let indent_len = indent_len.get();
7916 new_head = cmp::min(
7917 new_head,
7918 MultiBufferPoint::new(
7919 old_head.row,
7920 ((old_head.column - 1) / indent_len) * indent_len,
7921 ),
7922 );
7923 }
7924 }
7925
7926 selection.set_head(new_head, SelectionGoal::None);
7927 }
7928 }
7929 }
7930
7931 this.signature_help_state.set_backspace_pressed(true);
7932 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7933 s.select(selections)
7934 });
7935 this.insert("", window, cx);
7936 let empty_str: Arc<str> = Arc::from("");
7937 for (buffer, edits) in linked_ranges {
7938 let snapshot = buffer.read(cx).snapshot();
7939 use text::ToPoint as TP;
7940
7941 let edits = edits
7942 .into_iter()
7943 .map(|range| {
7944 let end_point = TP::to_point(&range.end, &snapshot);
7945 let mut start_point = TP::to_point(&range.start, &snapshot);
7946
7947 if end_point == start_point {
7948 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
7949 .saturating_sub(1);
7950 start_point =
7951 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
7952 };
7953
7954 (start_point..end_point, empty_str.clone())
7955 })
7956 .sorted_by_key(|(range, _)| range.start)
7957 .collect::<Vec<_>>();
7958 buffer.update(cx, |this, cx| {
7959 this.edit(edits, None, cx);
7960 })
7961 }
7962 this.refresh_inline_completion(true, false, window, cx);
7963 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
7964 });
7965 }
7966
7967 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
7968 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7969 self.transact(window, cx, |this, window, cx| {
7970 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7971 let line_mode = s.line_mode;
7972 s.move_with(|map, selection| {
7973 if selection.is_empty() && !line_mode {
7974 let cursor = movement::right(map, selection.head());
7975 selection.end = cursor;
7976 selection.reversed = true;
7977 selection.goal = SelectionGoal::None;
7978 }
7979 })
7980 });
7981 this.insert("", window, cx);
7982 this.refresh_inline_completion(true, false, window, cx);
7983 });
7984 }
7985
7986 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
7987 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7988 if self.move_to_prev_snippet_tabstop(window, cx) {
7989 return;
7990 }
7991 self.outdent(&Outdent, window, cx);
7992 }
7993
7994 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
7995 if self.move_to_next_snippet_tabstop(window, cx) {
7996 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7997 return;
7998 }
7999 if self.read_only(cx) {
8000 return;
8001 }
8002 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8003 let mut selections = self.selections.all_adjusted(cx);
8004 let buffer = self.buffer.read(cx);
8005 let snapshot = buffer.snapshot(cx);
8006 let rows_iter = selections.iter().map(|s| s.head().row);
8007 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8008
8009 let mut edits = Vec::new();
8010 let mut prev_edited_row = 0;
8011 let mut row_delta = 0;
8012 for selection in &mut selections {
8013 if selection.start.row != prev_edited_row {
8014 row_delta = 0;
8015 }
8016 prev_edited_row = selection.end.row;
8017
8018 // If the selection is non-empty, then increase the indentation of the selected lines.
8019 if !selection.is_empty() {
8020 row_delta =
8021 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8022 continue;
8023 }
8024
8025 // If the selection is empty and the cursor is in the leading whitespace before the
8026 // suggested indentation, then auto-indent the line.
8027 let cursor = selection.head();
8028 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8029 if let Some(suggested_indent) =
8030 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8031 {
8032 if cursor.column < suggested_indent.len
8033 && cursor.column <= current_indent.len
8034 && current_indent.len <= suggested_indent.len
8035 {
8036 selection.start = Point::new(cursor.row, suggested_indent.len);
8037 selection.end = selection.start;
8038 if row_delta == 0 {
8039 edits.extend(Buffer::edit_for_indent_size_adjustment(
8040 cursor.row,
8041 current_indent,
8042 suggested_indent,
8043 ));
8044 row_delta = suggested_indent.len - current_indent.len;
8045 }
8046 continue;
8047 }
8048 }
8049
8050 // Otherwise, insert a hard or soft tab.
8051 let settings = buffer.language_settings_at(cursor, cx);
8052 let tab_size = if settings.hard_tabs {
8053 IndentSize::tab()
8054 } else {
8055 let tab_size = settings.tab_size.get();
8056 let char_column = snapshot
8057 .text_for_range(Point::new(cursor.row, 0)..cursor)
8058 .flat_map(str::chars)
8059 .count()
8060 + row_delta as usize;
8061 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8062 IndentSize::spaces(chars_to_next_tab_stop)
8063 };
8064 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8065 selection.end = selection.start;
8066 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8067 row_delta += tab_size.len;
8068 }
8069
8070 self.transact(window, cx, |this, window, cx| {
8071 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8072 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8073 s.select(selections)
8074 });
8075 this.refresh_inline_completion(true, false, window, cx);
8076 });
8077 }
8078
8079 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8080 if self.read_only(cx) {
8081 return;
8082 }
8083 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8084 let mut selections = self.selections.all::<Point>(cx);
8085 let mut prev_edited_row = 0;
8086 let mut row_delta = 0;
8087 let mut edits = Vec::new();
8088 let buffer = self.buffer.read(cx);
8089 let snapshot = buffer.snapshot(cx);
8090 for selection in &mut selections {
8091 if selection.start.row != prev_edited_row {
8092 row_delta = 0;
8093 }
8094 prev_edited_row = selection.end.row;
8095
8096 row_delta =
8097 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8098 }
8099
8100 self.transact(window, cx, |this, window, cx| {
8101 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8102 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8103 s.select(selections)
8104 });
8105 });
8106 }
8107
8108 fn indent_selection(
8109 buffer: &MultiBuffer,
8110 snapshot: &MultiBufferSnapshot,
8111 selection: &mut Selection<Point>,
8112 edits: &mut Vec<(Range<Point>, String)>,
8113 delta_for_start_row: u32,
8114 cx: &App,
8115 ) -> u32 {
8116 let settings = buffer.language_settings_at(selection.start, cx);
8117 let tab_size = settings.tab_size.get();
8118 let indent_kind = if settings.hard_tabs {
8119 IndentKind::Tab
8120 } else {
8121 IndentKind::Space
8122 };
8123 let mut start_row = selection.start.row;
8124 let mut end_row = selection.end.row + 1;
8125
8126 // If a selection ends at the beginning of a line, don't indent
8127 // that last line.
8128 if selection.end.column == 0 && selection.end.row > selection.start.row {
8129 end_row -= 1;
8130 }
8131
8132 // Avoid re-indenting a row that has already been indented by a
8133 // previous selection, but still update this selection's column
8134 // to reflect that indentation.
8135 if delta_for_start_row > 0 {
8136 start_row += 1;
8137 selection.start.column += delta_for_start_row;
8138 if selection.end.row == selection.start.row {
8139 selection.end.column += delta_for_start_row;
8140 }
8141 }
8142
8143 let mut delta_for_end_row = 0;
8144 let has_multiple_rows = start_row + 1 != end_row;
8145 for row in start_row..end_row {
8146 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8147 let indent_delta = match (current_indent.kind, indent_kind) {
8148 (IndentKind::Space, IndentKind::Space) => {
8149 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8150 IndentSize::spaces(columns_to_next_tab_stop)
8151 }
8152 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8153 (_, IndentKind::Tab) => IndentSize::tab(),
8154 };
8155
8156 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8157 0
8158 } else {
8159 selection.start.column
8160 };
8161 let row_start = Point::new(row, start);
8162 edits.push((
8163 row_start..row_start,
8164 indent_delta.chars().collect::<String>(),
8165 ));
8166
8167 // Update this selection's endpoints to reflect the indentation.
8168 if row == selection.start.row {
8169 selection.start.column += indent_delta.len;
8170 }
8171 if row == selection.end.row {
8172 selection.end.column += indent_delta.len;
8173 delta_for_end_row = indent_delta.len;
8174 }
8175 }
8176
8177 if selection.start.row == selection.end.row {
8178 delta_for_start_row + delta_for_end_row
8179 } else {
8180 delta_for_end_row
8181 }
8182 }
8183
8184 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8185 if self.read_only(cx) {
8186 return;
8187 }
8188 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8190 let selections = self.selections.all::<Point>(cx);
8191 let mut deletion_ranges = Vec::new();
8192 let mut last_outdent = None;
8193 {
8194 let buffer = self.buffer.read(cx);
8195 let snapshot = buffer.snapshot(cx);
8196 for selection in &selections {
8197 let settings = buffer.language_settings_at(selection.start, cx);
8198 let tab_size = settings.tab_size.get();
8199 let mut rows = selection.spanned_rows(false, &display_map);
8200
8201 // Avoid re-outdenting a row that has already been outdented by a
8202 // previous selection.
8203 if let Some(last_row) = last_outdent {
8204 if last_row == rows.start {
8205 rows.start = rows.start.next_row();
8206 }
8207 }
8208 let has_multiple_rows = rows.len() > 1;
8209 for row in rows.iter_rows() {
8210 let indent_size = snapshot.indent_size_for_line(row);
8211 if indent_size.len > 0 {
8212 let deletion_len = match indent_size.kind {
8213 IndentKind::Space => {
8214 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8215 if columns_to_prev_tab_stop == 0 {
8216 tab_size
8217 } else {
8218 columns_to_prev_tab_stop
8219 }
8220 }
8221 IndentKind::Tab => 1,
8222 };
8223 let start = if has_multiple_rows
8224 || deletion_len > selection.start.column
8225 || indent_size.len < selection.start.column
8226 {
8227 0
8228 } else {
8229 selection.start.column - deletion_len
8230 };
8231 deletion_ranges.push(
8232 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8233 );
8234 last_outdent = Some(row);
8235 }
8236 }
8237 }
8238 }
8239
8240 self.transact(window, cx, |this, window, cx| {
8241 this.buffer.update(cx, |buffer, cx| {
8242 let empty_str: Arc<str> = Arc::default();
8243 buffer.edit(
8244 deletion_ranges
8245 .into_iter()
8246 .map(|range| (range, empty_str.clone())),
8247 None,
8248 cx,
8249 );
8250 });
8251 let selections = this.selections.all::<usize>(cx);
8252 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8253 s.select(selections)
8254 });
8255 });
8256 }
8257
8258 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8259 if self.read_only(cx) {
8260 return;
8261 }
8262 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8263 let selections = self
8264 .selections
8265 .all::<usize>(cx)
8266 .into_iter()
8267 .map(|s| s.range());
8268
8269 self.transact(window, cx, |this, window, cx| {
8270 this.buffer.update(cx, |buffer, cx| {
8271 buffer.autoindent_ranges(selections, cx);
8272 });
8273 let selections = this.selections.all::<usize>(cx);
8274 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8275 s.select(selections)
8276 });
8277 });
8278 }
8279
8280 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8281 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8283 let selections = self.selections.all::<Point>(cx);
8284
8285 let mut new_cursors = Vec::new();
8286 let mut edit_ranges = Vec::new();
8287 let mut selections = selections.iter().peekable();
8288 while let Some(selection) = selections.next() {
8289 let mut rows = selection.spanned_rows(false, &display_map);
8290 let goal_display_column = selection.head().to_display_point(&display_map).column();
8291
8292 // Accumulate contiguous regions of rows that we want to delete.
8293 while let Some(next_selection) = selections.peek() {
8294 let next_rows = next_selection.spanned_rows(false, &display_map);
8295 if next_rows.start <= rows.end {
8296 rows.end = next_rows.end;
8297 selections.next().unwrap();
8298 } else {
8299 break;
8300 }
8301 }
8302
8303 let buffer = &display_map.buffer_snapshot;
8304 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8305 let edit_end;
8306 let cursor_buffer_row;
8307 if buffer.max_point().row >= rows.end.0 {
8308 // If there's a line after the range, delete the \n from the end of the row range
8309 // and position the cursor on the next line.
8310 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8311 cursor_buffer_row = rows.end;
8312 } else {
8313 // If there isn't a line after the range, delete the \n from the line before the
8314 // start of the row range and position the cursor there.
8315 edit_start = edit_start.saturating_sub(1);
8316 edit_end = buffer.len();
8317 cursor_buffer_row = rows.start.previous_row();
8318 }
8319
8320 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8321 *cursor.column_mut() =
8322 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8323
8324 new_cursors.push((
8325 selection.id,
8326 buffer.anchor_after(cursor.to_point(&display_map)),
8327 ));
8328 edit_ranges.push(edit_start..edit_end);
8329 }
8330
8331 self.transact(window, cx, |this, window, cx| {
8332 let buffer = this.buffer.update(cx, |buffer, cx| {
8333 let empty_str: Arc<str> = Arc::default();
8334 buffer.edit(
8335 edit_ranges
8336 .into_iter()
8337 .map(|range| (range, empty_str.clone())),
8338 None,
8339 cx,
8340 );
8341 buffer.snapshot(cx)
8342 });
8343 let new_selections = new_cursors
8344 .into_iter()
8345 .map(|(id, cursor)| {
8346 let cursor = cursor.to_point(&buffer);
8347 Selection {
8348 id,
8349 start: cursor,
8350 end: cursor,
8351 reversed: false,
8352 goal: SelectionGoal::None,
8353 }
8354 })
8355 .collect();
8356
8357 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8358 s.select(new_selections);
8359 });
8360 });
8361 }
8362
8363 pub fn join_lines_impl(
8364 &mut self,
8365 insert_whitespace: bool,
8366 window: &mut Window,
8367 cx: &mut Context<Self>,
8368 ) {
8369 if self.read_only(cx) {
8370 return;
8371 }
8372 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8373 for selection in self.selections.all::<Point>(cx) {
8374 let start = MultiBufferRow(selection.start.row);
8375 // Treat single line selections as if they include the next line. Otherwise this action
8376 // would do nothing for single line selections individual cursors.
8377 let end = if selection.start.row == selection.end.row {
8378 MultiBufferRow(selection.start.row + 1)
8379 } else {
8380 MultiBufferRow(selection.end.row)
8381 };
8382
8383 if let Some(last_row_range) = row_ranges.last_mut() {
8384 if start <= last_row_range.end {
8385 last_row_range.end = end;
8386 continue;
8387 }
8388 }
8389 row_ranges.push(start..end);
8390 }
8391
8392 let snapshot = self.buffer.read(cx).snapshot(cx);
8393 let mut cursor_positions = Vec::new();
8394 for row_range in &row_ranges {
8395 let anchor = snapshot.anchor_before(Point::new(
8396 row_range.end.previous_row().0,
8397 snapshot.line_len(row_range.end.previous_row()),
8398 ));
8399 cursor_positions.push(anchor..anchor);
8400 }
8401
8402 self.transact(window, cx, |this, window, cx| {
8403 for row_range in row_ranges.into_iter().rev() {
8404 for row in row_range.iter_rows().rev() {
8405 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8406 let next_line_row = row.next_row();
8407 let indent = snapshot.indent_size_for_line(next_line_row);
8408 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8409
8410 let replace =
8411 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8412 " "
8413 } else {
8414 ""
8415 };
8416
8417 this.buffer.update(cx, |buffer, cx| {
8418 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8419 });
8420 }
8421 }
8422
8423 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8424 s.select_anchor_ranges(cursor_positions)
8425 });
8426 });
8427 }
8428
8429 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8430 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8431 self.join_lines_impl(true, window, cx);
8432 }
8433
8434 pub fn sort_lines_case_sensitive(
8435 &mut self,
8436 _: &SortLinesCaseSensitive,
8437 window: &mut Window,
8438 cx: &mut Context<Self>,
8439 ) {
8440 self.manipulate_lines(window, cx, |lines| lines.sort())
8441 }
8442
8443 pub fn sort_lines_case_insensitive(
8444 &mut self,
8445 _: &SortLinesCaseInsensitive,
8446 window: &mut Window,
8447 cx: &mut Context<Self>,
8448 ) {
8449 self.manipulate_lines(window, cx, |lines| {
8450 lines.sort_by_key(|line| line.to_lowercase())
8451 })
8452 }
8453
8454 pub fn unique_lines_case_insensitive(
8455 &mut self,
8456 _: &UniqueLinesCaseInsensitive,
8457 window: &mut Window,
8458 cx: &mut Context<Self>,
8459 ) {
8460 self.manipulate_lines(window, cx, |lines| {
8461 let mut seen = HashSet::default();
8462 lines.retain(|line| seen.insert(line.to_lowercase()));
8463 })
8464 }
8465
8466 pub fn unique_lines_case_sensitive(
8467 &mut self,
8468 _: &UniqueLinesCaseSensitive,
8469 window: &mut Window,
8470 cx: &mut Context<Self>,
8471 ) {
8472 self.manipulate_lines(window, cx, |lines| {
8473 let mut seen = HashSet::default();
8474 lines.retain(|line| seen.insert(*line));
8475 })
8476 }
8477
8478 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8479 let Some(project) = self.project.clone() else {
8480 return;
8481 };
8482 self.reload(project, window, cx)
8483 .detach_and_notify_err(window, cx);
8484 }
8485
8486 pub fn restore_file(
8487 &mut self,
8488 _: &::git::RestoreFile,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) {
8492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8493 let mut buffer_ids = HashSet::default();
8494 let snapshot = self.buffer().read(cx).snapshot(cx);
8495 for selection in self.selections.all::<usize>(cx) {
8496 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8497 }
8498
8499 let buffer = self.buffer().read(cx);
8500 let ranges = buffer_ids
8501 .into_iter()
8502 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8503 .collect::<Vec<_>>();
8504
8505 self.restore_hunks_in_ranges(ranges, window, cx);
8506 }
8507
8508 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8510 let selections = self
8511 .selections
8512 .all(cx)
8513 .into_iter()
8514 .map(|s| s.range())
8515 .collect();
8516 self.restore_hunks_in_ranges(selections, window, cx);
8517 }
8518
8519 pub fn restore_hunks_in_ranges(
8520 &mut self,
8521 ranges: Vec<Range<Point>>,
8522 window: &mut Window,
8523 cx: &mut Context<Editor>,
8524 ) {
8525 let mut revert_changes = HashMap::default();
8526 let chunk_by = self
8527 .snapshot(window, cx)
8528 .hunks_for_ranges(ranges)
8529 .into_iter()
8530 .chunk_by(|hunk| hunk.buffer_id);
8531 for (buffer_id, hunks) in &chunk_by {
8532 let hunks = hunks.collect::<Vec<_>>();
8533 for hunk in &hunks {
8534 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8535 }
8536 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8537 }
8538 drop(chunk_by);
8539 if !revert_changes.is_empty() {
8540 self.transact(window, cx, |editor, window, cx| {
8541 editor.restore(revert_changes, window, cx);
8542 });
8543 }
8544 }
8545
8546 pub fn open_active_item_in_terminal(
8547 &mut self,
8548 _: &OpenInTerminal,
8549 window: &mut Window,
8550 cx: &mut Context<Self>,
8551 ) {
8552 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8553 let project_path = buffer.read(cx).project_path(cx)?;
8554 let project = self.project.as_ref()?.read(cx);
8555 let entry = project.entry_for_path(&project_path, cx)?;
8556 let parent = match &entry.canonical_path {
8557 Some(canonical_path) => canonical_path.to_path_buf(),
8558 None => project.absolute_path(&project_path, cx)?,
8559 }
8560 .parent()?
8561 .to_path_buf();
8562 Some(parent)
8563 }) {
8564 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8565 }
8566 }
8567
8568 fn set_breakpoint_context_menu(
8569 &mut self,
8570 display_row: DisplayRow,
8571 position: Option<Anchor>,
8572 clicked_point: gpui::Point<Pixels>,
8573 window: &mut Window,
8574 cx: &mut Context<Self>,
8575 ) {
8576 if !cx.has_flag::<Debugger>() {
8577 return;
8578 }
8579 let source = self
8580 .buffer
8581 .read(cx)
8582 .snapshot(cx)
8583 .anchor_before(Point::new(display_row.0, 0u32));
8584
8585 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8586
8587 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8588 self,
8589 source,
8590 clicked_point,
8591 context_menu,
8592 window,
8593 cx,
8594 );
8595 }
8596
8597 fn add_edit_breakpoint_block(
8598 &mut self,
8599 anchor: Anchor,
8600 breakpoint: &Breakpoint,
8601 window: &mut Window,
8602 cx: &mut Context<Self>,
8603 ) {
8604 let weak_editor = cx.weak_entity();
8605 let bp_prompt = cx.new(|cx| {
8606 BreakpointPromptEditor::new(weak_editor, anchor, breakpoint.clone(), window, cx)
8607 });
8608
8609 let height = bp_prompt.update(cx, |this, cx| {
8610 this.prompt
8611 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8612 });
8613 let cloned_prompt = bp_prompt.clone();
8614 let blocks = vec![BlockProperties {
8615 style: BlockStyle::Sticky,
8616 placement: BlockPlacement::Above(anchor),
8617 height,
8618 render: Arc::new(move |cx| {
8619 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8620 cloned_prompt.clone().into_any_element()
8621 }),
8622 priority: 0,
8623 }];
8624
8625 let focus_handle = bp_prompt.focus_handle(cx);
8626 window.focus(&focus_handle);
8627
8628 let block_ids = self.insert_blocks(blocks, None, cx);
8629 bp_prompt.update(cx, |prompt, _| {
8630 prompt.add_block_ids(block_ids);
8631 });
8632 }
8633
8634 fn breakpoint_at_cursor_head(
8635 &self,
8636 window: &mut Window,
8637 cx: &mut Context<Self>,
8638 ) -> Option<(Anchor, Breakpoint)> {
8639 let cursor_position: Point = self.selections.newest(cx).head();
8640 self.breakpoint_at_row(cursor_position.row, window, cx)
8641 }
8642
8643 pub(crate) fn breakpoint_at_row(
8644 &self,
8645 row: u32,
8646 window: &mut Window,
8647 cx: &mut Context<Self>,
8648 ) -> Option<(Anchor, Breakpoint)> {
8649 let snapshot = self.snapshot(window, cx);
8650 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8651
8652 let project = self.project.clone()?;
8653
8654 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8655 snapshot
8656 .buffer_snapshot
8657 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8658 })?;
8659
8660 let enclosing_excerpt = breakpoint_position.excerpt_id;
8661 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8662 let buffer_snapshot = buffer.read(cx).snapshot();
8663
8664 let row = buffer_snapshot
8665 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8666 .row;
8667
8668 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8669 let anchor_end = snapshot
8670 .buffer_snapshot
8671 .anchor_after(Point::new(row, line_len));
8672
8673 let bp = self
8674 .breakpoint_store
8675 .as_ref()?
8676 .read_with(cx, |breakpoint_store, cx| {
8677 breakpoint_store
8678 .breakpoints(
8679 &buffer,
8680 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8681 &buffer_snapshot,
8682 cx,
8683 )
8684 .next()
8685 .and_then(|(anchor, bp)| {
8686 let breakpoint_row = buffer_snapshot
8687 .summary_for_anchor::<text::PointUtf16>(anchor)
8688 .row;
8689
8690 if breakpoint_row == row {
8691 snapshot
8692 .buffer_snapshot
8693 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8694 .map(|anchor| (anchor, bp.clone()))
8695 } else {
8696 None
8697 }
8698 })
8699 });
8700 bp
8701 }
8702
8703 pub fn edit_log_breakpoint(
8704 &mut self,
8705 _: &EditLogBreakpoint,
8706 window: &mut Window,
8707 cx: &mut Context<Self>,
8708 ) {
8709 let (anchor, bp) = self
8710 .breakpoint_at_cursor_head(window, cx)
8711 .unwrap_or_else(|| {
8712 let cursor_position: Point = self.selections.newest(cx).head();
8713
8714 let breakpoint_position = self
8715 .snapshot(window, cx)
8716 .display_snapshot
8717 .buffer_snapshot
8718 .anchor_after(Point::new(cursor_position.row, 0));
8719
8720 (
8721 breakpoint_position,
8722 Breakpoint {
8723 message: None,
8724 state: BreakpointState::Enabled,
8725 },
8726 )
8727 });
8728
8729 self.add_edit_breakpoint_block(anchor, &bp, window, cx);
8730 }
8731
8732 pub fn enable_breakpoint(
8733 &mut self,
8734 _: &crate::actions::EnableBreakpoint,
8735 window: &mut Window,
8736 cx: &mut Context<Self>,
8737 ) {
8738 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8739 if breakpoint.is_disabled() {
8740 self.edit_breakpoint_at_anchor(
8741 anchor,
8742 breakpoint,
8743 BreakpointEditAction::InvertState,
8744 cx,
8745 );
8746 }
8747 }
8748 }
8749
8750 pub fn disable_breakpoint(
8751 &mut self,
8752 _: &crate::actions::DisableBreakpoint,
8753 window: &mut Window,
8754 cx: &mut Context<Self>,
8755 ) {
8756 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8757 if breakpoint.is_enabled() {
8758 self.edit_breakpoint_at_anchor(
8759 anchor,
8760 breakpoint,
8761 BreakpointEditAction::InvertState,
8762 cx,
8763 );
8764 }
8765 }
8766 }
8767
8768 pub fn toggle_breakpoint(
8769 &mut self,
8770 _: &crate::actions::ToggleBreakpoint,
8771 window: &mut Window,
8772 cx: &mut Context<Self>,
8773 ) {
8774 let edit_action = BreakpointEditAction::Toggle;
8775
8776 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8777 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8778 } else {
8779 let cursor_position: Point = self.selections.newest(cx).head();
8780
8781 let breakpoint_position = self
8782 .snapshot(window, cx)
8783 .display_snapshot
8784 .buffer_snapshot
8785 .anchor_after(Point::new(cursor_position.row, 0));
8786
8787 self.edit_breakpoint_at_anchor(
8788 breakpoint_position,
8789 Breakpoint::new_standard(),
8790 edit_action,
8791 cx,
8792 );
8793 }
8794 }
8795
8796 pub fn edit_breakpoint_at_anchor(
8797 &mut self,
8798 breakpoint_position: Anchor,
8799 breakpoint: Breakpoint,
8800 edit_action: BreakpointEditAction,
8801 cx: &mut Context<Self>,
8802 ) {
8803 let Some(breakpoint_store) = &self.breakpoint_store else {
8804 return;
8805 };
8806
8807 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8808 if breakpoint_position == Anchor::min() {
8809 self.buffer()
8810 .read(cx)
8811 .excerpt_buffer_ids()
8812 .into_iter()
8813 .next()
8814 } else {
8815 None
8816 }
8817 }) else {
8818 return;
8819 };
8820
8821 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8822 return;
8823 };
8824
8825 breakpoint_store.update(cx, |breakpoint_store, cx| {
8826 breakpoint_store.toggle_breakpoint(
8827 buffer,
8828 (breakpoint_position.text_anchor, breakpoint),
8829 edit_action,
8830 cx,
8831 );
8832 });
8833
8834 cx.notify();
8835 }
8836
8837 #[cfg(any(test, feature = "test-support"))]
8838 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8839 self.breakpoint_store.clone()
8840 }
8841
8842 pub fn prepare_restore_change(
8843 &self,
8844 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8845 hunk: &MultiBufferDiffHunk,
8846 cx: &mut App,
8847 ) -> Option<()> {
8848 if hunk.is_created_file() {
8849 return None;
8850 }
8851 let buffer = self.buffer.read(cx);
8852 let diff = buffer.diff_for(hunk.buffer_id)?;
8853 let buffer = buffer.buffer(hunk.buffer_id)?;
8854 let buffer = buffer.read(cx);
8855 let original_text = diff
8856 .read(cx)
8857 .base_text()
8858 .as_rope()
8859 .slice(hunk.diff_base_byte_range.clone());
8860 let buffer_snapshot = buffer.snapshot();
8861 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8862 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8863 probe
8864 .0
8865 .start
8866 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8867 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8868 }) {
8869 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8870 Some(())
8871 } else {
8872 None
8873 }
8874 }
8875
8876 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8877 self.manipulate_lines(window, cx, |lines| lines.reverse())
8878 }
8879
8880 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8881 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8882 }
8883
8884 fn manipulate_lines<Fn>(
8885 &mut self,
8886 window: &mut Window,
8887 cx: &mut Context<Self>,
8888 mut callback: Fn,
8889 ) where
8890 Fn: FnMut(&mut Vec<&str>),
8891 {
8892 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8893
8894 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8895 let buffer = self.buffer.read(cx).snapshot(cx);
8896
8897 let mut edits = Vec::new();
8898
8899 let selections = self.selections.all::<Point>(cx);
8900 let mut selections = selections.iter().peekable();
8901 let mut contiguous_row_selections = Vec::new();
8902 let mut new_selections = Vec::new();
8903 let mut added_lines = 0;
8904 let mut removed_lines = 0;
8905
8906 while let Some(selection) = selections.next() {
8907 let (start_row, end_row) = consume_contiguous_rows(
8908 &mut contiguous_row_selections,
8909 selection,
8910 &display_map,
8911 &mut selections,
8912 );
8913
8914 let start_point = Point::new(start_row.0, 0);
8915 let end_point = Point::new(
8916 end_row.previous_row().0,
8917 buffer.line_len(end_row.previous_row()),
8918 );
8919 let text = buffer
8920 .text_for_range(start_point..end_point)
8921 .collect::<String>();
8922
8923 let mut lines = text.split('\n').collect_vec();
8924
8925 let lines_before = lines.len();
8926 callback(&mut lines);
8927 let lines_after = lines.len();
8928
8929 edits.push((start_point..end_point, lines.join("\n")));
8930
8931 // Selections must change based on added and removed line count
8932 let start_row =
8933 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
8934 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
8935 new_selections.push(Selection {
8936 id: selection.id,
8937 start: start_row,
8938 end: end_row,
8939 goal: SelectionGoal::None,
8940 reversed: selection.reversed,
8941 });
8942
8943 if lines_after > lines_before {
8944 added_lines += lines_after - lines_before;
8945 } else if lines_before > lines_after {
8946 removed_lines += lines_before - lines_after;
8947 }
8948 }
8949
8950 self.transact(window, cx, |this, window, cx| {
8951 let buffer = this.buffer.update(cx, |buffer, cx| {
8952 buffer.edit(edits, None, cx);
8953 buffer.snapshot(cx)
8954 });
8955
8956 // Recalculate offsets on newly edited buffer
8957 let new_selections = new_selections
8958 .iter()
8959 .map(|s| {
8960 let start_point = Point::new(s.start.0, 0);
8961 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
8962 Selection {
8963 id: s.id,
8964 start: buffer.point_to_offset(start_point),
8965 end: buffer.point_to_offset(end_point),
8966 goal: s.goal,
8967 reversed: s.reversed,
8968 }
8969 })
8970 .collect();
8971
8972 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8973 s.select(new_selections);
8974 });
8975
8976 this.request_autoscroll(Autoscroll::fit(), cx);
8977 });
8978 }
8979
8980 pub fn convert_to_upper_case(
8981 &mut self,
8982 _: &ConvertToUpperCase,
8983 window: &mut Window,
8984 cx: &mut Context<Self>,
8985 ) {
8986 self.manipulate_text(window, cx, |text| text.to_uppercase())
8987 }
8988
8989 pub fn convert_to_lower_case(
8990 &mut self,
8991 _: &ConvertToLowerCase,
8992 window: &mut Window,
8993 cx: &mut Context<Self>,
8994 ) {
8995 self.manipulate_text(window, cx, |text| text.to_lowercase())
8996 }
8997
8998 pub fn convert_to_title_case(
8999 &mut self,
9000 _: &ConvertToTitleCase,
9001 window: &mut Window,
9002 cx: &mut Context<Self>,
9003 ) {
9004 self.manipulate_text(window, cx, |text| {
9005 text.split('\n')
9006 .map(|line| line.to_case(Case::Title))
9007 .join("\n")
9008 })
9009 }
9010
9011 pub fn convert_to_snake_case(
9012 &mut self,
9013 _: &ConvertToSnakeCase,
9014 window: &mut Window,
9015 cx: &mut Context<Self>,
9016 ) {
9017 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9018 }
9019
9020 pub fn convert_to_kebab_case(
9021 &mut self,
9022 _: &ConvertToKebabCase,
9023 window: &mut Window,
9024 cx: &mut Context<Self>,
9025 ) {
9026 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9027 }
9028
9029 pub fn convert_to_upper_camel_case(
9030 &mut self,
9031 _: &ConvertToUpperCamelCase,
9032 window: &mut Window,
9033 cx: &mut Context<Self>,
9034 ) {
9035 self.manipulate_text(window, cx, |text| {
9036 text.split('\n')
9037 .map(|line| line.to_case(Case::UpperCamel))
9038 .join("\n")
9039 })
9040 }
9041
9042 pub fn convert_to_lower_camel_case(
9043 &mut self,
9044 _: &ConvertToLowerCamelCase,
9045 window: &mut Window,
9046 cx: &mut Context<Self>,
9047 ) {
9048 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9049 }
9050
9051 pub fn convert_to_opposite_case(
9052 &mut self,
9053 _: &ConvertToOppositeCase,
9054 window: &mut Window,
9055 cx: &mut Context<Self>,
9056 ) {
9057 self.manipulate_text(window, cx, |text| {
9058 text.chars()
9059 .fold(String::with_capacity(text.len()), |mut t, c| {
9060 if c.is_uppercase() {
9061 t.extend(c.to_lowercase());
9062 } else {
9063 t.extend(c.to_uppercase());
9064 }
9065 t
9066 })
9067 })
9068 }
9069
9070 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9071 where
9072 Fn: FnMut(&str) -> String,
9073 {
9074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9075 let buffer = self.buffer.read(cx).snapshot(cx);
9076
9077 let mut new_selections = Vec::new();
9078 let mut edits = Vec::new();
9079 let mut selection_adjustment = 0i32;
9080
9081 for selection in self.selections.all::<usize>(cx) {
9082 let selection_is_empty = selection.is_empty();
9083
9084 let (start, end) = if selection_is_empty {
9085 let word_range = movement::surrounding_word(
9086 &display_map,
9087 selection.start.to_display_point(&display_map),
9088 );
9089 let start = word_range.start.to_offset(&display_map, Bias::Left);
9090 let end = word_range.end.to_offset(&display_map, Bias::Left);
9091 (start, end)
9092 } else {
9093 (selection.start, selection.end)
9094 };
9095
9096 let text = buffer.text_for_range(start..end).collect::<String>();
9097 let old_length = text.len() as i32;
9098 let text = callback(&text);
9099
9100 new_selections.push(Selection {
9101 start: (start as i32 - selection_adjustment) as usize,
9102 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9103 goal: SelectionGoal::None,
9104 ..selection
9105 });
9106
9107 selection_adjustment += old_length - text.len() as i32;
9108
9109 edits.push((start..end, text));
9110 }
9111
9112 self.transact(window, cx, |this, window, cx| {
9113 this.buffer.update(cx, |buffer, cx| {
9114 buffer.edit(edits, None, cx);
9115 });
9116
9117 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9118 s.select(new_selections);
9119 });
9120
9121 this.request_autoscroll(Autoscroll::fit(), cx);
9122 });
9123 }
9124
9125 pub fn duplicate(
9126 &mut self,
9127 upwards: bool,
9128 whole_lines: bool,
9129 window: &mut Window,
9130 cx: &mut Context<Self>,
9131 ) {
9132 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9133
9134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9135 let buffer = &display_map.buffer_snapshot;
9136 let selections = self.selections.all::<Point>(cx);
9137
9138 let mut edits = Vec::new();
9139 let mut selections_iter = selections.iter().peekable();
9140 while let Some(selection) = selections_iter.next() {
9141 let mut rows = selection.spanned_rows(false, &display_map);
9142 // duplicate line-wise
9143 if whole_lines || selection.start == selection.end {
9144 // Avoid duplicating the same lines twice.
9145 while let Some(next_selection) = selections_iter.peek() {
9146 let next_rows = next_selection.spanned_rows(false, &display_map);
9147 if next_rows.start < rows.end {
9148 rows.end = next_rows.end;
9149 selections_iter.next().unwrap();
9150 } else {
9151 break;
9152 }
9153 }
9154
9155 // Copy the text from the selected row region and splice it either at the start
9156 // or end of the region.
9157 let start = Point::new(rows.start.0, 0);
9158 let end = Point::new(
9159 rows.end.previous_row().0,
9160 buffer.line_len(rows.end.previous_row()),
9161 );
9162 let text = buffer
9163 .text_for_range(start..end)
9164 .chain(Some("\n"))
9165 .collect::<String>();
9166 let insert_location = if upwards {
9167 Point::new(rows.end.0, 0)
9168 } else {
9169 start
9170 };
9171 edits.push((insert_location..insert_location, text));
9172 } else {
9173 // duplicate character-wise
9174 let start = selection.start;
9175 let end = selection.end;
9176 let text = buffer.text_for_range(start..end).collect::<String>();
9177 edits.push((selection.end..selection.end, text));
9178 }
9179 }
9180
9181 self.transact(window, cx, |this, _, cx| {
9182 this.buffer.update(cx, |buffer, cx| {
9183 buffer.edit(edits, None, cx);
9184 });
9185
9186 this.request_autoscroll(Autoscroll::fit(), cx);
9187 });
9188 }
9189
9190 pub fn duplicate_line_up(
9191 &mut self,
9192 _: &DuplicateLineUp,
9193 window: &mut Window,
9194 cx: &mut Context<Self>,
9195 ) {
9196 self.duplicate(true, true, window, cx);
9197 }
9198
9199 pub fn duplicate_line_down(
9200 &mut self,
9201 _: &DuplicateLineDown,
9202 window: &mut Window,
9203 cx: &mut Context<Self>,
9204 ) {
9205 self.duplicate(false, true, window, cx);
9206 }
9207
9208 pub fn duplicate_selection(
9209 &mut self,
9210 _: &DuplicateSelection,
9211 window: &mut Window,
9212 cx: &mut Context<Self>,
9213 ) {
9214 self.duplicate(false, false, window, cx);
9215 }
9216
9217 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9218 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9219
9220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9221 let buffer = self.buffer.read(cx).snapshot(cx);
9222
9223 let mut edits = Vec::new();
9224 let mut unfold_ranges = Vec::new();
9225 let mut refold_creases = Vec::new();
9226
9227 let selections = self.selections.all::<Point>(cx);
9228 let mut selections = selections.iter().peekable();
9229 let mut contiguous_row_selections = Vec::new();
9230 let mut new_selections = Vec::new();
9231
9232 while let Some(selection) = selections.next() {
9233 // Find all the selections that span a contiguous row range
9234 let (start_row, end_row) = consume_contiguous_rows(
9235 &mut contiguous_row_selections,
9236 selection,
9237 &display_map,
9238 &mut selections,
9239 );
9240
9241 // Move the text spanned by the row range to be before the line preceding the row range
9242 if start_row.0 > 0 {
9243 let range_to_move = Point::new(
9244 start_row.previous_row().0,
9245 buffer.line_len(start_row.previous_row()),
9246 )
9247 ..Point::new(
9248 end_row.previous_row().0,
9249 buffer.line_len(end_row.previous_row()),
9250 );
9251 let insertion_point = display_map
9252 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9253 .0;
9254
9255 // Don't move lines across excerpts
9256 if buffer
9257 .excerpt_containing(insertion_point..range_to_move.end)
9258 .is_some()
9259 {
9260 let text = buffer
9261 .text_for_range(range_to_move.clone())
9262 .flat_map(|s| s.chars())
9263 .skip(1)
9264 .chain(['\n'])
9265 .collect::<String>();
9266
9267 edits.push((
9268 buffer.anchor_after(range_to_move.start)
9269 ..buffer.anchor_before(range_to_move.end),
9270 String::new(),
9271 ));
9272 let insertion_anchor = buffer.anchor_after(insertion_point);
9273 edits.push((insertion_anchor..insertion_anchor, text));
9274
9275 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9276
9277 // Move selections up
9278 new_selections.extend(contiguous_row_selections.drain(..).map(
9279 |mut selection| {
9280 selection.start.row -= row_delta;
9281 selection.end.row -= row_delta;
9282 selection
9283 },
9284 ));
9285
9286 // Move folds up
9287 unfold_ranges.push(range_to_move.clone());
9288 for fold in display_map.folds_in_range(
9289 buffer.anchor_before(range_to_move.start)
9290 ..buffer.anchor_after(range_to_move.end),
9291 ) {
9292 let mut start = fold.range.start.to_point(&buffer);
9293 let mut end = fold.range.end.to_point(&buffer);
9294 start.row -= row_delta;
9295 end.row -= row_delta;
9296 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9297 }
9298 }
9299 }
9300
9301 // If we didn't move line(s), preserve the existing selections
9302 new_selections.append(&mut contiguous_row_selections);
9303 }
9304
9305 self.transact(window, cx, |this, window, cx| {
9306 this.unfold_ranges(&unfold_ranges, true, true, cx);
9307 this.buffer.update(cx, |buffer, cx| {
9308 for (range, text) in edits {
9309 buffer.edit([(range, text)], None, cx);
9310 }
9311 });
9312 this.fold_creases(refold_creases, true, window, cx);
9313 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9314 s.select(new_selections);
9315 })
9316 });
9317 }
9318
9319 pub fn move_line_down(
9320 &mut self,
9321 _: &MoveLineDown,
9322 window: &mut Window,
9323 cx: &mut Context<Self>,
9324 ) {
9325 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9326
9327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9328 let buffer = self.buffer.read(cx).snapshot(cx);
9329
9330 let mut edits = Vec::new();
9331 let mut unfold_ranges = Vec::new();
9332 let mut refold_creases = Vec::new();
9333
9334 let selections = self.selections.all::<Point>(cx);
9335 let mut selections = selections.iter().peekable();
9336 let mut contiguous_row_selections = Vec::new();
9337 let mut new_selections = Vec::new();
9338
9339 while let Some(selection) = selections.next() {
9340 // Find all the selections that span a contiguous row range
9341 let (start_row, end_row) = consume_contiguous_rows(
9342 &mut contiguous_row_selections,
9343 selection,
9344 &display_map,
9345 &mut selections,
9346 );
9347
9348 // Move the text spanned by the row range to be after the last line of the row range
9349 if end_row.0 <= buffer.max_point().row {
9350 let range_to_move =
9351 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9352 let insertion_point = display_map
9353 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9354 .0;
9355
9356 // Don't move lines across excerpt boundaries
9357 if buffer
9358 .excerpt_containing(range_to_move.start..insertion_point)
9359 .is_some()
9360 {
9361 let mut text = String::from("\n");
9362 text.extend(buffer.text_for_range(range_to_move.clone()));
9363 text.pop(); // Drop trailing newline
9364 edits.push((
9365 buffer.anchor_after(range_to_move.start)
9366 ..buffer.anchor_before(range_to_move.end),
9367 String::new(),
9368 ));
9369 let insertion_anchor = buffer.anchor_after(insertion_point);
9370 edits.push((insertion_anchor..insertion_anchor, text));
9371
9372 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9373
9374 // Move selections down
9375 new_selections.extend(contiguous_row_selections.drain(..).map(
9376 |mut selection| {
9377 selection.start.row += row_delta;
9378 selection.end.row += row_delta;
9379 selection
9380 },
9381 ));
9382
9383 // Move folds down
9384 unfold_ranges.push(range_to_move.clone());
9385 for fold in display_map.folds_in_range(
9386 buffer.anchor_before(range_to_move.start)
9387 ..buffer.anchor_after(range_to_move.end),
9388 ) {
9389 let mut start = fold.range.start.to_point(&buffer);
9390 let mut end = fold.range.end.to_point(&buffer);
9391 start.row += row_delta;
9392 end.row += row_delta;
9393 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9394 }
9395 }
9396 }
9397
9398 // If we didn't move line(s), preserve the existing selections
9399 new_selections.append(&mut contiguous_row_selections);
9400 }
9401
9402 self.transact(window, cx, |this, window, cx| {
9403 this.unfold_ranges(&unfold_ranges, true, true, cx);
9404 this.buffer.update(cx, |buffer, cx| {
9405 for (range, text) in edits {
9406 buffer.edit([(range, text)], None, cx);
9407 }
9408 });
9409 this.fold_creases(refold_creases, true, window, cx);
9410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9411 s.select(new_selections)
9412 });
9413 });
9414 }
9415
9416 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9417 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9418 let text_layout_details = &self.text_layout_details(window);
9419 self.transact(window, cx, |this, window, cx| {
9420 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9421 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9422 let line_mode = s.line_mode;
9423 s.move_with(|display_map, selection| {
9424 if !selection.is_empty() || line_mode {
9425 return;
9426 }
9427
9428 let mut head = selection.head();
9429 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9430 if head.column() == display_map.line_len(head.row()) {
9431 transpose_offset = display_map
9432 .buffer_snapshot
9433 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9434 }
9435
9436 if transpose_offset == 0 {
9437 return;
9438 }
9439
9440 *head.column_mut() += 1;
9441 head = display_map.clip_point(head, Bias::Right);
9442 let goal = SelectionGoal::HorizontalPosition(
9443 display_map
9444 .x_for_display_point(head, text_layout_details)
9445 .into(),
9446 );
9447 selection.collapse_to(head, goal);
9448
9449 let transpose_start = display_map
9450 .buffer_snapshot
9451 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9452 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9453 let transpose_end = display_map
9454 .buffer_snapshot
9455 .clip_offset(transpose_offset + 1, Bias::Right);
9456 if let Some(ch) =
9457 display_map.buffer_snapshot.chars_at(transpose_start).next()
9458 {
9459 edits.push((transpose_start..transpose_offset, String::new()));
9460 edits.push((transpose_end..transpose_end, ch.to_string()));
9461 }
9462 }
9463 });
9464 edits
9465 });
9466 this.buffer
9467 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9468 let selections = this.selections.all::<usize>(cx);
9469 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9470 s.select(selections);
9471 });
9472 });
9473 }
9474
9475 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9476 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9477 self.rewrap_impl(RewrapOptions::default(), cx)
9478 }
9479
9480 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9481 let buffer = self.buffer.read(cx).snapshot(cx);
9482 let selections = self.selections.all::<Point>(cx);
9483 let mut selections = selections.iter().peekable();
9484
9485 let mut edits = Vec::new();
9486 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9487
9488 while let Some(selection) = selections.next() {
9489 let mut start_row = selection.start.row;
9490 let mut end_row = selection.end.row;
9491
9492 // Skip selections that overlap with a range that has already been rewrapped.
9493 let selection_range = start_row..end_row;
9494 if rewrapped_row_ranges
9495 .iter()
9496 .any(|range| range.overlaps(&selection_range))
9497 {
9498 continue;
9499 }
9500
9501 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9502
9503 // Since not all lines in the selection may be at the same indent
9504 // level, choose the indent size that is the most common between all
9505 // of the lines.
9506 //
9507 // If there is a tie, we use the deepest indent.
9508 let (indent_size, indent_end) = {
9509 let mut indent_size_occurrences = HashMap::default();
9510 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9511
9512 for row in start_row..=end_row {
9513 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9514 rows_by_indent_size.entry(indent).or_default().push(row);
9515 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9516 }
9517
9518 let indent_size = indent_size_occurrences
9519 .into_iter()
9520 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9521 .map(|(indent, _)| indent)
9522 .unwrap_or_default();
9523 let row = rows_by_indent_size[&indent_size][0];
9524 let indent_end = Point::new(row, indent_size.len);
9525
9526 (indent_size, indent_end)
9527 };
9528
9529 let mut line_prefix = indent_size.chars().collect::<String>();
9530
9531 let mut inside_comment = false;
9532 if let Some(comment_prefix) =
9533 buffer
9534 .language_scope_at(selection.head())
9535 .and_then(|language| {
9536 language
9537 .line_comment_prefixes()
9538 .iter()
9539 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9540 .cloned()
9541 })
9542 {
9543 line_prefix.push_str(&comment_prefix);
9544 inside_comment = true;
9545 }
9546
9547 let language_settings = buffer.language_settings_at(selection.head(), cx);
9548 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9549 RewrapBehavior::InComments => inside_comment,
9550 RewrapBehavior::InSelections => !selection.is_empty(),
9551 RewrapBehavior::Anywhere => true,
9552 };
9553
9554 let should_rewrap = options.override_language_settings
9555 || allow_rewrap_based_on_language
9556 || self.hard_wrap.is_some();
9557 if !should_rewrap {
9558 continue;
9559 }
9560
9561 if selection.is_empty() {
9562 'expand_upwards: while start_row > 0 {
9563 let prev_row = start_row - 1;
9564 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9565 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9566 {
9567 start_row = prev_row;
9568 } else {
9569 break 'expand_upwards;
9570 }
9571 }
9572
9573 'expand_downwards: while end_row < buffer.max_point().row {
9574 let next_row = end_row + 1;
9575 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9576 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9577 {
9578 end_row = next_row;
9579 } else {
9580 break 'expand_downwards;
9581 }
9582 }
9583 }
9584
9585 let start = Point::new(start_row, 0);
9586 let start_offset = start.to_offset(&buffer);
9587 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9588 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9589 let Some(lines_without_prefixes) = selection_text
9590 .lines()
9591 .map(|line| {
9592 line.strip_prefix(&line_prefix)
9593 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9594 .ok_or_else(|| {
9595 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9596 })
9597 })
9598 .collect::<Result<Vec<_>, _>>()
9599 .log_err()
9600 else {
9601 continue;
9602 };
9603
9604 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9605 buffer
9606 .language_settings_at(Point::new(start_row, 0), cx)
9607 .preferred_line_length as usize
9608 });
9609 let wrapped_text = wrap_with_prefix(
9610 line_prefix,
9611 lines_without_prefixes.join("\n"),
9612 wrap_column,
9613 tab_size,
9614 options.preserve_existing_whitespace,
9615 );
9616
9617 // TODO: should always use char-based diff while still supporting cursor behavior that
9618 // matches vim.
9619 let mut diff_options = DiffOptions::default();
9620 if options.override_language_settings {
9621 diff_options.max_word_diff_len = 0;
9622 diff_options.max_word_diff_line_count = 0;
9623 } else {
9624 diff_options.max_word_diff_len = usize::MAX;
9625 diff_options.max_word_diff_line_count = usize::MAX;
9626 }
9627
9628 for (old_range, new_text) in
9629 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9630 {
9631 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9632 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9633 edits.push((edit_start..edit_end, new_text));
9634 }
9635
9636 rewrapped_row_ranges.push(start_row..=end_row);
9637 }
9638
9639 self.buffer
9640 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9641 }
9642
9643 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9644 let mut text = String::new();
9645 let buffer = self.buffer.read(cx).snapshot(cx);
9646 let mut selections = self.selections.all::<Point>(cx);
9647 let mut clipboard_selections = Vec::with_capacity(selections.len());
9648 {
9649 let max_point = buffer.max_point();
9650 let mut is_first = true;
9651 for selection in &mut selections {
9652 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9653 if is_entire_line {
9654 selection.start = Point::new(selection.start.row, 0);
9655 if !selection.is_empty() && selection.end.column == 0 {
9656 selection.end = cmp::min(max_point, selection.end);
9657 } else {
9658 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9659 }
9660 selection.goal = SelectionGoal::None;
9661 }
9662 if is_first {
9663 is_first = false;
9664 } else {
9665 text += "\n";
9666 }
9667 let mut len = 0;
9668 for chunk in buffer.text_for_range(selection.start..selection.end) {
9669 text.push_str(chunk);
9670 len += chunk.len();
9671 }
9672 clipboard_selections.push(ClipboardSelection {
9673 len,
9674 is_entire_line,
9675 first_line_indent: buffer
9676 .indent_size_for_line(MultiBufferRow(selection.start.row))
9677 .len,
9678 });
9679 }
9680 }
9681
9682 self.transact(window, cx, |this, window, cx| {
9683 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9684 s.select(selections);
9685 });
9686 this.insert("", window, cx);
9687 });
9688 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9689 }
9690
9691 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9692 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9693 let item = self.cut_common(window, cx);
9694 cx.write_to_clipboard(item);
9695 }
9696
9697 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9698 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9699 self.change_selections(None, window, cx, |s| {
9700 s.move_with(|snapshot, sel| {
9701 if sel.is_empty() {
9702 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9703 }
9704 });
9705 });
9706 let item = self.cut_common(window, cx);
9707 cx.set_global(KillRing(item))
9708 }
9709
9710 pub fn kill_ring_yank(
9711 &mut self,
9712 _: &KillRingYank,
9713 window: &mut Window,
9714 cx: &mut Context<Self>,
9715 ) {
9716 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9717 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9718 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9719 (kill_ring.text().to_string(), kill_ring.metadata_json())
9720 } else {
9721 return;
9722 }
9723 } else {
9724 return;
9725 };
9726 self.do_paste(&text, metadata, false, window, cx);
9727 }
9728
9729 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9730 self.do_copy(true, cx);
9731 }
9732
9733 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9734 self.do_copy(false, cx);
9735 }
9736
9737 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9738 let selections = self.selections.all::<Point>(cx);
9739 let buffer = self.buffer.read(cx).read(cx);
9740 let mut text = String::new();
9741
9742 let mut clipboard_selections = Vec::with_capacity(selections.len());
9743 {
9744 let max_point = buffer.max_point();
9745 let mut is_first = true;
9746 for selection in &selections {
9747 let mut start = selection.start;
9748 let mut end = selection.end;
9749 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9750 if is_entire_line {
9751 start = Point::new(start.row, 0);
9752 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9753 }
9754
9755 let mut trimmed_selections = Vec::new();
9756 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9757 let row = MultiBufferRow(start.row);
9758 let first_indent = buffer.indent_size_for_line(row);
9759 if first_indent.len == 0 || start.column > first_indent.len {
9760 trimmed_selections.push(start..end);
9761 } else {
9762 trimmed_selections.push(
9763 Point::new(row.0, first_indent.len)
9764 ..Point::new(row.0, buffer.line_len(row)),
9765 );
9766 for row in start.row + 1..=end.row {
9767 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9768 if row_indent_size.len >= first_indent.len {
9769 trimmed_selections.push(
9770 Point::new(row, first_indent.len)
9771 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9772 );
9773 } else {
9774 trimmed_selections.clear();
9775 trimmed_selections.push(start..end);
9776 break;
9777 }
9778 }
9779 }
9780 } else {
9781 trimmed_selections.push(start..end);
9782 }
9783
9784 for trimmed_range in trimmed_selections {
9785 if is_first {
9786 is_first = false;
9787 } else {
9788 text += "\n";
9789 }
9790 let mut len = 0;
9791 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9792 text.push_str(chunk);
9793 len += chunk.len();
9794 }
9795 clipboard_selections.push(ClipboardSelection {
9796 len,
9797 is_entire_line,
9798 first_line_indent: buffer
9799 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9800 .len,
9801 });
9802 }
9803 }
9804 }
9805
9806 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9807 text,
9808 clipboard_selections,
9809 ));
9810 }
9811
9812 pub fn do_paste(
9813 &mut self,
9814 text: &String,
9815 clipboard_selections: Option<Vec<ClipboardSelection>>,
9816 handle_entire_lines: bool,
9817 window: &mut Window,
9818 cx: &mut Context<Self>,
9819 ) {
9820 if self.read_only(cx) {
9821 return;
9822 }
9823
9824 let clipboard_text = Cow::Borrowed(text);
9825
9826 self.transact(window, cx, |this, window, cx| {
9827 if let Some(mut clipboard_selections) = clipboard_selections {
9828 let old_selections = this.selections.all::<usize>(cx);
9829 let all_selections_were_entire_line =
9830 clipboard_selections.iter().all(|s| s.is_entire_line);
9831 let first_selection_indent_column =
9832 clipboard_selections.first().map(|s| s.first_line_indent);
9833 if clipboard_selections.len() != old_selections.len() {
9834 clipboard_selections.drain(..);
9835 }
9836 let cursor_offset = this.selections.last::<usize>(cx).head();
9837 let mut auto_indent_on_paste = true;
9838
9839 this.buffer.update(cx, |buffer, cx| {
9840 let snapshot = buffer.read(cx);
9841 auto_indent_on_paste = snapshot
9842 .language_settings_at(cursor_offset, cx)
9843 .auto_indent_on_paste;
9844
9845 let mut start_offset = 0;
9846 let mut edits = Vec::new();
9847 let mut original_indent_columns = Vec::new();
9848 for (ix, selection) in old_selections.iter().enumerate() {
9849 let to_insert;
9850 let entire_line;
9851 let original_indent_column;
9852 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9853 let end_offset = start_offset + clipboard_selection.len;
9854 to_insert = &clipboard_text[start_offset..end_offset];
9855 entire_line = clipboard_selection.is_entire_line;
9856 start_offset = end_offset + 1;
9857 original_indent_column = Some(clipboard_selection.first_line_indent);
9858 } else {
9859 to_insert = clipboard_text.as_str();
9860 entire_line = all_selections_were_entire_line;
9861 original_indent_column = first_selection_indent_column
9862 }
9863
9864 // If the corresponding selection was empty when this slice of the
9865 // clipboard text was written, then the entire line containing the
9866 // selection was copied. If this selection is also currently empty,
9867 // then paste the line before the current line of the buffer.
9868 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9869 let column = selection.start.to_point(&snapshot).column as usize;
9870 let line_start = selection.start - column;
9871 line_start..line_start
9872 } else {
9873 selection.range()
9874 };
9875
9876 edits.push((range, to_insert));
9877 original_indent_columns.push(original_indent_column);
9878 }
9879 drop(snapshot);
9880
9881 buffer.edit(
9882 edits,
9883 if auto_indent_on_paste {
9884 Some(AutoindentMode::Block {
9885 original_indent_columns,
9886 })
9887 } else {
9888 None
9889 },
9890 cx,
9891 );
9892 });
9893
9894 let selections = this.selections.all::<usize>(cx);
9895 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9896 s.select(selections)
9897 });
9898 } else {
9899 this.insert(&clipboard_text, window, cx);
9900 }
9901 });
9902 }
9903
9904 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9905 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9906 if let Some(item) = cx.read_from_clipboard() {
9907 let entries = item.entries();
9908
9909 match entries.first() {
9910 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9911 // of all the pasted entries.
9912 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9913 .do_paste(
9914 clipboard_string.text(),
9915 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9916 true,
9917 window,
9918 cx,
9919 ),
9920 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9921 }
9922 }
9923 }
9924
9925 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9926 if self.read_only(cx) {
9927 return;
9928 }
9929
9930 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9931
9932 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
9933 if let Some((selections, _)) =
9934 self.selection_history.transaction(transaction_id).cloned()
9935 {
9936 self.change_selections(None, window, cx, |s| {
9937 s.select_anchors(selections.to_vec());
9938 });
9939 } else {
9940 log::error!(
9941 "No entry in selection_history found for undo. \
9942 This may correspond to a bug where undo does not update the selection. \
9943 If this is occurring, please add details to \
9944 https://github.com/zed-industries/zed/issues/22692"
9945 );
9946 }
9947 self.request_autoscroll(Autoscroll::fit(), cx);
9948 self.unmark_text(window, cx);
9949 self.refresh_inline_completion(true, false, window, cx);
9950 cx.emit(EditorEvent::Edited { transaction_id });
9951 cx.emit(EditorEvent::TransactionUndone { transaction_id });
9952 }
9953 }
9954
9955 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
9956 if self.read_only(cx) {
9957 return;
9958 }
9959
9960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9961
9962 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
9963 if let Some((_, Some(selections))) =
9964 self.selection_history.transaction(transaction_id).cloned()
9965 {
9966 self.change_selections(None, window, cx, |s| {
9967 s.select_anchors(selections.to_vec());
9968 });
9969 } else {
9970 log::error!(
9971 "No entry in selection_history found for redo. \
9972 This may correspond to a bug where undo does not update the selection. \
9973 If this is occurring, please add details to \
9974 https://github.com/zed-industries/zed/issues/22692"
9975 );
9976 }
9977 self.request_autoscroll(Autoscroll::fit(), cx);
9978 self.unmark_text(window, cx);
9979 self.refresh_inline_completion(true, false, window, cx);
9980 cx.emit(EditorEvent::Edited { transaction_id });
9981 }
9982 }
9983
9984 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
9985 self.buffer
9986 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
9987 }
9988
9989 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
9990 self.buffer
9991 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
9992 }
9993
9994 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
9995 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
9996 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9997 let line_mode = s.line_mode;
9998 s.move_with(|map, selection| {
9999 let cursor = if selection.is_empty() && !line_mode {
10000 movement::left(map, selection.start)
10001 } else {
10002 selection.start
10003 };
10004 selection.collapse_to(cursor, SelectionGoal::None);
10005 });
10006 })
10007 }
10008
10009 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10011 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10012 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10013 })
10014 }
10015
10016 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10017 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10018 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10019 let line_mode = s.line_mode;
10020 s.move_with(|map, selection| {
10021 let cursor = if selection.is_empty() && !line_mode {
10022 movement::right(map, selection.end)
10023 } else {
10024 selection.end
10025 };
10026 selection.collapse_to(cursor, SelectionGoal::None)
10027 });
10028 })
10029 }
10030
10031 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10032 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10033 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10034 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10035 })
10036 }
10037
10038 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10039 if self.take_rename(true, window, cx).is_some() {
10040 return;
10041 }
10042
10043 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10044 cx.propagate();
10045 return;
10046 }
10047
10048 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10049
10050 let text_layout_details = &self.text_layout_details(window);
10051 let selection_count = self.selections.count();
10052 let first_selection = self.selections.first_anchor();
10053
10054 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10055 let line_mode = s.line_mode;
10056 s.move_with(|map, selection| {
10057 if !selection.is_empty() && !line_mode {
10058 selection.goal = SelectionGoal::None;
10059 }
10060 let (cursor, goal) = movement::up(
10061 map,
10062 selection.start,
10063 selection.goal,
10064 false,
10065 text_layout_details,
10066 );
10067 selection.collapse_to(cursor, goal);
10068 });
10069 });
10070
10071 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10072 {
10073 cx.propagate();
10074 }
10075 }
10076
10077 pub fn move_up_by_lines(
10078 &mut self,
10079 action: &MoveUpByLines,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 if self.take_rename(true, window, cx).is_some() {
10084 return;
10085 }
10086
10087 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10088 cx.propagate();
10089 return;
10090 }
10091
10092 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10093
10094 let text_layout_details = &self.text_layout_details(window);
10095
10096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10097 let line_mode = s.line_mode;
10098 s.move_with(|map, selection| {
10099 if !selection.is_empty() && !line_mode {
10100 selection.goal = SelectionGoal::None;
10101 }
10102 let (cursor, goal) = movement::up_by_rows(
10103 map,
10104 selection.start,
10105 action.lines,
10106 selection.goal,
10107 false,
10108 text_layout_details,
10109 );
10110 selection.collapse_to(cursor, goal);
10111 });
10112 })
10113 }
10114
10115 pub fn move_down_by_lines(
10116 &mut self,
10117 action: &MoveDownByLines,
10118 window: &mut Window,
10119 cx: &mut Context<Self>,
10120 ) {
10121 if self.take_rename(true, window, cx).is_some() {
10122 return;
10123 }
10124
10125 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10126 cx.propagate();
10127 return;
10128 }
10129
10130 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10131
10132 let text_layout_details = &self.text_layout_details(window);
10133
10134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10135 let line_mode = s.line_mode;
10136 s.move_with(|map, selection| {
10137 if !selection.is_empty() && !line_mode {
10138 selection.goal = SelectionGoal::None;
10139 }
10140 let (cursor, goal) = movement::down_by_rows(
10141 map,
10142 selection.start,
10143 action.lines,
10144 selection.goal,
10145 false,
10146 text_layout_details,
10147 );
10148 selection.collapse_to(cursor, goal);
10149 });
10150 })
10151 }
10152
10153 pub fn select_down_by_lines(
10154 &mut self,
10155 action: &SelectDownByLines,
10156 window: &mut Window,
10157 cx: &mut Context<Self>,
10158 ) {
10159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10160 let text_layout_details = &self.text_layout_details(window);
10161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10162 s.move_heads_with(|map, head, goal| {
10163 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10164 })
10165 })
10166 }
10167
10168 pub fn select_up_by_lines(
10169 &mut self,
10170 action: &SelectUpByLines,
10171 window: &mut Window,
10172 cx: &mut Context<Self>,
10173 ) {
10174 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10175 let text_layout_details = &self.text_layout_details(window);
10176 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10177 s.move_heads_with(|map, head, goal| {
10178 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10179 })
10180 })
10181 }
10182
10183 pub fn select_page_up(
10184 &mut self,
10185 _: &SelectPageUp,
10186 window: &mut Window,
10187 cx: &mut Context<Self>,
10188 ) {
10189 let Some(row_count) = self.visible_row_count() else {
10190 return;
10191 };
10192
10193 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10194
10195 let text_layout_details = &self.text_layout_details(window);
10196
10197 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10198 s.move_heads_with(|map, head, goal| {
10199 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10200 })
10201 })
10202 }
10203
10204 pub fn move_page_up(
10205 &mut self,
10206 action: &MovePageUp,
10207 window: &mut Window,
10208 cx: &mut Context<Self>,
10209 ) {
10210 if self.take_rename(true, window, cx).is_some() {
10211 return;
10212 }
10213
10214 if self
10215 .context_menu
10216 .borrow_mut()
10217 .as_mut()
10218 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10219 .unwrap_or(false)
10220 {
10221 return;
10222 }
10223
10224 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10225 cx.propagate();
10226 return;
10227 }
10228
10229 let Some(row_count) = self.visible_row_count() else {
10230 return;
10231 };
10232
10233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10234
10235 let autoscroll = if action.center_cursor {
10236 Autoscroll::center()
10237 } else {
10238 Autoscroll::fit()
10239 };
10240
10241 let text_layout_details = &self.text_layout_details(window);
10242
10243 self.change_selections(Some(autoscroll), window, cx, |s| {
10244 let line_mode = s.line_mode;
10245 s.move_with(|map, selection| {
10246 if !selection.is_empty() && !line_mode {
10247 selection.goal = SelectionGoal::None;
10248 }
10249 let (cursor, goal) = movement::up_by_rows(
10250 map,
10251 selection.end,
10252 row_count,
10253 selection.goal,
10254 false,
10255 text_layout_details,
10256 );
10257 selection.collapse_to(cursor, goal);
10258 });
10259 });
10260 }
10261
10262 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10263 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10264 let text_layout_details = &self.text_layout_details(window);
10265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10266 s.move_heads_with(|map, head, goal| {
10267 movement::up(map, head, goal, false, text_layout_details)
10268 })
10269 })
10270 }
10271
10272 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10273 self.take_rename(true, window, cx);
10274
10275 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10276 cx.propagate();
10277 return;
10278 }
10279
10280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10281
10282 let text_layout_details = &self.text_layout_details(window);
10283 let selection_count = self.selections.count();
10284 let first_selection = self.selections.first_anchor();
10285
10286 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10287 let line_mode = s.line_mode;
10288 s.move_with(|map, selection| {
10289 if !selection.is_empty() && !line_mode {
10290 selection.goal = SelectionGoal::None;
10291 }
10292 let (cursor, goal) = movement::down(
10293 map,
10294 selection.end,
10295 selection.goal,
10296 false,
10297 text_layout_details,
10298 );
10299 selection.collapse_to(cursor, goal);
10300 });
10301 });
10302
10303 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10304 {
10305 cx.propagate();
10306 }
10307 }
10308
10309 pub fn select_page_down(
10310 &mut self,
10311 _: &SelectPageDown,
10312 window: &mut Window,
10313 cx: &mut Context<Self>,
10314 ) {
10315 let Some(row_count) = self.visible_row_count() else {
10316 return;
10317 };
10318
10319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10320
10321 let text_layout_details = &self.text_layout_details(window);
10322
10323 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10324 s.move_heads_with(|map, head, goal| {
10325 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10326 })
10327 })
10328 }
10329
10330 pub fn move_page_down(
10331 &mut self,
10332 action: &MovePageDown,
10333 window: &mut Window,
10334 cx: &mut Context<Self>,
10335 ) {
10336 if self.take_rename(true, window, cx).is_some() {
10337 return;
10338 }
10339
10340 if self
10341 .context_menu
10342 .borrow_mut()
10343 .as_mut()
10344 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10345 .unwrap_or(false)
10346 {
10347 return;
10348 }
10349
10350 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10351 cx.propagate();
10352 return;
10353 }
10354
10355 let Some(row_count) = self.visible_row_count() else {
10356 return;
10357 };
10358
10359 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10360
10361 let autoscroll = if action.center_cursor {
10362 Autoscroll::center()
10363 } else {
10364 Autoscroll::fit()
10365 };
10366
10367 let text_layout_details = &self.text_layout_details(window);
10368 self.change_selections(Some(autoscroll), window, cx, |s| {
10369 let line_mode = s.line_mode;
10370 s.move_with(|map, selection| {
10371 if !selection.is_empty() && !line_mode {
10372 selection.goal = SelectionGoal::None;
10373 }
10374 let (cursor, goal) = movement::down_by_rows(
10375 map,
10376 selection.end,
10377 row_count,
10378 selection.goal,
10379 false,
10380 text_layout_details,
10381 );
10382 selection.collapse_to(cursor, goal);
10383 });
10384 });
10385 }
10386
10387 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10388 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10389 let text_layout_details = &self.text_layout_details(window);
10390 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10391 s.move_heads_with(|map, head, goal| {
10392 movement::down(map, head, goal, false, text_layout_details)
10393 })
10394 });
10395 }
10396
10397 pub fn context_menu_first(
10398 &mut self,
10399 _: &ContextMenuFirst,
10400 _window: &mut Window,
10401 cx: &mut Context<Self>,
10402 ) {
10403 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10404 context_menu.select_first(self.completion_provider.as_deref(), cx);
10405 }
10406 }
10407
10408 pub fn context_menu_prev(
10409 &mut self,
10410 _: &ContextMenuPrevious,
10411 _window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10415 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10416 }
10417 }
10418
10419 pub fn context_menu_next(
10420 &mut self,
10421 _: &ContextMenuNext,
10422 _window: &mut Window,
10423 cx: &mut Context<Self>,
10424 ) {
10425 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10426 context_menu.select_next(self.completion_provider.as_deref(), cx);
10427 }
10428 }
10429
10430 pub fn context_menu_last(
10431 &mut self,
10432 _: &ContextMenuLast,
10433 _window: &mut Window,
10434 cx: &mut Context<Self>,
10435 ) {
10436 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10437 context_menu.select_last(self.completion_provider.as_deref(), cx);
10438 }
10439 }
10440
10441 pub fn move_to_previous_word_start(
10442 &mut self,
10443 _: &MoveToPreviousWordStart,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10449 s.move_cursors_with(|map, head, _| {
10450 (
10451 movement::previous_word_start(map, head),
10452 SelectionGoal::None,
10453 )
10454 });
10455 })
10456 }
10457
10458 pub fn move_to_previous_subword_start(
10459 &mut self,
10460 _: &MoveToPreviousSubwordStart,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10465 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10466 s.move_cursors_with(|map, head, _| {
10467 (
10468 movement::previous_subword_start(map, head),
10469 SelectionGoal::None,
10470 )
10471 });
10472 })
10473 }
10474
10475 pub fn select_to_previous_word_start(
10476 &mut self,
10477 _: &SelectToPreviousWordStart,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10482 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10483 s.move_heads_with(|map, head, _| {
10484 (
10485 movement::previous_word_start(map, head),
10486 SelectionGoal::None,
10487 )
10488 });
10489 })
10490 }
10491
10492 pub fn select_to_previous_subword_start(
10493 &mut self,
10494 _: &SelectToPreviousSubwordStart,
10495 window: &mut Window,
10496 cx: &mut Context<Self>,
10497 ) {
10498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10500 s.move_heads_with(|map, head, _| {
10501 (
10502 movement::previous_subword_start(map, head),
10503 SelectionGoal::None,
10504 )
10505 });
10506 })
10507 }
10508
10509 pub fn delete_to_previous_word_start(
10510 &mut self,
10511 action: &DeleteToPreviousWordStart,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10516 self.transact(window, cx, |this, window, cx| {
10517 this.select_autoclose_pair(window, cx);
10518 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10519 let line_mode = s.line_mode;
10520 s.move_with(|map, selection| {
10521 if selection.is_empty() && !line_mode {
10522 let cursor = if action.ignore_newlines {
10523 movement::previous_word_start(map, selection.head())
10524 } else {
10525 movement::previous_word_start_or_newline(map, selection.head())
10526 };
10527 selection.set_head(cursor, SelectionGoal::None);
10528 }
10529 });
10530 });
10531 this.insert("", window, cx);
10532 });
10533 }
10534
10535 pub fn delete_to_previous_subword_start(
10536 &mut self,
10537 _: &DeleteToPreviousSubwordStart,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10542 self.transact(window, cx, |this, window, cx| {
10543 this.select_autoclose_pair(window, cx);
10544 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10545 let line_mode = s.line_mode;
10546 s.move_with(|map, selection| {
10547 if selection.is_empty() && !line_mode {
10548 let cursor = movement::previous_subword_start(map, selection.head());
10549 selection.set_head(cursor, SelectionGoal::None);
10550 }
10551 });
10552 });
10553 this.insert("", window, cx);
10554 });
10555 }
10556
10557 pub fn move_to_next_word_end(
10558 &mut self,
10559 _: &MoveToNextWordEnd,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10564 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10565 s.move_cursors_with(|map, head, _| {
10566 (movement::next_word_end(map, head), SelectionGoal::None)
10567 });
10568 })
10569 }
10570
10571 pub fn move_to_next_subword_end(
10572 &mut self,
10573 _: &MoveToNextSubwordEnd,
10574 window: &mut Window,
10575 cx: &mut Context<Self>,
10576 ) {
10577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10579 s.move_cursors_with(|map, head, _| {
10580 (movement::next_subword_end(map, head), SelectionGoal::None)
10581 });
10582 })
10583 }
10584
10585 pub fn select_to_next_word_end(
10586 &mut self,
10587 _: &SelectToNextWordEnd,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10593 s.move_heads_with(|map, head, _| {
10594 (movement::next_word_end(map, head), SelectionGoal::None)
10595 });
10596 })
10597 }
10598
10599 pub fn select_to_next_subword_end(
10600 &mut self,
10601 _: &SelectToNextSubwordEnd,
10602 window: &mut Window,
10603 cx: &mut Context<Self>,
10604 ) {
10605 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10606 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10607 s.move_heads_with(|map, head, _| {
10608 (movement::next_subword_end(map, head), SelectionGoal::None)
10609 });
10610 })
10611 }
10612
10613 pub fn delete_to_next_word_end(
10614 &mut self,
10615 action: &DeleteToNextWordEnd,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10620 self.transact(window, cx, |this, window, cx| {
10621 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10622 let line_mode = s.line_mode;
10623 s.move_with(|map, selection| {
10624 if selection.is_empty() && !line_mode {
10625 let cursor = if action.ignore_newlines {
10626 movement::next_word_end(map, selection.head())
10627 } else {
10628 movement::next_word_end_or_newline(map, selection.head())
10629 };
10630 selection.set_head(cursor, SelectionGoal::None);
10631 }
10632 });
10633 });
10634 this.insert("", window, cx);
10635 });
10636 }
10637
10638 pub fn delete_to_next_subword_end(
10639 &mut self,
10640 _: &DeleteToNextSubwordEnd,
10641 window: &mut Window,
10642 cx: &mut Context<Self>,
10643 ) {
10644 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10645 self.transact(window, cx, |this, window, cx| {
10646 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10647 s.move_with(|map, selection| {
10648 if selection.is_empty() {
10649 let cursor = movement::next_subword_end(map, selection.head());
10650 selection.set_head(cursor, SelectionGoal::None);
10651 }
10652 });
10653 });
10654 this.insert("", window, cx);
10655 });
10656 }
10657
10658 pub fn move_to_beginning_of_line(
10659 &mut self,
10660 action: &MoveToBeginningOfLine,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10665 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10666 s.move_cursors_with(|map, head, _| {
10667 (
10668 movement::indented_line_beginning(
10669 map,
10670 head,
10671 action.stop_at_soft_wraps,
10672 action.stop_at_indent,
10673 ),
10674 SelectionGoal::None,
10675 )
10676 });
10677 })
10678 }
10679
10680 pub fn select_to_beginning_of_line(
10681 &mut self,
10682 action: &SelectToBeginningOfLine,
10683 window: &mut Window,
10684 cx: &mut Context<Self>,
10685 ) {
10686 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10687 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10688 s.move_heads_with(|map, head, _| {
10689 (
10690 movement::indented_line_beginning(
10691 map,
10692 head,
10693 action.stop_at_soft_wraps,
10694 action.stop_at_indent,
10695 ),
10696 SelectionGoal::None,
10697 )
10698 });
10699 });
10700 }
10701
10702 pub fn delete_to_beginning_of_line(
10703 &mut self,
10704 action: &DeleteToBeginningOfLine,
10705 window: &mut Window,
10706 cx: &mut Context<Self>,
10707 ) {
10708 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10709 self.transact(window, cx, |this, window, cx| {
10710 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10711 s.move_with(|_, selection| {
10712 selection.reversed = true;
10713 });
10714 });
10715
10716 this.select_to_beginning_of_line(
10717 &SelectToBeginningOfLine {
10718 stop_at_soft_wraps: false,
10719 stop_at_indent: action.stop_at_indent,
10720 },
10721 window,
10722 cx,
10723 );
10724 this.backspace(&Backspace, window, cx);
10725 });
10726 }
10727
10728 pub fn move_to_end_of_line(
10729 &mut self,
10730 action: &MoveToEndOfLine,
10731 window: &mut Window,
10732 cx: &mut Context<Self>,
10733 ) {
10734 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10736 s.move_cursors_with(|map, head, _| {
10737 (
10738 movement::line_end(map, head, action.stop_at_soft_wraps),
10739 SelectionGoal::None,
10740 )
10741 });
10742 })
10743 }
10744
10745 pub fn select_to_end_of_line(
10746 &mut self,
10747 action: &SelectToEndOfLine,
10748 window: &mut Window,
10749 cx: &mut Context<Self>,
10750 ) {
10751 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10753 s.move_heads_with(|map, head, _| {
10754 (
10755 movement::line_end(map, head, action.stop_at_soft_wraps),
10756 SelectionGoal::None,
10757 )
10758 });
10759 })
10760 }
10761
10762 pub fn delete_to_end_of_line(
10763 &mut self,
10764 _: &DeleteToEndOfLine,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) {
10768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10769 self.transact(window, cx, |this, window, cx| {
10770 this.select_to_end_of_line(
10771 &SelectToEndOfLine {
10772 stop_at_soft_wraps: false,
10773 },
10774 window,
10775 cx,
10776 );
10777 this.delete(&Delete, window, cx);
10778 });
10779 }
10780
10781 pub fn cut_to_end_of_line(
10782 &mut self,
10783 _: &CutToEndOfLine,
10784 window: &mut Window,
10785 cx: &mut Context<Self>,
10786 ) {
10787 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10788 self.transact(window, cx, |this, window, cx| {
10789 this.select_to_end_of_line(
10790 &SelectToEndOfLine {
10791 stop_at_soft_wraps: false,
10792 },
10793 window,
10794 cx,
10795 );
10796 this.cut(&Cut, window, cx);
10797 });
10798 }
10799
10800 pub fn move_to_start_of_paragraph(
10801 &mut self,
10802 _: &MoveToStartOfParagraph,
10803 window: &mut Window,
10804 cx: &mut Context<Self>,
10805 ) {
10806 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10807 cx.propagate();
10808 return;
10809 }
10810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10812 s.move_with(|map, selection| {
10813 selection.collapse_to(
10814 movement::start_of_paragraph(map, selection.head(), 1),
10815 SelectionGoal::None,
10816 )
10817 });
10818 })
10819 }
10820
10821 pub fn move_to_end_of_paragraph(
10822 &mut self,
10823 _: &MoveToEndOfParagraph,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10828 cx.propagate();
10829 return;
10830 }
10831 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10833 s.move_with(|map, selection| {
10834 selection.collapse_to(
10835 movement::end_of_paragraph(map, selection.head(), 1),
10836 SelectionGoal::None,
10837 )
10838 });
10839 })
10840 }
10841
10842 pub fn select_to_start_of_paragraph(
10843 &mut self,
10844 _: &SelectToStartOfParagraph,
10845 window: &mut Window,
10846 cx: &mut Context<Self>,
10847 ) {
10848 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10849 cx.propagate();
10850 return;
10851 }
10852 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10854 s.move_heads_with(|map, head, _| {
10855 (
10856 movement::start_of_paragraph(map, head, 1),
10857 SelectionGoal::None,
10858 )
10859 });
10860 })
10861 }
10862
10863 pub fn select_to_end_of_paragraph(
10864 &mut self,
10865 _: &SelectToEndOfParagraph,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10870 cx.propagate();
10871 return;
10872 }
10873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10874 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10875 s.move_heads_with(|map, head, _| {
10876 (
10877 movement::end_of_paragraph(map, head, 1),
10878 SelectionGoal::None,
10879 )
10880 });
10881 })
10882 }
10883
10884 pub fn move_to_start_of_excerpt(
10885 &mut self,
10886 _: &MoveToStartOfExcerpt,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10891 cx.propagate();
10892 return;
10893 }
10894 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10895 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10896 s.move_with(|map, selection| {
10897 selection.collapse_to(
10898 movement::start_of_excerpt(
10899 map,
10900 selection.head(),
10901 workspace::searchable::Direction::Prev,
10902 ),
10903 SelectionGoal::None,
10904 )
10905 });
10906 })
10907 }
10908
10909 pub fn move_to_start_of_next_excerpt(
10910 &mut self,
10911 _: &MoveToStartOfNextExcerpt,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10916 cx.propagate();
10917 return;
10918 }
10919
10920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10921 s.move_with(|map, selection| {
10922 selection.collapse_to(
10923 movement::start_of_excerpt(
10924 map,
10925 selection.head(),
10926 workspace::searchable::Direction::Next,
10927 ),
10928 SelectionGoal::None,
10929 )
10930 });
10931 })
10932 }
10933
10934 pub fn move_to_end_of_excerpt(
10935 &mut self,
10936 _: &MoveToEndOfExcerpt,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10941 cx.propagate();
10942 return;
10943 }
10944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10945 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10946 s.move_with(|map, selection| {
10947 selection.collapse_to(
10948 movement::end_of_excerpt(
10949 map,
10950 selection.head(),
10951 workspace::searchable::Direction::Next,
10952 ),
10953 SelectionGoal::None,
10954 )
10955 });
10956 })
10957 }
10958
10959 pub fn move_to_end_of_previous_excerpt(
10960 &mut self,
10961 _: &MoveToEndOfPreviousExcerpt,
10962 window: &mut Window,
10963 cx: &mut Context<Self>,
10964 ) {
10965 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10966 cx.propagate();
10967 return;
10968 }
10969 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10971 s.move_with(|map, selection| {
10972 selection.collapse_to(
10973 movement::end_of_excerpt(
10974 map,
10975 selection.head(),
10976 workspace::searchable::Direction::Prev,
10977 ),
10978 SelectionGoal::None,
10979 )
10980 });
10981 })
10982 }
10983
10984 pub fn select_to_start_of_excerpt(
10985 &mut self,
10986 _: &SelectToStartOfExcerpt,
10987 window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10991 cx.propagate();
10992 return;
10993 }
10994 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10996 s.move_heads_with(|map, head, _| {
10997 (
10998 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
10999 SelectionGoal::None,
11000 )
11001 });
11002 })
11003 }
11004
11005 pub fn select_to_start_of_next_excerpt(
11006 &mut self,
11007 _: &SelectToStartOfNextExcerpt,
11008 window: &mut Window,
11009 cx: &mut Context<Self>,
11010 ) {
11011 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11012 cx.propagate();
11013 return;
11014 }
11015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11016 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11017 s.move_heads_with(|map, head, _| {
11018 (
11019 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11020 SelectionGoal::None,
11021 )
11022 });
11023 })
11024 }
11025
11026 pub fn select_to_end_of_excerpt(
11027 &mut self,
11028 _: &SelectToEndOfExcerpt,
11029 window: &mut Window,
11030 cx: &mut Context<Self>,
11031 ) {
11032 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11033 cx.propagate();
11034 return;
11035 }
11036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11038 s.move_heads_with(|map, head, _| {
11039 (
11040 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11041 SelectionGoal::None,
11042 )
11043 });
11044 })
11045 }
11046
11047 pub fn select_to_end_of_previous_excerpt(
11048 &mut self,
11049 _: &SelectToEndOfPreviousExcerpt,
11050 window: &mut Window,
11051 cx: &mut Context<Self>,
11052 ) {
11053 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11054 cx.propagate();
11055 return;
11056 }
11057 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11059 s.move_heads_with(|map, head, _| {
11060 (
11061 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11062 SelectionGoal::None,
11063 )
11064 });
11065 })
11066 }
11067
11068 pub fn move_to_beginning(
11069 &mut self,
11070 _: &MoveToBeginning,
11071 window: &mut Window,
11072 cx: &mut Context<Self>,
11073 ) {
11074 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11075 cx.propagate();
11076 return;
11077 }
11078 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11080 s.select_ranges(vec![0..0]);
11081 });
11082 }
11083
11084 pub fn select_to_beginning(
11085 &mut self,
11086 _: &SelectToBeginning,
11087 window: &mut Window,
11088 cx: &mut Context<Self>,
11089 ) {
11090 let mut selection = self.selections.last::<Point>(cx);
11091 selection.set_head(Point::zero(), SelectionGoal::None);
11092 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11093 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11094 s.select(vec![selection]);
11095 });
11096 }
11097
11098 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11099 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11100 cx.propagate();
11101 return;
11102 }
11103 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11104 let cursor = self.buffer.read(cx).read(cx).len();
11105 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11106 s.select_ranges(vec![cursor..cursor])
11107 });
11108 }
11109
11110 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11111 self.nav_history = nav_history;
11112 }
11113
11114 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11115 self.nav_history.as_ref()
11116 }
11117
11118 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11119 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11120 }
11121
11122 fn push_to_nav_history(
11123 &mut self,
11124 cursor_anchor: Anchor,
11125 new_position: Option<Point>,
11126 is_deactivate: bool,
11127 cx: &mut Context<Self>,
11128 ) {
11129 if let Some(nav_history) = self.nav_history.as_mut() {
11130 let buffer = self.buffer.read(cx).read(cx);
11131 let cursor_position = cursor_anchor.to_point(&buffer);
11132 let scroll_state = self.scroll_manager.anchor();
11133 let scroll_top_row = scroll_state.top_row(&buffer);
11134 drop(buffer);
11135
11136 if let Some(new_position) = new_position {
11137 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11138 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11139 return;
11140 }
11141 }
11142
11143 nav_history.push(
11144 Some(NavigationData {
11145 cursor_anchor,
11146 cursor_position,
11147 scroll_anchor: scroll_state,
11148 scroll_top_row,
11149 }),
11150 cx,
11151 );
11152 cx.emit(EditorEvent::PushedToNavHistory {
11153 anchor: cursor_anchor,
11154 is_deactivate,
11155 })
11156 }
11157 }
11158
11159 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11161 let buffer = self.buffer.read(cx).snapshot(cx);
11162 let mut selection = self.selections.first::<usize>(cx);
11163 selection.set_head(buffer.len(), SelectionGoal::None);
11164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11165 s.select(vec![selection]);
11166 });
11167 }
11168
11169 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11170 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11171 let end = self.buffer.read(cx).read(cx).len();
11172 self.change_selections(None, window, cx, |s| {
11173 s.select_ranges(vec![0..end]);
11174 });
11175 }
11176
11177 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11178 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11180 let mut selections = self.selections.all::<Point>(cx);
11181 let max_point = display_map.buffer_snapshot.max_point();
11182 for selection in &mut selections {
11183 let rows = selection.spanned_rows(true, &display_map);
11184 selection.start = Point::new(rows.start.0, 0);
11185 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11186 selection.reversed = false;
11187 }
11188 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11189 s.select(selections);
11190 });
11191 }
11192
11193 pub fn split_selection_into_lines(
11194 &mut self,
11195 _: &SplitSelectionIntoLines,
11196 window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 let selections = self
11200 .selections
11201 .all::<Point>(cx)
11202 .into_iter()
11203 .map(|selection| selection.start..selection.end)
11204 .collect::<Vec<_>>();
11205 self.unfold_ranges(&selections, true, true, cx);
11206
11207 let mut new_selection_ranges = Vec::new();
11208 {
11209 let buffer = self.buffer.read(cx).read(cx);
11210 for selection in selections {
11211 for row in selection.start.row..selection.end.row {
11212 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11213 new_selection_ranges.push(cursor..cursor);
11214 }
11215
11216 let is_multiline_selection = selection.start.row != selection.end.row;
11217 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11218 // so this action feels more ergonomic when paired with other selection operations
11219 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11220 if !should_skip_last {
11221 new_selection_ranges.push(selection.end..selection.end);
11222 }
11223 }
11224 }
11225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11226 s.select_ranges(new_selection_ranges);
11227 });
11228 }
11229
11230 pub fn add_selection_above(
11231 &mut self,
11232 _: &AddSelectionAbove,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 self.add_selection(true, window, cx);
11237 }
11238
11239 pub fn add_selection_below(
11240 &mut self,
11241 _: &AddSelectionBelow,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 ) {
11245 self.add_selection(false, window, cx);
11246 }
11247
11248 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11250
11251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11252 let mut selections = self.selections.all::<Point>(cx);
11253 let text_layout_details = self.text_layout_details(window);
11254 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11255 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11256 let range = oldest_selection.display_range(&display_map).sorted();
11257
11258 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11259 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11260 let positions = start_x.min(end_x)..start_x.max(end_x);
11261
11262 selections.clear();
11263 let mut stack = Vec::new();
11264 for row in range.start.row().0..=range.end.row().0 {
11265 if let Some(selection) = self.selections.build_columnar_selection(
11266 &display_map,
11267 DisplayRow(row),
11268 &positions,
11269 oldest_selection.reversed,
11270 &text_layout_details,
11271 ) {
11272 stack.push(selection.id);
11273 selections.push(selection);
11274 }
11275 }
11276
11277 if above {
11278 stack.reverse();
11279 }
11280
11281 AddSelectionsState { above, stack }
11282 });
11283
11284 let last_added_selection = *state.stack.last().unwrap();
11285 let mut new_selections = Vec::new();
11286 if above == state.above {
11287 let end_row = if above {
11288 DisplayRow(0)
11289 } else {
11290 display_map.max_point().row()
11291 };
11292
11293 'outer: for selection in selections {
11294 if selection.id == last_added_selection {
11295 let range = selection.display_range(&display_map).sorted();
11296 debug_assert_eq!(range.start.row(), range.end.row());
11297 let mut row = range.start.row();
11298 let positions =
11299 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11300 px(start)..px(end)
11301 } else {
11302 let start_x =
11303 display_map.x_for_display_point(range.start, &text_layout_details);
11304 let end_x =
11305 display_map.x_for_display_point(range.end, &text_layout_details);
11306 start_x.min(end_x)..start_x.max(end_x)
11307 };
11308
11309 while row != end_row {
11310 if above {
11311 row.0 -= 1;
11312 } else {
11313 row.0 += 1;
11314 }
11315
11316 if let Some(new_selection) = self.selections.build_columnar_selection(
11317 &display_map,
11318 row,
11319 &positions,
11320 selection.reversed,
11321 &text_layout_details,
11322 ) {
11323 state.stack.push(new_selection.id);
11324 if above {
11325 new_selections.push(new_selection);
11326 new_selections.push(selection);
11327 } else {
11328 new_selections.push(selection);
11329 new_selections.push(new_selection);
11330 }
11331
11332 continue 'outer;
11333 }
11334 }
11335 }
11336
11337 new_selections.push(selection);
11338 }
11339 } else {
11340 new_selections = selections;
11341 new_selections.retain(|s| s.id != last_added_selection);
11342 state.stack.pop();
11343 }
11344
11345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11346 s.select(new_selections);
11347 });
11348 if state.stack.len() > 1 {
11349 self.add_selections_state = Some(state);
11350 }
11351 }
11352
11353 pub fn select_next_match_internal(
11354 &mut self,
11355 display_map: &DisplaySnapshot,
11356 replace_newest: bool,
11357 autoscroll: Option<Autoscroll>,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) -> Result<()> {
11361 fn select_next_match_ranges(
11362 this: &mut Editor,
11363 range: Range<usize>,
11364 replace_newest: bool,
11365 auto_scroll: Option<Autoscroll>,
11366 window: &mut Window,
11367 cx: &mut Context<Editor>,
11368 ) {
11369 this.unfold_ranges(&[range.clone()], false, true, cx);
11370 this.change_selections(auto_scroll, window, cx, |s| {
11371 if replace_newest {
11372 s.delete(s.newest_anchor().id);
11373 }
11374 s.insert_range(range.clone());
11375 });
11376 }
11377
11378 let buffer = &display_map.buffer_snapshot;
11379 let mut selections = self.selections.all::<usize>(cx);
11380 if let Some(mut select_next_state) = self.select_next_state.take() {
11381 let query = &select_next_state.query;
11382 if !select_next_state.done {
11383 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11384 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11385 let mut next_selected_range = None;
11386
11387 let bytes_after_last_selection =
11388 buffer.bytes_in_range(last_selection.end..buffer.len());
11389 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11390 let query_matches = query
11391 .stream_find_iter(bytes_after_last_selection)
11392 .map(|result| (last_selection.end, result))
11393 .chain(
11394 query
11395 .stream_find_iter(bytes_before_first_selection)
11396 .map(|result| (0, result)),
11397 );
11398
11399 for (start_offset, query_match) in query_matches {
11400 let query_match = query_match.unwrap(); // can only fail due to I/O
11401 let offset_range =
11402 start_offset + query_match.start()..start_offset + query_match.end();
11403 let display_range = offset_range.start.to_display_point(display_map)
11404 ..offset_range.end.to_display_point(display_map);
11405
11406 if !select_next_state.wordwise
11407 || (!movement::is_inside_word(display_map, display_range.start)
11408 && !movement::is_inside_word(display_map, display_range.end))
11409 {
11410 // TODO: This is n^2, because we might check all the selections
11411 if !selections
11412 .iter()
11413 .any(|selection| selection.range().overlaps(&offset_range))
11414 {
11415 next_selected_range = Some(offset_range);
11416 break;
11417 }
11418 }
11419 }
11420
11421 if let Some(next_selected_range) = next_selected_range {
11422 select_next_match_ranges(
11423 self,
11424 next_selected_range,
11425 replace_newest,
11426 autoscroll,
11427 window,
11428 cx,
11429 );
11430 } else {
11431 select_next_state.done = true;
11432 }
11433 }
11434
11435 self.select_next_state = Some(select_next_state);
11436 } else {
11437 let mut only_carets = true;
11438 let mut same_text_selected = true;
11439 let mut selected_text = None;
11440
11441 let mut selections_iter = selections.iter().peekable();
11442 while let Some(selection) = selections_iter.next() {
11443 if selection.start != selection.end {
11444 only_carets = false;
11445 }
11446
11447 if same_text_selected {
11448 if selected_text.is_none() {
11449 selected_text =
11450 Some(buffer.text_for_range(selection.range()).collect::<String>());
11451 }
11452
11453 if let Some(next_selection) = selections_iter.peek() {
11454 if next_selection.range().len() == selection.range().len() {
11455 let next_selected_text = buffer
11456 .text_for_range(next_selection.range())
11457 .collect::<String>();
11458 if Some(next_selected_text) != selected_text {
11459 same_text_selected = false;
11460 selected_text = None;
11461 }
11462 } else {
11463 same_text_selected = false;
11464 selected_text = None;
11465 }
11466 }
11467 }
11468 }
11469
11470 if only_carets {
11471 for selection in &mut selections {
11472 let word_range = movement::surrounding_word(
11473 display_map,
11474 selection.start.to_display_point(display_map),
11475 );
11476 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11477 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11478 selection.goal = SelectionGoal::None;
11479 selection.reversed = false;
11480 select_next_match_ranges(
11481 self,
11482 selection.start..selection.end,
11483 replace_newest,
11484 autoscroll,
11485 window,
11486 cx,
11487 );
11488 }
11489
11490 if selections.len() == 1 {
11491 let selection = selections
11492 .last()
11493 .expect("ensured that there's only one selection");
11494 let query = buffer
11495 .text_for_range(selection.start..selection.end)
11496 .collect::<String>();
11497 let is_empty = query.is_empty();
11498 let select_state = SelectNextState {
11499 query: AhoCorasick::new(&[query])?,
11500 wordwise: true,
11501 done: is_empty,
11502 };
11503 self.select_next_state = Some(select_state);
11504 } else {
11505 self.select_next_state = None;
11506 }
11507 } else if let Some(selected_text) = selected_text {
11508 self.select_next_state = Some(SelectNextState {
11509 query: AhoCorasick::new(&[selected_text])?,
11510 wordwise: false,
11511 done: false,
11512 });
11513 self.select_next_match_internal(
11514 display_map,
11515 replace_newest,
11516 autoscroll,
11517 window,
11518 cx,
11519 )?;
11520 }
11521 }
11522 Ok(())
11523 }
11524
11525 pub fn select_all_matches(
11526 &mut self,
11527 _action: &SelectAllMatches,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) -> Result<()> {
11531 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11532
11533 self.push_to_selection_history();
11534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11535
11536 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11537 let Some(select_next_state) = self.select_next_state.as_mut() else {
11538 return Ok(());
11539 };
11540 if select_next_state.done {
11541 return Ok(());
11542 }
11543
11544 let mut new_selections = self.selections.all::<usize>(cx);
11545
11546 let buffer = &display_map.buffer_snapshot;
11547 let query_matches = select_next_state
11548 .query
11549 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11550
11551 for query_match in query_matches {
11552 let query_match = query_match.unwrap(); // can only fail due to I/O
11553 let offset_range = query_match.start()..query_match.end();
11554 let display_range = offset_range.start.to_display_point(&display_map)
11555 ..offset_range.end.to_display_point(&display_map);
11556
11557 if !select_next_state.wordwise
11558 || (!movement::is_inside_word(&display_map, display_range.start)
11559 && !movement::is_inside_word(&display_map, display_range.end))
11560 {
11561 self.selections.change_with(cx, |selections| {
11562 new_selections.push(Selection {
11563 id: selections.new_selection_id(),
11564 start: offset_range.start,
11565 end: offset_range.end,
11566 reversed: false,
11567 goal: SelectionGoal::None,
11568 });
11569 });
11570 }
11571 }
11572
11573 new_selections.sort_by_key(|selection| selection.start);
11574 let mut ix = 0;
11575 while ix + 1 < new_selections.len() {
11576 let current_selection = &new_selections[ix];
11577 let next_selection = &new_selections[ix + 1];
11578 if current_selection.range().overlaps(&next_selection.range()) {
11579 if current_selection.id < next_selection.id {
11580 new_selections.remove(ix + 1);
11581 } else {
11582 new_selections.remove(ix);
11583 }
11584 } else {
11585 ix += 1;
11586 }
11587 }
11588
11589 let reversed = self.selections.oldest::<usize>(cx).reversed;
11590
11591 for selection in new_selections.iter_mut() {
11592 selection.reversed = reversed;
11593 }
11594
11595 select_next_state.done = true;
11596 self.unfold_ranges(
11597 &new_selections
11598 .iter()
11599 .map(|selection| selection.range())
11600 .collect::<Vec<_>>(),
11601 false,
11602 false,
11603 cx,
11604 );
11605 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11606 selections.select(new_selections)
11607 });
11608
11609 Ok(())
11610 }
11611
11612 pub fn select_next(
11613 &mut self,
11614 action: &SelectNext,
11615 window: &mut Window,
11616 cx: &mut Context<Self>,
11617 ) -> Result<()> {
11618 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11619 self.push_to_selection_history();
11620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11621 self.select_next_match_internal(
11622 &display_map,
11623 action.replace_newest,
11624 Some(Autoscroll::newest()),
11625 window,
11626 cx,
11627 )?;
11628 Ok(())
11629 }
11630
11631 pub fn select_previous(
11632 &mut self,
11633 action: &SelectPrevious,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) -> Result<()> {
11637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11638 self.push_to_selection_history();
11639 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11640 let buffer = &display_map.buffer_snapshot;
11641 let mut selections = self.selections.all::<usize>(cx);
11642 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11643 let query = &select_prev_state.query;
11644 if !select_prev_state.done {
11645 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11646 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11647 let mut next_selected_range = None;
11648 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11649 let bytes_before_last_selection =
11650 buffer.reversed_bytes_in_range(0..last_selection.start);
11651 let bytes_after_first_selection =
11652 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11653 let query_matches = query
11654 .stream_find_iter(bytes_before_last_selection)
11655 .map(|result| (last_selection.start, result))
11656 .chain(
11657 query
11658 .stream_find_iter(bytes_after_first_selection)
11659 .map(|result| (buffer.len(), result)),
11660 );
11661 for (end_offset, query_match) in query_matches {
11662 let query_match = query_match.unwrap(); // can only fail due to I/O
11663 let offset_range =
11664 end_offset - query_match.end()..end_offset - query_match.start();
11665 let display_range = offset_range.start.to_display_point(&display_map)
11666 ..offset_range.end.to_display_point(&display_map);
11667
11668 if !select_prev_state.wordwise
11669 || (!movement::is_inside_word(&display_map, display_range.start)
11670 && !movement::is_inside_word(&display_map, display_range.end))
11671 {
11672 next_selected_range = Some(offset_range);
11673 break;
11674 }
11675 }
11676
11677 if let Some(next_selected_range) = next_selected_range {
11678 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11679 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11680 if action.replace_newest {
11681 s.delete(s.newest_anchor().id);
11682 }
11683 s.insert_range(next_selected_range);
11684 });
11685 } else {
11686 select_prev_state.done = true;
11687 }
11688 }
11689
11690 self.select_prev_state = Some(select_prev_state);
11691 } else {
11692 let mut only_carets = true;
11693 let mut same_text_selected = true;
11694 let mut selected_text = None;
11695
11696 let mut selections_iter = selections.iter().peekable();
11697 while let Some(selection) = selections_iter.next() {
11698 if selection.start != selection.end {
11699 only_carets = false;
11700 }
11701
11702 if same_text_selected {
11703 if selected_text.is_none() {
11704 selected_text =
11705 Some(buffer.text_for_range(selection.range()).collect::<String>());
11706 }
11707
11708 if let Some(next_selection) = selections_iter.peek() {
11709 if next_selection.range().len() == selection.range().len() {
11710 let next_selected_text = buffer
11711 .text_for_range(next_selection.range())
11712 .collect::<String>();
11713 if Some(next_selected_text) != selected_text {
11714 same_text_selected = false;
11715 selected_text = None;
11716 }
11717 } else {
11718 same_text_selected = false;
11719 selected_text = None;
11720 }
11721 }
11722 }
11723 }
11724
11725 if only_carets {
11726 for selection in &mut selections {
11727 let word_range = movement::surrounding_word(
11728 &display_map,
11729 selection.start.to_display_point(&display_map),
11730 );
11731 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11732 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11733 selection.goal = SelectionGoal::None;
11734 selection.reversed = false;
11735 }
11736 if selections.len() == 1 {
11737 let selection = selections
11738 .last()
11739 .expect("ensured that there's only one selection");
11740 let query = buffer
11741 .text_for_range(selection.start..selection.end)
11742 .collect::<String>();
11743 let is_empty = query.is_empty();
11744 let select_state = SelectNextState {
11745 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11746 wordwise: true,
11747 done: is_empty,
11748 };
11749 self.select_prev_state = Some(select_state);
11750 } else {
11751 self.select_prev_state = None;
11752 }
11753
11754 self.unfold_ranges(
11755 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11756 false,
11757 true,
11758 cx,
11759 );
11760 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11761 s.select(selections);
11762 });
11763 } else if let Some(selected_text) = selected_text {
11764 self.select_prev_state = Some(SelectNextState {
11765 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11766 wordwise: false,
11767 done: false,
11768 });
11769 self.select_previous(action, window, cx)?;
11770 }
11771 }
11772 Ok(())
11773 }
11774
11775 pub fn toggle_comments(
11776 &mut self,
11777 action: &ToggleComments,
11778 window: &mut Window,
11779 cx: &mut Context<Self>,
11780 ) {
11781 if self.read_only(cx) {
11782 return;
11783 }
11784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11785 let text_layout_details = &self.text_layout_details(window);
11786 self.transact(window, cx, |this, window, cx| {
11787 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11788 let mut edits = Vec::new();
11789 let mut selection_edit_ranges = Vec::new();
11790 let mut last_toggled_row = None;
11791 let snapshot = this.buffer.read(cx).read(cx);
11792 let empty_str: Arc<str> = Arc::default();
11793 let mut suffixes_inserted = Vec::new();
11794 let ignore_indent = action.ignore_indent;
11795
11796 fn comment_prefix_range(
11797 snapshot: &MultiBufferSnapshot,
11798 row: MultiBufferRow,
11799 comment_prefix: &str,
11800 comment_prefix_whitespace: &str,
11801 ignore_indent: bool,
11802 ) -> Range<Point> {
11803 let indent_size = if ignore_indent {
11804 0
11805 } else {
11806 snapshot.indent_size_for_line(row).len
11807 };
11808
11809 let start = Point::new(row.0, indent_size);
11810
11811 let mut line_bytes = snapshot
11812 .bytes_in_range(start..snapshot.max_point())
11813 .flatten()
11814 .copied();
11815
11816 // If this line currently begins with the line comment prefix, then record
11817 // the range containing the prefix.
11818 if line_bytes
11819 .by_ref()
11820 .take(comment_prefix.len())
11821 .eq(comment_prefix.bytes())
11822 {
11823 // Include any whitespace that matches the comment prefix.
11824 let matching_whitespace_len = line_bytes
11825 .zip(comment_prefix_whitespace.bytes())
11826 .take_while(|(a, b)| a == b)
11827 .count() as u32;
11828 let end = Point::new(
11829 start.row,
11830 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11831 );
11832 start..end
11833 } else {
11834 start..start
11835 }
11836 }
11837
11838 fn comment_suffix_range(
11839 snapshot: &MultiBufferSnapshot,
11840 row: MultiBufferRow,
11841 comment_suffix: &str,
11842 comment_suffix_has_leading_space: bool,
11843 ) -> Range<Point> {
11844 let end = Point::new(row.0, snapshot.line_len(row));
11845 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11846
11847 let mut line_end_bytes = snapshot
11848 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11849 .flatten()
11850 .copied();
11851
11852 let leading_space_len = if suffix_start_column > 0
11853 && line_end_bytes.next() == Some(b' ')
11854 && comment_suffix_has_leading_space
11855 {
11856 1
11857 } else {
11858 0
11859 };
11860
11861 // If this line currently begins with the line comment prefix, then record
11862 // the range containing the prefix.
11863 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11864 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11865 start..end
11866 } else {
11867 end..end
11868 }
11869 }
11870
11871 // TODO: Handle selections that cross excerpts
11872 for selection in &mut selections {
11873 let start_column = snapshot
11874 .indent_size_for_line(MultiBufferRow(selection.start.row))
11875 .len;
11876 let language = if let Some(language) =
11877 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11878 {
11879 language
11880 } else {
11881 continue;
11882 };
11883
11884 selection_edit_ranges.clear();
11885
11886 // If multiple selections contain a given row, avoid processing that
11887 // row more than once.
11888 let mut start_row = MultiBufferRow(selection.start.row);
11889 if last_toggled_row == Some(start_row) {
11890 start_row = start_row.next_row();
11891 }
11892 let end_row =
11893 if selection.end.row > selection.start.row && selection.end.column == 0 {
11894 MultiBufferRow(selection.end.row - 1)
11895 } else {
11896 MultiBufferRow(selection.end.row)
11897 };
11898 last_toggled_row = Some(end_row);
11899
11900 if start_row > end_row {
11901 continue;
11902 }
11903
11904 // If the language has line comments, toggle those.
11905 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11906
11907 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11908 if ignore_indent {
11909 full_comment_prefixes = full_comment_prefixes
11910 .into_iter()
11911 .map(|s| Arc::from(s.trim_end()))
11912 .collect();
11913 }
11914
11915 if !full_comment_prefixes.is_empty() {
11916 let first_prefix = full_comment_prefixes
11917 .first()
11918 .expect("prefixes is non-empty");
11919 let prefix_trimmed_lengths = full_comment_prefixes
11920 .iter()
11921 .map(|p| p.trim_end_matches(' ').len())
11922 .collect::<SmallVec<[usize; 4]>>();
11923
11924 let mut all_selection_lines_are_comments = true;
11925
11926 for row in start_row.0..=end_row.0 {
11927 let row = MultiBufferRow(row);
11928 if start_row < end_row && snapshot.is_line_blank(row) {
11929 continue;
11930 }
11931
11932 let prefix_range = full_comment_prefixes
11933 .iter()
11934 .zip(prefix_trimmed_lengths.iter().copied())
11935 .map(|(prefix, trimmed_prefix_len)| {
11936 comment_prefix_range(
11937 snapshot.deref(),
11938 row,
11939 &prefix[..trimmed_prefix_len],
11940 &prefix[trimmed_prefix_len..],
11941 ignore_indent,
11942 )
11943 })
11944 .max_by_key(|range| range.end.column - range.start.column)
11945 .expect("prefixes is non-empty");
11946
11947 if prefix_range.is_empty() {
11948 all_selection_lines_are_comments = false;
11949 }
11950
11951 selection_edit_ranges.push(prefix_range);
11952 }
11953
11954 if all_selection_lines_are_comments {
11955 edits.extend(
11956 selection_edit_ranges
11957 .iter()
11958 .cloned()
11959 .map(|range| (range, empty_str.clone())),
11960 );
11961 } else {
11962 let min_column = selection_edit_ranges
11963 .iter()
11964 .map(|range| range.start.column)
11965 .min()
11966 .unwrap_or(0);
11967 edits.extend(selection_edit_ranges.iter().map(|range| {
11968 let position = Point::new(range.start.row, min_column);
11969 (position..position, first_prefix.clone())
11970 }));
11971 }
11972 } else if let Some((full_comment_prefix, comment_suffix)) =
11973 language.block_comment_delimiters()
11974 {
11975 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
11976 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
11977 let prefix_range = comment_prefix_range(
11978 snapshot.deref(),
11979 start_row,
11980 comment_prefix,
11981 comment_prefix_whitespace,
11982 ignore_indent,
11983 );
11984 let suffix_range = comment_suffix_range(
11985 snapshot.deref(),
11986 end_row,
11987 comment_suffix.trim_start_matches(' '),
11988 comment_suffix.starts_with(' '),
11989 );
11990
11991 if prefix_range.is_empty() || suffix_range.is_empty() {
11992 edits.push((
11993 prefix_range.start..prefix_range.start,
11994 full_comment_prefix.clone(),
11995 ));
11996 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
11997 suffixes_inserted.push((end_row, comment_suffix.len()));
11998 } else {
11999 edits.push((prefix_range, empty_str.clone()));
12000 edits.push((suffix_range, empty_str.clone()));
12001 }
12002 } else {
12003 continue;
12004 }
12005 }
12006
12007 drop(snapshot);
12008 this.buffer.update(cx, |buffer, cx| {
12009 buffer.edit(edits, None, cx);
12010 });
12011
12012 // Adjust selections so that they end before any comment suffixes that
12013 // were inserted.
12014 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12015 let mut selections = this.selections.all::<Point>(cx);
12016 let snapshot = this.buffer.read(cx).read(cx);
12017 for selection in &mut selections {
12018 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12019 match row.cmp(&MultiBufferRow(selection.end.row)) {
12020 Ordering::Less => {
12021 suffixes_inserted.next();
12022 continue;
12023 }
12024 Ordering::Greater => break,
12025 Ordering::Equal => {
12026 if selection.end.column == snapshot.line_len(row) {
12027 if selection.is_empty() {
12028 selection.start.column -= suffix_len as u32;
12029 }
12030 selection.end.column -= suffix_len as u32;
12031 }
12032 break;
12033 }
12034 }
12035 }
12036 }
12037
12038 drop(snapshot);
12039 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.select(selections)
12041 });
12042
12043 let selections = this.selections.all::<Point>(cx);
12044 let selections_on_single_row = selections.windows(2).all(|selections| {
12045 selections[0].start.row == selections[1].start.row
12046 && selections[0].end.row == selections[1].end.row
12047 && selections[0].start.row == selections[0].end.row
12048 });
12049 let selections_selecting = selections
12050 .iter()
12051 .any(|selection| selection.start != selection.end);
12052 let advance_downwards = action.advance_downwards
12053 && selections_on_single_row
12054 && !selections_selecting
12055 && !matches!(this.mode, EditorMode::SingleLine { .. });
12056
12057 if advance_downwards {
12058 let snapshot = this.buffer.read(cx).snapshot(cx);
12059
12060 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_cursors_with(|display_snapshot, display_point, _| {
12062 let mut point = display_point.to_point(display_snapshot);
12063 point.row += 1;
12064 point = snapshot.clip_point(point, Bias::Left);
12065 let display_point = point.to_display_point(display_snapshot);
12066 let goal = SelectionGoal::HorizontalPosition(
12067 display_snapshot
12068 .x_for_display_point(display_point, text_layout_details)
12069 .into(),
12070 );
12071 (display_point, goal)
12072 })
12073 });
12074 }
12075 });
12076 }
12077
12078 pub fn select_enclosing_symbol(
12079 &mut self,
12080 _: &SelectEnclosingSymbol,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) {
12084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12085
12086 let buffer = self.buffer.read(cx).snapshot(cx);
12087 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12088
12089 fn update_selection(
12090 selection: &Selection<usize>,
12091 buffer_snap: &MultiBufferSnapshot,
12092 ) -> Option<Selection<usize>> {
12093 let cursor = selection.head();
12094 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12095 for symbol in symbols.iter().rev() {
12096 let start = symbol.range.start.to_offset(buffer_snap);
12097 let end = symbol.range.end.to_offset(buffer_snap);
12098 let new_range = start..end;
12099 if start < selection.start || end > selection.end {
12100 return Some(Selection {
12101 id: selection.id,
12102 start: new_range.start,
12103 end: new_range.end,
12104 goal: SelectionGoal::None,
12105 reversed: selection.reversed,
12106 });
12107 }
12108 }
12109 None
12110 }
12111
12112 let mut selected_larger_symbol = false;
12113 let new_selections = old_selections
12114 .iter()
12115 .map(|selection| match update_selection(selection, &buffer) {
12116 Some(new_selection) => {
12117 if new_selection.range() != selection.range() {
12118 selected_larger_symbol = true;
12119 }
12120 new_selection
12121 }
12122 None => selection.clone(),
12123 })
12124 .collect::<Vec<_>>();
12125
12126 if selected_larger_symbol {
12127 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12128 s.select(new_selections);
12129 });
12130 }
12131 }
12132
12133 pub fn select_larger_syntax_node(
12134 &mut self,
12135 _: &SelectLargerSyntaxNode,
12136 window: &mut Window,
12137 cx: &mut Context<Self>,
12138 ) {
12139 let Some(visible_row_count) = self.visible_row_count() else {
12140 return;
12141 };
12142 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12143 if old_selections.is_empty() {
12144 return;
12145 }
12146
12147 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12148
12149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12150 let buffer = self.buffer.read(cx).snapshot(cx);
12151
12152 let mut selected_larger_node = false;
12153 let mut new_selections = old_selections
12154 .iter()
12155 .map(|selection| {
12156 let old_range = selection.start..selection.end;
12157 let mut new_range = old_range.clone();
12158 let mut new_node = None;
12159 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12160 {
12161 new_node = Some(node);
12162 new_range = match containing_range {
12163 MultiOrSingleBufferOffsetRange::Single(_) => break,
12164 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12165 };
12166 if !display_map.intersects_fold(new_range.start)
12167 && !display_map.intersects_fold(new_range.end)
12168 {
12169 break;
12170 }
12171 }
12172
12173 if let Some(node) = new_node {
12174 // Log the ancestor, to support using this action as a way to explore TreeSitter
12175 // nodes. Parent and grandparent are also logged because this operation will not
12176 // visit nodes that have the same range as their parent.
12177 log::info!("Node: {node:?}");
12178 let parent = node.parent();
12179 log::info!("Parent: {parent:?}");
12180 let grandparent = parent.and_then(|x| x.parent());
12181 log::info!("Grandparent: {grandparent:?}");
12182 }
12183
12184 selected_larger_node |= new_range != old_range;
12185 Selection {
12186 id: selection.id,
12187 start: new_range.start,
12188 end: new_range.end,
12189 goal: SelectionGoal::None,
12190 reversed: selection.reversed,
12191 }
12192 })
12193 .collect::<Vec<_>>();
12194
12195 if !selected_larger_node {
12196 return; // don't put this call in the history
12197 }
12198
12199 // scroll based on transformation done to the last selection created by the user
12200 let (last_old, last_new) = old_selections
12201 .last()
12202 .zip(new_selections.last().cloned())
12203 .expect("old_selections isn't empty");
12204
12205 // revert selection
12206 let is_selection_reversed = {
12207 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12208 new_selections.last_mut().expect("checked above").reversed =
12209 should_newest_selection_be_reversed;
12210 should_newest_selection_be_reversed
12211 };
12212
12213 if selected_larger_node {
12214 self.select_syntax_node_history.disable_clearing = true;
12215 self.change_selections(None, window, cx, |s| {
12216 s.select(new_selections.clone());
12217 });
12218 self.select_syntax_node_history.disable_clearing = false;
12219 }
12220
12221 let start_row = last_new.start.to_display_point(&display_map).row().0;
12222 let end_row = last_new.end.to_display_point(&display_map).row().0;
12223 let selection_height = end_row - start_row + 1;
12224 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12225
12226 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
12227 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
12228 let middle_row = (end_row + start_row) / 2;
12229 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12230 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12231 SelectSyntaxNodeScrollBehavior::CenterSelection
12232 } else if is_selection_reversed {
12233 self.scroll_cursor_top(&Default::default(), window, cx);
12234 SelectSyntaxNodeScrollBehavior::CursorTop
12235 } else {
12236 self.scroll_cursor_bottom(&Default::default(), window, cx);
12237 SelectSyntaxNodeScrollBehavior::CursorBottom
12238 };
12239
12240 self.select_syntax_node_history.push((
12241 old_selections,
12242 scroll_behavior,
12243 is_selection_reversed,
12244 ));
12245 }
12246
12247 pub fn select_smaller_syntax_node(
12248 &mut self,
12249 _: &SelectSmallerSyntaxNode,
12250 window: &mut Window,
12251 cx: &mut Context<Self>,
12252 ) {
12253 let Some(visible_row_count) = self.visible_row_count() else {
12254 return;
12255 };
12256
12257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12258
12259 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12260 self.select_syntax_node_history.pop()
12261 {
12262 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12263
12264 if let Some(selection) = selections.last_mut() {
12265 selection.reversed = is_selection_reversed;
12266 }
12267
12268 self.select_syntax_node_history.disable_clearing = true;
12269 self.change_selections(None, window, cx, |s| {
12270 s.select(selections.to_vec());
12271 });
12272 self.select_syntax_node_history.disable_clearing = false;
12273
12274 let newest = self.selections.newest::<usize>(cx);
12275 let start_row = newest.start.to_display_point(&display_map).row().0;
12276 let end_row = newest.end.to_display_point(&display_map).row().0;
12277
12278 match scroll_behavior {
12279 SelectSyntaxNodeScrollBehavior::CursorTop => {
12280 self.scroll_cursor_top(&Default::default(), window, cx);
12281 }
12282 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12283 let middle_row = (end_row + start_row) / 2;
12284 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12285 // centralize the selection, not the cursor
12286 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12287 }
12288 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12289 self.scroll_cursor_bottom(&Default::default(), window, cx);
12290 }
12291 }
12292 }
12293 }
12294
12295 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12296 if !EditorSettings::get_global(cx).gutter.runnables {
12297 self.clear_tasks();
12298 return Task::ready(());
12299 }
12300 let project = self.project.as_ref().map(Entity::downgrade);
12301 cx.spawn_in(window, async move |this, cx| {
12302 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12303 let Some(project) = project.and_then(|p| p.upgrade()) else {
12304 return;
12305 };
12306 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12307 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12308 }) else {
12309 return;
12310 };
12311
12312 let hide_runnables = project
12313 .update(cx, |project, cx| {
12314 // Do not display any test indicators in non-dev server remote projects.
12315 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12316 })
12317 .unwrap_or(true);
12318 if hide_runnables {
12319 return;
12320 }
12321 let new_rows =
12322 cx.background_spawn({
12323 let snapshot = display_snapshot.clone();
12324 async move {
12325 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12326 }
12327 })
12328 .await;
12329
12330 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12331 this.update(cx, |this, _| {
12332 this.clear_tasks();
12333 for (key, value) in rows {
12334 this.insert_tasks(key, value);
12335 }
12336 })
12337 .ok();
12338 })
12339 }
12340 fn fetch_runnable_ranges(
12341 snapshot: &DisplaySnapshot,
12342 range: Range<Anchor>,
12343 ) -> Vec<language::RunnableRange> {
12344 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12345 }
12346
12347 fn runnable_rows(
12348 project: Entity<Project>,
12349 snapshot: DisplaySnapshot,
12350 runnable_ranges: Vec<RunnableRange>,
12351 mut cx: AsyncWindowContext,
12352 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12353 runnable_ranges
12354 .into_iter()
12355 .filter_map(|mut runnable| {
12356 let tasks = cx
12357 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12358 .ok()?;
12359 if tasks.is_empty() {
12360 return None;
12361 }
12362
12363 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12364
12365 let row = snapshot
12366 .buffer_snapshot
12367 .buffer_line_for_row(MultiBufferRow(point.row))?
12368 .1
12369 .start
12370 .row;
12371
12372 let context_range =
12373 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12374 Some((
12375 (runnable.buffer_id, row),
12376 RunnableTasks {
12377 templates: tasks,
12378 offset: snapshot
12379 .buffer_snapshot
12380 .anchor_before(runnable.run_range.start),
12381 context_range,
12382 column: point.column,
12383 extra_variables: runnable.extra_captures,
12384 },
12385 ))
12386 })
12387 .collect()
12388 }
12389
12390 fn templates_with_tags(
12391 project: &Entity<Project>,
12392 runnable: &mut Runnable,
12393 cx: &mut App,
12394 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12395 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12396 let (worktree_id, file) = project
12397 .buffer_for_id(runnable.buffer, cx)
12398 .and_then(|buffer| buffer.read(cx).file())
12399 .map(|file| (file.worktree_id(cx), file.clone()))
12400 .unzip();
12401
12402 (
12403 project.task_store().read(cx).task_inventory().cloned(),
12404 worktree_id,
12405 file,
12406 )
12407 });
12408
12409 let tags = mem::take(&mut runnable.tags);
12410 let mut tags: Vec<_> = tags
12411 .into_iter()
12412 .flat_map(|tag| {
12413 let tag = tag.0.clone();
12414 inventory
12415 .as_ref()
12416 .into_iter()
12417 .flat_map(|inventory| {
12418 inventory.read(cx).list_tasks(
12419 file.clone(),
12420 Some(runnable.language.clone()),
12421 worktree_id,
12422 cx,
12423 )
12424 })
12425 .filter(move |(_, template)| {
12426 template.tags.iter().any(|source_tag| source_tag == &tag)
12427 })
12428 })
12429 .sorted_by_key(|(kind, _)| kind.to_owned())
12430 .collect();
12431 if let Some((leading_tag_source, _)) = tags.first() {
12432 // Strongest source wins; if we have worktree tag binding, prefer that to
12433 // global and language bindings;
12434 // if we have a global binding, prefer that to language binding.
12435 let first_mismatch = tags
12436 .iter()
12437 .position(|(tag_source, _)| tag_source != leading_tag_source);
12438 if let Some(index) = first_mismatch {
12439 tags.truncate(index);
12440 }
12441 }
12442
12443 tags
12444 }
12445
12446 pub fn move_to_enclosing_bracket(
12447 &mut self,
12448 _: &MoveToEnclosingBracket,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12453 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12454 s.move_offsets_with(|snapshot, selection| {
12455 let Some(enclosing_bracket_ranges) =
12456 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12457 else {
12458 return;
12459 };
12460
12461 let mut best_length = usize::MAX;
12462 let mut best_inside = false;
12463 let mut best_in_bracket_range = false;
12464 let mut best_destination = None;
12465 for (open, close) in enclosing_bracket_ranges {
12466 let close = close.to_inclusive();
12467 let length = close.end() - open.start;
12468 let inside = selection.start >= open.end && selection.end <= *close.start();
12469 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12470 || close.contains(&selection.head());
12471
12472 // If best is next to a bracket and current isn't, skip
12473 if !in_bracket_range && best_in_bracket_range {
12474 continue;
12475 }
12476
12477 // Prefer smaller lengths unless best is inside and current isn't
12478 if length > best_length && (best_inside || !inside) {
12479 continue;
12480 }
12481
12482 best_length = length;
12483 best_inside = inside;
12484 best_in_bracket_range = in_bracket_range;
12485 best_destination = Some(
12486 if close.contains(&selection.start) && close.contains(&selection.end) {
12487 if inside {
12488 open.end
12489 } else {
12490 open.start
12491 }
12492 } else if inside {
12493 *close.start()
12494 } else {
12495 *close.end()
12496 },
12497 );
12498 }
12499
12500 if let Some(destination) = best_destination {
12501 selection.collapse_to(destination, SelectionGoal::None);
12502 }
12503 })
12504 });
12505 }
12506
12507 pub fn undo_selection(
12508 &mut self,
12509 _: &UndoSelection,
12510 window: &mut Window,
12511 cx: &mut Context<Self>,
12512 ) {
12513 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12514 self.end_selection(window, cx);
12515 self.selection_history.mode = SelectionHistoryMode::Undoing;
12516 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12517 self.change_selections(None, window, cx, |s| {
12518 s.select_anchors(entry.selections.to_vec())
12519 });
12520 self.select_next_state = entry.select_next_state;
12521 self.select_prev_state = entry.select_prev_state;
12522 self.add_selections_state = entry.add_selections_state;
12523 self.request_autoscroll(Autoscroll::newest(), cx);
12524 }
12525 self.selection_history.mode = SelectionHistoryMode::Normal;
12526 }
12527
12528 pub fn redo_selection(
12529 &mut self,
12530 _: &RedoSelection,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12535 self.end_selection(window, cx);
12536 self.selection_history.mode = SelectionHistoryMode::Redoing;
12537 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12538 self.change_selections(None, window, cx, |s| {
12539 s.select_anchors(entry.selections.to_vec())
12540 });
12541 self.select_next_state = entry.select_next_state;
12542 self.select_prev_state = entry.select_prev_state;
12543 self.add_selections_state = entry.add_selections_state;
12544 self.request_autoscroll(Autoscroll::newest(), cx);
12545 }
12546 self.selection_history.mode = SelectionHistoryMode::Normal;
12547 }
12548
12549 pub fn expand_excerpts(
12550 &mut self,
12551 action: &ExpandExcerpts,
12552 _: &mut Window,
12553 cx: &mut Context<Self>,
12554 ) {
12555 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12556 }
12557
12558 pub fn expand_excerpts_down(
12559 &mut self,
12560 action: &ExpandExcerptsDown,
12561 _: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) {
12564 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12565 }
12566
12567 pub fn expand_excerpts_up(
12568 &mut self,
12569 action: &ExpandExcerptsUp,
12570 _: &mut Window,
12571 cx: &mut Context<Self>,
12572 ) {
12573 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12574 }
12575
12576 pub fn expand_excerpts_for_direction(
12577 &mut self,
12578 lines: u32,
12579 direction: ExpandExcerptDirection,
12580
12581 cx: &mut Context<Self>,
12582 ) {
12583 let selections = self.selections.disjoint_anchors();
12584
12585 let lines = if lines == 0 {
12586 EditorSettings::get_global(cx).expand_excerpt_lines
12587 } else {
12588 lines
12589 };
12590
12591 self.buffer.update(cx, |buffer, cx| {
12592 let snapshot = buffer.snapshot(cx);
12593 let mut excerpt_ids = selections
12594 .iter()
12595 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12596 .collect::<Vec<_>>();
12597 excerpt_ids.sort();
12598 excerpt_ids.dedup();
12599 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12600 })
12601 }
12602
12603 pub fn expand_excerpt(
12604 &mut self,
12605 excerpt: ExcerptId,
12606 direction: ExpandExcerptDirection,
12607 window: &mut Window,
12608 cx: &mut Context<Self>,
12609 ) {
12610 let current_scroll_position = self.scroll_position(cx);
12611 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12612 self.buffer.update(cx, |buffer, cx| {
12613 buffer.expand_excerpts([excerpt], lines, direction, cx)
12614 });
12615 if direction == ExpandExcerptDirection::Down {
12616 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12617 self.set_scroll_position(new_scroll_position, window, cx);
12618 }
12619 }
12620
12621 pub fn go_to_singleton_buffer_point(
12622 &mut self,
12623 point: Point,
12624 window: &mut Window,
12625 cx: &mut Context<Self>,
12626 ) {
12627 self.go_to_singleton_buffer_range(point..point, window, cx);
12628 }
12629
12630 pub fn go_to_singleton_buffer_range(
12631 &mut self,
12632 range: Range<Point>,
12633 window: &mut Window,
12634 cx: &mut Context<Self>,
12635 ) {
12636 let multibuffer = self.buffer().read(cx);
12637 let Some(buffer) = multibuffer.as_singleton() else {
12638 return;
12639 };
12640 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12641 return;
12642 };
12643 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12644 return;
12645 };
12646 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12647 s.select_anchor_ranges([start..end])
12648 });
12649 }
12650
12651 fn go_to_diagnostic(
12652 &mut self,
12653 _: &GoToDiagnostic,
12654 window: &mut Window,
12655 cx: &mut Context<Self>,
12656 ) {
12657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12658 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12659 }
12660
12661 fn go_to_prev_diagnostic(
12662 &mut self,
12663 _: &GoToPreviousDiagnostic,
12664 window: &mut Window,
12665 cx: &mut Context<Self>,
12666 ) {
12667 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12668 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12669 }
12670
12671 pub fn go_to_diagnostic_impl(
12672 &mut self,
12673 direction: Direction,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) {
12677 let buffer = self.buffer.read(cx).snapshot(cx);
12678 let selection = self.selections.newest::<usize>(cx);
12679
12680 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12681 if direction == Direction::Next {
12682 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12683 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12684 return;
12685 };
12686 self.activate_diagnostics(
12687 buffer_id,
12688 popover.local_diagnostic.diagnostic.group_id,
12689 window,
12690 cx,
12691 );
12692 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12693 let primary_range_start = active_diagnostics.primary_range.start;
12694 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12695 let mut new_selection = s.newest_anchor().clone();
12696 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12697 s.select_anchors(vec![new_selection.clone()]);
12698 });
12699 self.refresh_inline_completion(false, true, window, cx);
12700 }
12701 return;
12702 }
12703 }
12704
12705 let active_group_id = self
12706 .active_diagnostics
12707 .as_ref()
12708 .map(|active_group| active_group.group_id);
12709 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12710 active_diagnostics
12711 .primary_range
12712 .to_offset(&buffer)
12713 .to_inclusive()
12714 });
12715 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12716 if active_primary_range.contains(&selection.head()) {
12717 *active_primary_range.start()
12718 } else {
12719 selection.head()
12720 }
12721 } else {
12722 selection.head()
12723 };
12724
12725 let snapshot = self.snapshot(window, cx);
12726 let primary_diagnostics_before = buffer
12727 .diagnostics_in_range::<usize>(0..search_start)
12728 .filter(|entry| entry.diagnostic.is_primary)
12729 .filter(|entry| entry.range.start != entry.range.end)
12730 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12731 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12732 .collect::<Vec<_>>();
12733 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12734 primary_diagnostics_before
12735 .iter()
12736 .position(|entry| entry.diagnostic.group_id == active_group_id)
12737 });
12738
12739 let primary_diagnostics_after = buffer
12740 .diagnostics_in_range::<usize>(search_start..buffer.len())
12741 .filter(|entry| entry.diagnostic.is_primary)
12742 .filter(|entry| entry.range.start != entry.range.end)
12743 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12744 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12745 .collect::<Vec<_>>();
12746 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12747 primary_diagnostics_after
12748 .iter()
12749 .enumerate()
12750 .rev()
12751 .find_map(|(i, entry)| {
12752 if entry.diagnostic.group_id == active_group_id {
12753 Some(i)
12754 } else {
12755 None
12756 }
12757 })
12758 });
12759
12760 let next_primary_diagnostic = match direction {
12761 Direction::Prev => primary_diagnostics_before
12762 .iter()
12763 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12764 .rev()
12765 .next(),
12766 Direction::Next => primary_diagnostics_after
12767 .iter()
12768 .skip(
12769 last_same_group_diagnostic_after
12770 .map(|index| index + 1)
12771 .unwrap_or(0),
12772 )
12773 .next(),
12774 };
12775
12776 // Cycle around to the start of the buffer, potentially moving back to the start of
12777 // the currently active diagnostic.
12778 let cycle_around = || match direction {
12779 Direction::Prev => primary_diagnostics_after
12780 .iter()
12781 .rev()
12782 .chain(primary_diagnostics_before.iter().rev())
12783 .next(),
12784 Direction::Next => primary_diagnostics_before
12785 .iter()
12786 .chain(primary_diagnostics_after.iter())
12787 .next(),
12788 };
12789
12790 if let Some((primary_range, group_id)) = next_primary_diagnostic
12791 .or_else(cycle_around)
12792 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12793 {
12794 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12795 return;
12796 };
12797 self.activate_diagnostics(buffer_id, group_id, window, cx);
12798 if self.active_diagnostics.is_some() {
12799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12800 s.select(vec![Selection {
12801 id: selection.id,
12802 start: primary_range.start,
12803 end: primary_range.start,
12804 reversed: false,
12805 goal: SelectionGoal::None,
12806 }]);
12807 });
12808 self.refresh_inline_completion(false, true, window, cx);
12809 }
12810 }
12811 }
12812
12813 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12814 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12815 let snapshot = self.snapshot(window, cx);
12816 let selection = self.selections.newest::<Point>(cx);
12817 self.go_to_hunk_before_or_after_position(
12818 &snapshot,
12819 selection.head(),
12820 Direction::Next,
12821 window,
12822 cx,
12823 );
12824 }
12825
12826 pub fn go_to_hunk_before_or_after_position(
12827 &mut self,
12828 snapshot: &EditorSnapshot,
12829 position: Point,
12830 direction: Direction,
12831 window: &mut Window,
12832 cx: &mut Context<Editor>,
12833 ) {
12834 let row = if direction == Direction::Next {
12835 self.hunk_after_position(snapshot, position)
12836 .map(|hunk| hunk.row_range.start)
12837 } else {
12838 self.hunk_before_position(snapshot, position)
12839 };
12840
12841 if let Some(row) = row {
12842 let destination = Point::new(row.0, 0);
12843 let autoscroll = Autoscroll::center();
12844
12845 self.unfold_ranges(&[destination..destination], false, false, cx);
12846 self.change_selections(Some(autoscroll), window, cx, |s| {
12847 s.select_ranges([destination..destination]);
12848 });
12849 }
12850 }
12851
12852 fn hunk_after_position(
12853 &mut self,
12854 snapshot: &EditorSnapshot,
12855 position: Point,
12856 ) -> Option<MultiBufferDiffHunk> {
12857 snapshot
12858 .buffer_snapshot
12859 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12860 .find(|hunk| hunk.row_range.start.0 > position.row)
12861 .or_else(|| {
12862 snapshot
12863 .buffer_snapshot
12864 .diff_hunks_in_range(Point::zero()..position)
12865 .find(|hunk| hunk.row_range.end.0 < position.row)
12866 })
12867 }
12868
12869 fn go_to_prev_hunk(
12870 &mut self,
12871 _: &GoToPreviousHunk,
12872 window: &mut Window,
12873 cx: &mut Context<Self>,
12874 ) {
12875 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12876 let snapshot = self.snapshot(window, cx);
12877 let selection = self.selections.newest::<Point>(cx);
12878 self.go_to_hunk_before_or_after_position(
12879 &snapshot,
12880 selection.head(),
12881 Direction::Prev,
12882 window,
12883 cx,
12884 );
12885 }
12886
12887 fn hunk_before_position(
12888 &mut self,
12889 snapshot: &EditorSnapshot,
12890 position: Point,
12891 ) -> Option<MultiBufferRow> {
12892 snapshot
12893 .buffer_snapshot
12894 .diff_hunk_before(position)
12895 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12896 }
12897
12898 fn go_to_line<T: 'static>(
12899 &mut self,
12900 position: Anchor,
12901 highlight_color: Option<Hsla>,
12902 window: &mut Window,
12903 cx: &mut Context<Self>,
12904 ) {
12905 let snapshot = self.snapshot(window, cx).display_snapshot;
12906 let position = position.to_point(&snapshot.buffer_snapshot);
12907 let start = snapshot
12908 .buffer_snapshot
12909 .clip_point(Point::new(position.row, 0), Bias::Left);
12910 let end = start + Point::new(1, 0);
12911 let start = snapshot.buffer_snapshot.anchor_before(start);
12912 let end = snapshot.buffer_snapshot.anchor_before(end);
12913
12914 self.highlight_rows::<T>(
12915 start..end,
12916 highlight_color
12917 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12918 false,
12919 cx,
12920 );
12921 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
12922 }
12923
12924 pub fn go_to_definition(
12925 &mut self,
12926 _: &GoToDefinition,
12927 window: &mut Window,
12928 cx: &mut Context<Self>,
12929 ) -> Task<Result<Navigated>> {
12930 let definition =
12931 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12932 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
12933 cx.spawn_in(window, async move |editor, cx| {
12934 if definition.await? == Navigated::Yes {
12935 return Ok(Navigated::Yes);
12936 }
12937 match fallback_strategy {
12938 GoToDefinitionFallback::None => Ok(Navigated::No),
12939 GoToDefinitionFallback::FindAllReferences => {
12940 match editor.update_in(cx, |editor, window, cx| {
12941 editor.find_all_references(&FindAllReferences, window, cx)
12942 })? {
12943 Some(references) => references.await,
12944 None => Ok(Navigated::No),
12945 }
12946 }
12947 }
12948 })
12949 }
12950
12951 pub fn go_to_declaration(
12952 &mut self,
12953 _: &GoToDeclaration,
12954 window: &mut Window,
12955 cx: &mut Context<Self>,
12956 ) -> Task<Result<Navigated>> {
12957 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
12958 }
12959
12960 pub fn go_to_declaration_split(
12961 &mut self,
12962 _: &GoToDeclaration,
12963 window: &mut Window,
12964 cx: &mut Context<Self>,
12965 ) -> Task<Result<Navigated>> {
12966 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
12967 }
12968
12969 pub fn go_to_implementation(
12970 &mut self,
12971 _: &GoToImplementation,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) -> Task<Result<Navigated>> {
12975 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
12976 }
12977
12978 pub fn go_to_implementation_split(
12979 &mut self,
12980 _: &GoToImplementationSplit,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) -> Task<Result<Navigated>> {
12984 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
12985 }
12986
12987 pub fn go_to_type_definition(
12988 &mut self,
12989 _: &GoToTypeDefinition,
12990 window: &mut Window,
12991 cx: &mut Context<Self>,
12992 ) -> Task<Result<Navigated>> {
12993 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
12994 }
12995
12996 pub fn go_to_definition_split(
12997 &mut self,
12998 _: &GoToDefinitionSplit,
12999 window: &mut Window,
13000 cx: &mut Context<Self>,
13001 ) -> Task<Result<Navigated>> {
13002 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13003 }
13004
13005 pub fn go_to_type_definition_split(
13006 &mut self,
13007 _: &GoToTypeDefinitionSplit,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) -> Task<Result<Navigated>> {
13011 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13012 }
13013
13014 fn go_to_definition_of_kind(
13015 &mut self,
13016 kind: GotoDefinitionKind,
13017 split: bool,
13018 window: &mut Window,
13019 cx: &mut Context<Self>,
13020 ) -> Task<Result<Navigated>> {
13021 let Some(provider) = self.semantics_provider.clone() else {
13022 return Task::ready(Ok(Navigated::No));
13023 };
13024 let head = self.selections.newest::<usize>(cx).head();
13025 let buffer = self.buffer.read(cx);
13026 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13027 text_anchor
13028 } else {
13029 return Task::ready(Ok(Navigated::No));
13030 };
13031
13032 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13033 return Task::ready(Ok(Navigated::No));
13034 };
13035
13036 cx.spawn_in(window, async move |editor, cx| {
13037 let definitions = definitions.await?;
13038 let navigated = editor
13039 .update_in(cx, |editor, window, cx| {
13040 editor.navigate_to_hover_links(
13041 Some(kind),
13042 definitions
13043 .into_iter()
13044 .filter(|location| {
13045 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13046 })
13047 .map(HoverLink::Text)
13048 .collect::<Vec<_>>(),
13049 split,
13050 window,
13051 cx,
13052 )
13053 })?
13054 .await?;
13055 anyhow::Ok(navigated)
13056 })
13057 }
13058
13059 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13060 let selection = self.selections.newest_anchor();
13061 let head = selection.head();
13062 let tail = selection.tail();
13063
13064 let Some((buffer, start_position)) =
13065 self.buffer.read(cx).text_anchor_for_position(head, cx)
13066 else {
13067 return;
13068 };
13069
13070 let end_position = if head != tail {
13071 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13072 return;
13073 };
13074 Some(pos)
13075 } else {
13076 None
13077 };
13078
13079 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13080 let url = if let Some(end_pos) = end_position {
13081 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13082 } else {
13083 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13084 };
13085
13086 if let Some(url) = url {
13087 editor.update(cx, |_, cx| {
13088 cx.open_url(&url);
13089 })
13090 } else {
13091 Ok(())
13092 }
13093 });
13094
13095 url_finder.detach();
13096 }
13097
13098 pub fn open_selected_filename(
13099 &mut self,
13100 _: &OpenSelectedFilename,
13101 window: &mut Window,
13102 cx: &mut Context<Self>,
13103 ) {
13104 let Some(workspace) = self.workspace() else {
13105 return;
13106 };
13107
13108 let position = self.selections.newest_anchor().head();
13109
13110 let Some((buffer, buffer_position)) =
13111 self.buffer.read(cx).text_anchor_for_position(position, cx)
13112 else {
13113 return;
13114 };
13115
13116 let project = self.project.clone();
13117
13118 cx.spawn_in(window, async move |_, cx| {
13119 let result = find_file(&buffer, project, buffer_position, cx).await;
13120
13121 if let Some((_, path)) = result {
13122 workspace
13123 .update_in(cx, |workspace, window, cx| {
13124 workspace.open_resolved_path(path, window, cx)
13125 })?
13126 .await?;
13127 }
13128 anyhow::Ok(())
13129 })
13130 .detach();
13131 }
13132
13133 pub(crate) fn navigate_to_hover_links(
13134 &mut self,
13135 kind: Option<GotoDefinitionKind>,
13136 mut definitions: Vec<HoverLink>,
13137 split: bool,
13138 window: &mut Window,
13139 cx: &mut Context<Editor>,
13140 ) -> Task<Result<Navigated>> {
13141 // If there is one definition, just open it directly
13142 if definitions.len() == 1 {
13143 let definition = definitions.pop().unwrap();
13144
13145 enum TargetTaskResult {
13146 Location(Option<Location>),
13147 AlreadyNavigated,
13148 }
13149
13150 let target_task = match definition {
13151 HoverLink::Text(link) => {
13152 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13153 }
13154 HoverLink::InlayHint(lsp_location, server_id) => {
13155 let computation =
13156 self.compute_target_location(lsp_location, server_id, window, cx);
13157 cx.background_spawn(async move {
13158 let location = computation.await?;
13159 Ok(TargetTaskResult::Location(location))
13160 })
13161 }
13162 HoverLink::Url(url) => {
13163 cx.open_url(&url);
13164 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13165 }
13166 HoverLink::File(path) => {
13167 if let Some(workspace) = self.workspace() {
13168 cx.spawn_in(window, async move |_, cx| {
13169 workspace
13170 .update_in(cx, |workspace, window, cx| {
13171 workspace.open_resolved_path(path, window, cx)
13172 })?
13173 .await
13174 .map(|_| TargetTaskResult::AlreadyNavigated)
13175 })
13176 } else {
13177 Task::ready(Ok(TargetTaskResult::Location(None)))
13178 }
13179 }
13180 };
13181 cx.spawn_in(window, async move |editor, cx| {
13182 let target = match target_task.await.context("target resolution task")? {
13183 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13184 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13185 TargetTaskResult::Location(Some(target)) => target,
13186 };
13187
13188 editor.update_in(cx, |editor, window, cx| {
13189 let Some(workspace) = editor.workspace() else {
13190 return Navigated::No;
13191 };
13192 let pane = workspace.read(cx).active_pane().clone();
13193
13194 let range = target.range.to_point(target.buffer.read(cx));
13195 let range = editor.range_for_match(&range);
13196 let range = collapse_multiline_range(range);
13197
13198 if !split
13199 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13200 {
13201 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13202 } else {
13203 window.defer(cx, move |window, cx| {
13204 let target_editor: Entity<Self> =
13205 workspace.update(cx, |workspace, cx| {
13206 let pane = if split {
13207 workspace.adjacent_pane(window, cx)
13208 } else {
13209 workspace.active_pane().clone()
13210 };
13211
13212 workspace.open_project_item(
13213 pane,
13214 target.buffer.clone(),
13215 true,
13216 true,
13217 window,
13218 cx,
13219 )
13220 });
13221 target_editor.update(cx, |target_editor, cx| {
13222 // When selecting a definition in a different buffer, disable the nav history
13223 // to avoid creating a history entry at the previous cursor location.
13224 pane.update(cx, |pane, _| pane.disable_history());
13225 target_editor.go_to_singleton_buffer_range(range, window, cx);
13226 pane.update(cx, |pane, _| pane.enable_history());
13227 });
13228 });
13229 }
13230 Navigated::Yes
13231 })
13232 })
13233 } else if !definitions.is_empty() {
13234 cx.spawn_in(window, async move |editor, cx| {
13235 let (title, location_tasks, workspace) = editor
13236 .update_in(cx, |editor, window, cx| {
13237 let tab_kind = match kind {
13238 Some(GotoDefinitionKind::Implementation) => "Implementations",
13239 _ => "Definitions",
13240 };
13241 let title = definitions
13242 .iter()
13243 .find_map(|definition| match definition {
13244 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13245 let buffer = origin.buffer.read(cx);
13246 format!(
13247 "{} for {}",
13248 tab_kind,
13249 buffer
13250 .text_for_range(origin.range.clone())
13251 .collect::<String>()
13252 )
13253 }),
13254 HoverLink::InlayHint(_, _) => None,
13255 HoverLink::Url(_) => None,
13256 HoverLink::File(_) => None,
13257 })
13258 .unwrap_or(tab_kind.to_string());
13259 let location_tasks = definitions
13260 .into_iter()
13261 .map(|definition| match definition {
13262 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13263 HoverLink::InlayHint(lsp_location, server_id) => editor
13264 .compute_target_location(lsp_location, server_id, window, cx),
13265 HoverLink::Url(_) => Task::ready(Ok(None)),
13266 HoverLink::File(_) => Task::ready(Ok(None)),
13267 })
13268 .collect::<Vec<_>>();
13269 (title, location_tasks, editor.workspace().clone())
13270 })
13271 .context("location tasks preparation")?;
13272
13273 let locations = future::join_all(location_tasks)
13274 .await
13275 .into_iter()
13276 .filter_map(|location| location.transpose())
13277 .collect::<Result<_>>()
13278 .context("location tasks")?;
13279
13280 let Some(workspace) = workspace else {
13281 return Ok(Navigated::No);
13282 };
13283 let opened = workspace
13284 .update_in(cx, |workspace, window, cx| {
13285 Self::open_locations_in_multibuffer(
13286 workspace,
13287 locations,
13288 title,
13289 split,
13290 MultibufferSelectionMode::First,
13291 window,
13292 cx,
13293 )
13294 })
13295 .ok();
13296
13297 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13298 })
13299 } else {
13300 Task::ready(Ok(Navigated::No))
13301 }
13302 }
13303
13304 fn compute_target_location(
13305 &self,
13306 lsp_location: lsp::Location,
13307 server_id: LanguageServerId,
13308 window: &mut Window,
13309 cx: &mut Context<Self>,
13310 ) -> Task<anyhow::Result<Option<Location>>> {
13311 let Some(project) = self.project.clone() else {
13312 return Task::ready(Ok(None));
13313 };
13314
13315 cx.spawn_in(window, async move |editor, cx| {
13316 let location_task = editor.update(cx, |_, cx| {
13317 project.update(cx, |project, cx| {
13318 let language_server_name = project
13319 .language_server_statuses(cx)
13320 .find(|(id, _)| server_id == *id)
13321 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13322 language_server_name.map(|language_server_name| {
13323 project.open_local_buffer_via_lsp(
13324 lsp_location.uri.clone(),
13325 server_id,
13326 language_server_name,
13327 cx,
13328 )
13329 })
13330 })
13331 })?;
13332 let location = match location_task {
13333 Some(task) => Some({
13334 let target_buffer_handle = task.await.context("open local buffer")?;
13335 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13336 let target_start = target_buffer
13337 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13338 let target_end = target_buffer
13339 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13340 target_buffer.anchor_after(target_start)
13341 ..target_buffer.anchor_before(target_end)
13342 })?;
13343 Location {
13344 buffer: target_buffer_handle,
13345 range,
13346 }
13347 }),
13348 None => None,
13349 };
13350 Ok(location)
13351 })
13352 }
13353
13354 pub fn find_all_references(
13355 &mut self,
13356 _: &FindAllReferences,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) -> Option<Task<Result<Navigated>>> {
13360 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13361
13362 let selection = self.selections.newest::<usize>(cx);
13363 let multi_buffer = self.buffer.read(cx);
13364 let head = selection.head();
13365
13366 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13367 let head_anchor = multi_buffer_snapshot.anchor_at(
13368 head,
13369 if head < selection.tail() {
13370 Bias::Right
13371 } else {
13372 Bias::Left
13373 },
13374 );
13375
13376 match self
13377 .find_all_references_task_sources
13378 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13379 {
13380 Ok(_) => {
13381 log::info!(
13382 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13383 );
13384 return None;
13385 }
13386 Err(i) => {
13387 self.find_all_references_task_sources.insert(i, head_anchor);
13388 }
13389 }
13390
13391 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13392 let workspace = self.workspace()?;
13393 let project = workspace.read(cx).project().clone();
13394 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13395 Some(cx.spawn_in(window, async move |editor, cx| {
13396 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13397 if let Ok(i) = editor
13398 .find_all_references_task_sources
13399 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13400 {
13401 editor.find_all_references_task_sources.remove(i);
13402 }
13403 });
13404
13405 let locations = references.await?;
13406 if locations.is_empty() {
13407 return anyhow::Ok(Navigated::No);
13408 }
13409
13410 workspace.update_in(cx, |workspace, window, cx| {
13411 let title = locations
13412 .first()
13413 .as_ref()
13414 .map(|location| {
13415 let buffer = location.buffer.read(cx);
13416 format!(
13417 "References to `{}`",
13418 buffer
13419 .text_for_range(location.range.clone())
13420 .collect::<String>()
13421 )
13422 })
13423 .unwrap();
13424 Self::open_locations_in_multibuffer(
13425 workspace,
13426 locations,
13427 title,
13428 false,
13429 MultibufferSelectionMode::First,
13430 window,
13431 cx,
13432 );
13433 Navigated::Yes
13434 })
13435 }))
13436 }
13437
13438 /// Opens a multibuffer with the given project locations in it
13439 pub fn open_locations_in_multibuffer(
13440 workspace: &mut Workspace,
13441 mut locations: Vec<Location>,
13442 title: String,
13443 split: bool,
13444 multibuffer_selection_mode: MultibufferSelectionMode,
13445 window: &mut Window,
13446 cx: &mut Context<Workspace>,
13447 ) {
13448 // If there are multiple definitions, open them in a multibuffer
13449 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13450 let mut locations = locations.into_iter().peekable();
13451 let mut ranges = Vec::new();
13452 let capability = workspace.project().read(cx).capability();
13453
13454 let excerpt_buffer = cx.new(|cx| {
13455 let mut multibuffer = MultiBuffer::new(capability);
13456 while let Some(location) = locations.next() {
13457 let buffer = location.buffer.read(cx);
13458 let mut ranges_for_buffer = Vec::new();
13459 let range = location.range.to_offset(buffer);
13460 ranges_for_buffer.push(range.clone());
13461
13462 while let Some(next_location) = locations.peek() {
13463 if next_location.buffer == location.buffer {
13464 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13465 locations.next();
13466 } else {
13467 break;
13468 }
13469 }
13470
13471 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13472 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13473 location.buffer.clone(),
13474 ranges_for_buffer,
13475 DEFAULT_MULTIBUFFER_CONTEXT,
13476 cx,
13477 ))
13478 }
13479
13480 multibuffer.with_title(title)
13481 });
13482
13483 let editor = cx.new(|cx| {
13484 Editor::for_multibuffer(
13485 excerpt_buffer,
13486 Some(workspace.project().clone()),
13487 window,
13488 cx,
13489 )
13490 });
13491 editor.update(cx, |editor, cx| {
13492 match multibuffer_selection_mode {
13493 MultibufferSelectionMode::First => {
13494 if let Some(first_range) = ranges.first() {
13495 editor.change_selections(None, window, cx, |selections| {
13496 selections.clear_disjoint();
13497 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13498 });
13499 }
13500 editor.highlight_background::<Self>(
13501 &ranges,
13502 |theme| theme.editor_highlighted_line_background,
13503 cx,
13504 );
13505 }
13506 MultibufferSelectionMode::All => {
13507 editor.change_selections(None, window, cx, |selections| {
13508 selections.clear_disjoint();
13509 selections.select_anchor_ranges(ranges);
13510 });
13511 }
13512 }
13513 editor.register_buffers_with_language_servers(cx);
13514 });
13515
13516 let item = Box::new(editor);
13517 let item_id = item.item_id();
13518
13519 if split {
13520 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13521 } else {
13522 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13523 let (preview_item_id, preview_item_idx) =
13524 workspace.active_pane().update(cx, |pane, _| {
13525 (pane.preview_item_id(), pane.preview_item_idx())
13526 });
13527
13528 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13529
13530 if let Some(preview_item_id) = preview_item_id {
13531 workspace.active_pane().update(cx, |pane, cx| {
13532 pane.remove_item(preview_item_id, false, false, window, cx);
13533 });
13534 }
13535 } else {
13536 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13537 }
13538 }
13539 workspace.active_pane().update(cx, |pane, cx| {
13540 pane.set_preview_item_id(Some(item_id), cx);
13541 });
13542 }
13543
13544 pub fn rename(
13545 &mut self,
13546 _: &Rename,
13547 window: &mut Window,
13548 cx: &mut Context<Self>,
13549 ) -> Option<Task<Result<()>>> {
13550 use language::ToOffset as _;
13551
13552 let provider = self.semantics_provider.clone()?;
13553 let selection = self.selections.newest_anchor().clone();
13554 let (cursor_buffer, cursor_buffer_position) = self
13555 .buffer
13556 .read(cx)
13557 .text_anchor_for_position(selection.head(), cx)?;
13558 let (tail_buffer, cursor_buffer_position_end) = self
13559 .buffer
13560 .read(cx)
13561 .text_anchor_for_position(selection.tail(), cx)?;
13562 if tail_buffer != cursor_buffer {
13563 return None;
13564 }
13565
13566 let snapshot = cursor_buffer.read(cx).snapshot();
13567 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13568 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13569 let prepare_rename = provider
13570 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13571 .unwrap_or_else(|| Task::ready(Ok(None)));
13572 drop(snapshot);
13573
13574 Some(cx.spawn_in(window, async move |this, cx| {
13575 let rename_range = if let Some(range) = prepare_rename.await? {
13576 Some(range)
13577 } else {
13578 this.update(cx, |this, cx| {
13579 let buffer = this.buffer.read(cx).snapshot(cx);
13580 let mut buffer_highlights = this
13581 .document_highlights_for_position(selection.head(), &buffer)
13582 .filter(|highlight| {
13583 highlight.start.excerpt_id == selection.head().excerpt_id
13584 && highlight.end.excerpt_id == selection.head().excerpt_id
13585 });
13586 buffer_highlights
13587 .next()
13588 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13589 })?
13590 };
13591 if let Some(rename_range) = rename_range {
13592 this.update_in(cx, |this, window, cx| {
13593 let snapshot = cursor_buffer.read(cx).snapshot();
13594 let rename_buffer_range = rename_range.to_offset(&snapshot);
13595 let cursor_offset_in_rename_range =
13596 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13597 let cursor_offset_in_rename_range_end =
13598 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13599
13600 this.take_rename(false, window, cx);
13601 let buffer = this.buffer.read(cx).read(cx);
13602 let cursor_offset = selection.head().to_offset(&buffer);
13603 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13604 let rename_end = rename_start + rename_buffer_range.len();
13605 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13606 let mut old_highlight_id = None;
13607 let old_name: Arc<str> = buffer
13608 .chunks(rename_start..rename_end, true)
13609 .map(|chunk| {
13610 if old_highlight_id.is_none() {
13611 old_highlight_id = chunk.syntax_highlight_id;
13612 }
13613 chunk.text
13614 })
13615 .collect::<String>()
13616 .into();
13617
13618 drop(buffer);
13619
13620 // Position the selection in the rename editor so that it matches the current selection.
13621 this.show_local_selections = false;
13622 let rename_editor = cx.new(|cx| {
13623 let mut editor = Editor::single_line(window, cx);
13624 editor.buffer.update(cx, |buffer, cx| {
13625 buffer.edit([(0..0, old_name.clone())], None, cx)
13626 });
13627 let rename_selection_range = match cursor_offset_in_rename_range
13628 .cmp(&cursor_offset_in_rename_range_end)
13629 {
13630 Ordering::Equal => {
13631 editor.select_all(&SelectAll, window, cx);
13632 return editor;
13633 }
13634 Ordering::Less => {
13635 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13636 }
13637 Ordering::Greater => {
13638 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13639 }
13640 };
13641 if rename_selection_range.end > old_name.len() {
13642 editor.select_all(&SelectAll, window, cx);
13643 } else {
13644 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13645 s.select_ranges([rename_selection_range]);
13646 });
13647 }
13648 editor
13649 });
13650 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13651 if e == &EditorEvent::Focused {
13652 cx.emit(EditorEvent::FocusedIn)
13653 }
13654 })
13655 .detach();
13656
13657 let write_highlights =
13658 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13659 let read_highlights =
13660 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13661 let ranges = write_highlights
13662 .iter()
13663 .flat_map(|(_, ranges)| ranges.iter())
13664 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13665 .cloned()
13666 .collect();
13667
13668 this.highlight_text::<Rename>(
13669 ranges,
13670 HighlightStyle {
13671 fade_out: Some(0.6),
13672 ..Default::default()
13673 },
13674 cx,
13675 );
13676 let rename_focus_handle = rename_editor.focus_handle(cx);
13677 window.focus(&rename_focus_handle);
13678 let block_id = this.insert_blocks(
13679 [BlockProperties {
13680 style: BlockStyle::Flex,
13681 placement: BlockPlacement::Below(range.start),
13682 height: 1,
13683 render: Arc::new({
13684 let rename_editor = rename_editor.clone();
13685 move |cx: &mut BlockContext| {
13686 let mut text_style = cx.editor_style.text.clone();
13687 if let Some(highlight_style) = old_highlight_id
13688 .and_then(|h| h.style(&cx.editor_style.syntax))
13689 {
13690 text_style = text_style.highlight(highlight_style);
13691 }
13692 div()
13693 .block_mouse_down()
13694 .pl(cx.anchor_x)
13695 .child(EditorElement::new(
13696 &rename_editor,
13697 EditorStyle {
13698 background: cx.theme().system().transparent,
13699 local_player: cx.editor_style.local_player,
13700 text: text_style,
13701 scrollbar_width: cx.editor_style.scrollbar_width,
13702 syntax: cx.editor_style.syntax.clone(),
13703 status: cx.editor_style.status.clone(),
13704 inlay_hints_style: HighlightStyle {
13705 font_weight: Some(FontWeight::BOLD),
13706 ..make_inlay_hints_style(cx.app)
13707 },
13708 inline_completion_styles: make_suggestion_styles(
13709 cx.app,
13710 ),
13711 ..EditorStyle::default()
13712 },
13713 ))
13714 .into_any_element()
13715 }
13716 }),
13717 priority: 0,
13718 }],
13719 Some(Autoscroll::fit()),
13720 cx,
13721 )[0];
13722 this.pending_rename = Some(RenameState {
13723 range,
13724 old_name,
13725 editor: rename_editor,
13726 block_id,
13727 });
13728 })?;
13729 }
13730
13731 Ok(())
13732 }))
13733 }
13734
13735 pub fn confirm_rename(
13736 &mut self,
13737 _: &ConfirmRename,
13738 window: &mut Window,
13739 cx: &mut Context<Self>,
13740 ) -> Option<Task<Result<()>>> {
13741 let rename = self.take_rename(false, window, cx)?;
13742 let workspace = self.workspace()?.downgrade();
13743 let (buffer, start) = self
13744 .buffer
13745 .read(cx)
13746 .text_anchor_for_position(rename.range.start, cx)?;
13747 let (end_buffer, _) = self
13748 .buffer
13749 .read(cx)
13750 .text_anchor_for_position(rename.range.end, cx)?;
13751 if buffer != end_buffer {
13752 return None;
13753 }
13754
13755 let old_name = rename.old_name;
13756 let new_name = rename.editor.read(cx).text(cx);
13757
13758 let rename = self.semantics_provider.as_ref()?.perform_rename(
13759 &buffer,
13760 start,
13761 new_name.clone(),
13762 cx,
13763 )?;
13764
13765 Some(cx.spawn_in(window, async move |editor, cx| {
13766 let project_transaction = rename.await?;
13767 Self::open_project_transaction(
13768 &editor,
13769 workspace,
13770 project_transaction,
13771 format!("Rename: {} → {}", old_name, new_name),
13772 cx,
13773 )
13774 .await?;
13775
13776 editor.update(cx, |editor, cx| {
13777 editor.refresh_document_highlights(cx);
13778 })?;
13779 Ok(())
13780 }))
13781 }
13782
13783 fn take_rename(
13784 &mut self,
13785 moving_cursor: bool,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) -> Option<RenameState> {
13789 let rename = self.pending_rename.take()?;
13790 if rename.editor.focus_handle(cx).is_focused(window) {
13791 window.focus(&self.focus_handle);
13792 }
13793
13794 self.remove_blocks(
13795 [rename.block_id].into_iter().collect(),
13796 Some(Autoscroll::fit()),
13797 cx,
13798 );
13799 self.clear_highlights::<Rename>(cx);
13800 self.show_local_selections = true;
13801
13802 if moving_cursor {
13803 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13804 editor.selections.newest::<usize>(cx).head()
13805 });
13806
13807 // Update the selection to match the position of the selection inside
13808 // the rename editor.
13809 let snapshot = self.buffer.read(cx).read(cx);
13810 let rename_range = rename.range.to_offset(&snapshot);
13811 let cursor_in_editor = snapshot
13812 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13813 .min(rename_range.end);
13814 drop(snapshot);
13815
13816 self.change_selections(None, window, cx, |s| {
13817 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13818 });
13819 } else {
13820 self.refresh_document_highlights(cx);
13821 }
13822
13823 Some(rename)
13824 }
13825
13826 pub fn pending_rename(&self) -> Option<&RenameState> {
13827 self.pending_rename.as_ref()
13828 }
13829
13830 fn format(
13831 &mut self,
13832 _: &Format,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) -> Option<Task<Result<()>>> {
13836 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13837
13838 let project = match &self.project {
13839 Some(project) => project.clone(),
13840 None => return None,
13841 };
13842
13843 Some(self.perform_format(
13844 project,
13845 FormatTrigger::Manual,
13846 FormatTarget::Buffers,
13847 window,
13848 cx,
13849 ))
13850 }
13851
13852 fn format_selections(
13853 &mut self,
13854 _: &FormatSelections,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) -> Option<Task<Result<()>>> {
13858 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13859
13860 let project = match &self.project {
13861 Some(project) => project.clone(),
13862 None => return None,
13863 };
13864
13865 let ranges = self
13866 .selections
13867 .all_adjusted(cx)
13868 .into_iter()
13869 .map(|selection| selection.range())
13870 .collect_vec();
13871
13872 Some(self.perform_format(
13873 project,
13874 FormatTrigger::Manual,
13875 FormatTarget::Ranges(ranges),
13876 window,
13877 cx,
13878 ))
13879 }
13880
13881 fn perform_format(
13882 &mut self,
13883 project: Entity<Project>,
13884 trigger: FormatTrigger,
13885 target: FormatTarget,
13886 window: &mut Window,
13887 cx: &mut Context<Self>,
13888 ) -> Task<Result<()>> {
13889 let buffer = self.buffer.clone();
13890 let (buffers, target) = match target {
13891 FormatTarget::Buffers => {
13892 let mut buffers = buffer.read(cx).all_buffers();
13893 if trigger == FormatTrigger::Save {
13894 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13895 }
13896 (buffers, LspFormatTarget::Buffers)
13897 }
13898 FormatTarget::Ranges(selection_ranges) => {
13899 let multi_buffer = buffer.read(cx);
13900 let snapshot = multi_buffer.read(cx);
13901 let mut buffers = HashSet::default();
13902 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13903 BTreeMap::new();
13904 for selection_range in selection_ranges {
13905 for (buffer, buffer_range, _) in
13906 snapshot.range_to_buffer_ranges(selection_range)
13907 {
13908 let buffer_id = buffer.remote_id();
13909 let start = buffer.anchor_before(buffer_range.start);
13910 let end = buffer.anchor_after(buffer_range.end);
13911 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13912 buffer_id_to_ranges
13913 .entry(buffer_id)
13914 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13915 .or_insert_with(|| vec![start..end]);
13916 }
13917 }
13918 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13919 }
13920 };
13921
13922 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13923 let format = project.update(cx, |project, cx| {
13924 project.format(buffers, target, true, trigger, cx)
13925 });
13926
13927 cx.spawn_in(window, async move |_, cx| {
13928 let transaction = futures::select_biased! {
13929 transaction = format.log_err().fuse() => transaction,
13930 () = timeout => {
13931 log::warn!("timed out waiting for formatting");
13932 None
13933 }
13934 };
13935
13936 buffer
13937 .update(cx, |buffer, cx| {
13938 if let Some(transaction) = transaction {
13939 if !buffer.is_singleton() {
13940 buffer.push_transaction(&transaction.0, cx);
13941 }
13942 }
13943 cx.notify();
13944 })
13945 .ok();
13946
13947 Ok(())
13948 })
13949 }
13950
13951 fn organize_imports(
13952 &mut self,
13953 _: &OrganizeImports,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) -> Option<Task<Result<()>>> {
13957 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13958 let project = match &self.project {
13959 Some(project) => project.clone(),
13960 None => return None,
13961 };
13962 Some(self.perform_code_action_kind(
13963 project,
13964 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13965 window,
13966 cx,
13967 ))
13968 }
13969
13970 fn perform_code_action_kind(
13971 &mut self,
13972 project: Entity<Project>,
13973 kind: CodeActionKind,
13974 window: &mut Window,
13975 cx: &mut Context<Self>,
13976 ) -> Task<Result<()>> {
13977 let buffer = self.buffer.clone();
13978 let buffers = buffer.read(cx).all_buffers();
13979 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
13980 let apply_action = project.update(cx, |project, cx| {
13981 project.apply_code_action_kind(buffers, kind, true, cx)
13982 });
13983 cx.spawn_in(window, async move |_, cx| {
13984 let transaction = futures::select_biased! {
13985 () = timeout => {
13986 log::warn!("timed out waiting for executing code action");
13987 None
13988 }
13989 transaction = apply_action.log_err().fuse() => transaction,
13990 };
13991 buffer
13992 .update(cx, |buffer, cx| {
13993 // check if we need this
13994 if let Some(transaction) = transaction {
13995 if !buffer.is_singleton() {
13996 buffer.push_transaction(&transaction.0, cx);
13997 }
13998 }
13999 cx.notify();
14000 })
14001 .ok();
14002 Ok(())
14003 })
14004 }
14005
14006 fn restart_language_server(
14007 &mut self,
14008 _: &RestartLanguageServer,
14009 _: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if let Some(project) = self.project.clone() {
14013 self.buffer.update(cx, |multi_buffer, cx| {
14014 project.update(cx, |project, cx| {
14015 project.restart_language_servers_for_buffers(
14016 multi_buffer.all_buffers().into_iter().collect(),
14017 cx,
14018 );
14019 });
14020 })
14021 }
14022 }
14023
14024 fn cancel_language_server_work(
14025 workspace: &mut Workspace,
14026 _: &actions::CancelLanguageServerWork,
14027 _: &mut Window,
14028 cx: &mut Context<Workspace>,
14029 ) {
14030 let project = workspace.project();
14031 let buffers = workspace
14032 .active_item(cx)
14033 .and_then(|item| item.act_as::<Editor>(cx))
14034 .map_or(HashSet::default(), |editor| {
14035 editor.read(cx).buffer.read(cx).all_buffers()
14036 });
14037 project.update(cx, |project, cx| {
14038 project.cancel_language_server_work_for_buffers(buffers, cx);
14039 });
14040 }
14041
14042 fn show_character_palette(
14043 &mut self,
14044 _: &ShowCharacterPalette,
14045 window: &mut Window,
14046 _: &mut Context<Self>,
14047 ) {
14048 window.show_character_palette();
14049 }
14050
14051 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14052 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14053 let buffer = self.buffer.read(cx).snapshot(cx);
14054 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14055 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14056 let is_valid = buffer
14057 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14058 .any(|entry| {
14059 entry.diagnostic.is_primary
14060 && !entry.range.is_empty()
14061 && entry.range.start == primary_range_start
14062 && entry.diagnostic.message == active_diagnostics.primary_message
14063 });
14064
14065 if is_valid != active_diagnostics.is_valid {
14066 active_diagnostics.is_valid = is_valid;
14067 if is_valid {
14068 let mut new_styles = HashMap::default();
14069 for (block_id, diagnostic) in &active_diagnostics.blocks {
14070 new_styles.insert(
14071 *block_id,
14072 diagnostic_block_renderer(diagnostic.clone(), None, true),
14073 );
14074 }
14075 self.display_map.update(cx, |display_map, _cx| {
14076 display_map.replace_blocks(new_styles);
14077 });
14078 } else {
14079 self.dismiss_diagnostics(cx);
14080 }
14081 }
14082 }
14083 }
14084
14085 fn activate_diagnostics(
14086 &mut self,
14087 buffer_id: BufferId,
14088 group_id: usize,
14089 window: &mut Window,
14090 cx: &mut Context<Self>,
14091 ) {
14092 self.dismiss_diagnostics(cx);
14093 let snapshot = self.snapshot(window, cx);
14094 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14095 let buffer = self.buffer.read(cx).snapshot(cx);
14096
14097 let mut primary_range = None;
14098 let mut primary_message = None;
14099 let diagnostic_group = buffer
14100 .diagnostic_group(buffer_id, group_id)
14101 .filter_map(|entry| {
14102 let start = entry.range.start;
14103 let end = entry.range.end;
14104 if snapshot.is_line_folded(MultiBufferRow(start.row))
14105 && (start.row == end.row
14106 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14107 {
14108 return None;
14109 }
14110 if entry.diagnostic.is_primary {
14111 primary_range = Some(entry.range.clone());
14112 primary_message = Some(entry.diagnostic.message.clone());
14113 }
14114 Some(entry)
14115 })
14116 .collect::<Vec<_>>();
14117 let primary_range = primary_range?;
14118 let primary_message = primary_message?;
14119
14120 let blocks = display_map
14121 .insert_blocks(
14122 diagnostic_group.iter().map(|entry| {
14123 let diagnostic = entry.diagnostic.clone();
14124 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14125 BlockProperties {
14126 style: BlockStyle::Fixed,
14127 placement: BlockPlacement::Below(
14128 buffer.anchor_after(entry.range.start),
14129 ),
14130 height: message_height,
14131 render: diagnostic_block_renderer(diagnostic, None, true),
14132 priority: 0,
14133 }
14134 }),
14135 cx,
14136 )
14137 .into_iter()
14138 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14139 .collect();
14140
14141 Some(ActiveDiagnosticGroup {
14142 primary_range: buffer.anchor_before(primary_range.start)
14143 ..buffer.anchor_after(primary_range.end),
14144 primary_message,
14145 group_id,
14146 blocks,
14147 is_valid: true,
14148 })
14149 });
14150 }
14151
14152 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14153 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14154 self.display_map.update(cx, |display_map, cx| {
14155 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14156 });
14157 cx.notify();
14158 }
14159 }
14160
14161 /// Disable inline diagnostics rendering for this editor.
14162 pub fn disable_inline_diagnostics(&mut self) {
14163 self.inline_diagnostics_enabled = false;
14164 self.inline_diagnostics_update = Task::ready(());
14165 self.inline_diagnostics.clear();
14166 }
14167
14168 pub fn inline_diagnostics_enabled(&self) -> bool {
14169 self.inline_diagnostics_enabled
14170 }
14171
14172 pub fn show_inline_diagnostics(&self) -> bool {
14173 self.show_inline_diagnostics
14174 }
14175
14176 pub fn toggle_inline_diagnostics(
14177 &mut self,
14178 _: &ToggleInlineDiagnostics,
14179 window: &mut Window,
14180 cx: &mut Context<Editor>,
14181 ) {
14182 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14183 self.refresh_inline_diagnostics(false, window, cx);
14184 }
14185
14186 fn refresh_inline_diagnostics(
14187 &mut self,
14188 debounce: bool,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) {
14192 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14193 self.inline_diagnostics_update = Task::ready(());
14194 self.inline_diagnostics.clear();
14195 return;
14196 }
14197
14198 let debounce_ms = ProjectSettings::get_global(cx)
14199 .diagnostics
14200 .inline
14201 .update_debounce_ms;
14202 let debounce = if debounce && debounce_ms > 0 {
14203 Some(Duration::from_millis(debounce_ms))
14204 } else {
14205 None
14206 };
14207 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14208 if let Some(debounce) = debounce {
14209 cx.background_executor().timer(debounce).await;
14210 }
14211 let Some(snapshot) = editor
14212 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14213 .ok()
14214 else {
14215 return;
14216 };
14217
14218 let new_inline_diagnostics = cx
14219 .background_spawn(async move {
14220 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14221 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14222 let message = diagnostic_entry
14223 .diagnostic
14224 .message
14225 .split_once('\n')
14226 .map(|(line, _)| line)
14227 .map(SharedString::new)
14228 .unwrap_or_else(|| {
14229 SharedString::from(diagnostic_entry.diagnostic.message)
14230 });
14231 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14232 let (Ok(i) | Err(i)) = inline_diagnostics
14233 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14234 inline_diagnostics.insert(
14235 i,
14236 (
14237 start_anchor,
14238 InlineDiagnostic {
14239 message,
14240 group_id: diagnostic_entry.diagnostic.group_id,
14241 start: diagnostic_entry.range.start.to_point(&snapshot),
14242 is_primary: diagnostic_entry.diagnostic.is_primary,
14243 severity: diagnostic_entry.diagnostic.severity,
14244 },
14245 ),
14246 );
14247 }
14248 inline_diagnostics
14249 })
14250 .await;
14251
14252 editor
14253 .update(cx, |editor, cx| {
14254 editor.inline_diagnostics = new_inline_diagnostics;
14255 cx.notify();
14256 })
14257 .ok();
14258 });
14259 }
14260
14261 pub fn set_selections_from_remote(
14262 &mut self,
14263 selections: Vec<Selection<Anchor>>,
14264 pending_selection: Option<Selection<Anchor>>,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 let old_cursor_position = self.selections.newest_anchor().head();
14269 self.selections.change_with(cx, |s| {
14270 s.select_anchors(selections);
14271 if let Some(pending_selection) = pending_selection {
14272 s.set_pending(pending_selection, SelectMode::Character);
14273 } else {
14274 s.clear_pending();
14275 }
14276 });
14277 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14278 }
14279
14280 fn push_to_selection_history(&mut self) {
14281 self.selection_history.push(SelectionHistoryEntry {
14282 selections: self.selections.disjoint_anchors(),
14283 select_next_state: self.select_next_state.clone(),
14284 select_prev_state: self.select_prev_state.clone(),
14285 add_selections_state: self.add_selections_state.clone(),
14286 });
14287 }
14288
14289 pub fn transact(
14290 &mut self,
14291 window: &mut Window,
14292 cx: &mut Context<Self>,
14293 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14294 ) -> Option<TransactionId> {
14295 self.start_transaction_at(Instant::now(), window, cx);
14296 update(self, window, cx);
14297 self.end_transaction_at(Instant::now(), cx)
14298 }
14299
14300 pub fn start_transaction_at(
14301 &mut self,
14302 now: Instant,
14303 window: &mut Window,
14304 cx: &mut Context<Self>,
14305 ) {
14306 self.end_selection(window, cx);
14307 if let Some(tx_id) = self
14308 .buffer
14309 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14310 {
14311 self.selection_history
14312 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14313 cx.emit(EditorEvent::TransactionBegun {
14314 transaction_id: tx_id,
14315 })
14316 }
14317 }
14318
14319 pub fn end_transaction_at(
14320 &mut self,
14321 now: Instant,
14322 cx: &mut Context<Self>,
14323 ) -> Option<TransactionId> {
14324 if let Some(transaction_id) = self
14325 .buffer
14326 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14327 {
14328 if let Some((_, end_selections)) =
14329 self.selection_history.transaction_mut(transaction_id)
14330 {
14331 *end_selections = Some(self.selections.disjoint_anchors());
14332 } else {
14333 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14334 }
14335
14336 cx.emit(EditorEvent::Edited { transaction_id });
14337 Some(transaction_id)
14338 } else {
14339 None
14340 }
14341 }
14342
14343 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14344 if self.selection_mark_mode {
14345 self.change_selections(None, window, cx, |s| {
14346 s.move_with(|_, sel| {
14347 sel.collapse_to(sel.head(), SelectionGoal::None);
14348 });
14349 })
14350 }
14351 self.selection_mark_mode = true;
14352 cx.notify();
14353 }
14354
14355 pub fn swap_selection_ends(
14356 &mut self,
14357 _: &actions::SwapSelectionEnds,
14358 window: &mut Window,
14359 cx: &mut Context<Self>,
14360 ) {
14361 self.change_selections(None, window, cx, |s| {
14362 s.move_with(|_, sel| {
14363 if sel.start != sel.end {
14364 sel.reversed = !sel.reversed
14365 }
14366 });
14367 });
14368 self.request_autoscroll(Autoscroll::newest(), cx);
14369 cx.notify();
14370 }
14371
14372 pub fn toggle_fold(
14373 &mut self,
14374 _: &actions::ToggleFold,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 if self.is_singleton(cx) {
14379 let selection = self.selections.newest::<Point>(cx);
14380
14381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14382 let range = if selection.is_empty() {
14383 let point = selection.head().to_display_point(&display_map);
14384 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14385 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14386 .to_point(&display_map);
14387 start..end
14388 } else {
14389 selection.range()
14390 };
14391 if display_map.folds_in_range(range).next().is_some() {
14392 self.unfold_lines(&Default::default(), window, cx)
14393 } else {
14394 self.fold(&Default::default(), window, cx)
14395 }
14396 } else {
14397 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14398 let buffer_ids: HashSet<_> = self
14399 .selections
14400 .disjoint_anchor_ranges()
14401 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14402 .collect();
14403
14404 let should_unfold = buffer_ids
14405 .iter()
14406 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14407
14408 for buffer_id in buffer_ids {
14409 if should_unfold {
14410 self.unfold_buffer(buffer_id, cx);
14411 } else {
14412 self.fold_buffer(buffer_id, cx);
14413 }
14414 }
14415 }
14416 }
14417
14418 pub fn toggle_fold_recursive(
14419 &mut self,
14420 _: &actions::ToggleFoldRecursive,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) {
14424 let selection = self.selections.newest::<Point>(cx);
14425
14426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14427 let range = if selection.is_empty() {
14428 let point = selection.head().to_display_point(&display_map);
14429 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14430 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14431 .to_point(&display_map);
14432 start..end
14433 } else {
14434 selection.range()
14435 };
14436 if display_map.folds_in_range(range).next().is_some() {
14437 self.unfold_recursive(&Default::default(), window, cx)
14438 } else {
14439 self.fold_recursive(&Default::default(), window, cx)
14440 }
14441 }
14442
14443 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14444 if self.is_singleton(cx) {
14445 let mut to_fold = Vec::new();
14446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14447 let selections = self.selections.all_adjusted(cx);
14448
14449 for selection in selections {
14450 let range = selection.range().sorted();
14451 let buffer_start_row = range.start.row;
14452
14453 if range.start.row != range.end.row {
14454 let mut found = false;
14455 let mut row = range.start.row;
14456 while row <= range.end.row {
14457 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14458 {
14459 found = true;
14460 row = crease.range().end.row + 1;
14461 to_fold.push(crease);
14462 } else {
14463 row += 1
14464 }
14465 }
14466 if found {
14467 continue;
14468 }
14469 }
14470
14471 for row in (0..=range.start.row).rev() {
14472 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14473 if crease.range().end.row >= buffer_start_row {
14474 to_fold.push(crease);
14475 if row <= range.start.row {
14476 break;
14477 }
14478 }
14479 }
14480 }
14481 }
14482
14483 self.fold_creases(to_fold, true, window, cx);
14484 } else {
14485 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14486 let buffer_ids = self
14487 .selections
14488 .disjoint_anchor_ranges()
14489 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14490 .collect::<HashSet<_>>();
14491 for buffer_id in buffer_ids {
14492 self.fold_buffer(buffer_id, cx);
14493 }
14494 }
14495 }
14496
14497 fn fold_at_level(
14498 &mut self,
14499 fold_at: &FoldAtLevel,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 if !self.buffer.read(cx).is_singleton() {
14504 return;
14505 }
14506
14507 let fold_at_level = fold_at.0;
14508 let snapshot = self.buffer.read(cx).snapshot(cx);
14509 let mut to_fold = Vec::new();
14510 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14511
14512 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14513 while start_row < end_row {
14514 match self
14515 .snapshot(window, cx)
14516 .crease_for_buffer_row(MultiBufferRow(start_row))
14517 {
14518 Some(crease) => {
14519 let nested_start_row = crease.range().start.row + 1;
14520 let nested_end_row = crease.range().end.row;
14521
14522 if current_level < fold_at_level {
14523 stack.push((nested_start_row, nested_end_row, current_level + 1));
14524 } else if current_level == fold_at_level {
14525 to_fold.push(crease);
14526 }
14527
14528 start_row = nested_end_row + 1;
14529 }
14530 None => start_row += 1,
14531 }
14532 }
14533 }
14534
14535 self.fold_creases(to_fold, true, window, cx);
14536 }
14537
14538 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14539 if self.buffer.read(cx).is_singleton() {
14540 let mut fold_ranges = Vec::new();
14541 let snapshot = self.buffer.read(cx).snapshot(cx);
14542
14543 for row in 0..snapshot.max_row().0 {
14544 if let Some(foldable_range) = self
14545 .snapshot(window, cx)
14546 .crease_for_buffer_row(MultiBufferRow(row))
14547 {
14548 fold_ranges.push(foldable_range);
14549 }
14550 }
14551
14552 self.fold_creases(fold_ranges, true, window, cx);
14553 } else {
14554 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14555 editor
14556 .update_in(cx, |editor, _, cx| {
14557 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14558 editor.fold_buffer(buffer_id, cx);
14559 }
14560 })
14561 .ok();
14562 });
14563 }
14564 }
14565
14566 pub fn fold_function_bodies(
14567 &mut self,
14568 _: &actions::FoldFunctionBodies,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 let snapshot = self.buffer.read(cx).snapshot(cx);
14573
14574 let ranges = snapshot
14575 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14576 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14577 .collect::<Vec<_>>();
14578
14579 let creases = ranges
14580 .into_iter()
14581 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14582 .collect();
14583
14584 self.fold_creases(creases, true, window, cx);
14585 }
14586
14587 pub fn fold_recursive(
14588 &mut self,
14589 _: &actions::FoldRecursive,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 let mut to_fold = Vec::new();
14594 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14595 let selections = self.selections.all_adjusted(cx);
14596
14597 for selection in selections {
14598 let range = selection.range().sorted();
14599 let buffer_start_row = range.start.row;
14600
14601 if range.start.row != range.end.row {
14602 let mut found = false;
14603 for row in range.start.row..=range.end.row {
14604 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14605 found = true;
14606 to_fold.push(crease);
14607 }
14608 }
14609 if found {
14610 continue;
14611 }
14612 }
14613
14614 for row in (0..=range.start.row).rev() {
14615 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14616 if crease.range().end.row >= buffer_start_row {
14617 to_fold.push(crease);
14618 } else {
14619 break;
14620 }
14621 }
14622 }
14623 }
14624
14625 self.fold_creases(to_fold, true, window, cx);
14626 }
14627
14628 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14629 let buffer_row = fold_at.buffer_row;
14630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14631
14632 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14633 let autoscroll = self
14634 .selections
14635 .all::<Point>(cx)
14636 .iter()
14637 .any(|selection| crease.range().overlaps(&selection.range()));
14638
14639 self.fold_creases(vec![crease], autoscroll, window, cx);
14640 }
14641 }
14642
14643 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14644 if self.is_singleton(cx) {
14645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14646 let buffer = &display_map.buffer_snapshot;
14647 let selections = self.selections.all::<Point>(cx);
14648 let ranges = selections
14649 .iter()
14650 .map(|s| {
14651 let range = s.display_range(&display_map).sorted();
14652 let mut start = range.start.to_point(&display_map);
14653 let mut end = range.end.to_point(&display_map);
14654 start.column = 0;
14655 end.column = buffer.line_len(MultiBufferRow(end.row));
14656 start..end
14657 })
14658 .collect::<Vec<_>>();
14659
14660 self.unfold_ranges(&ranges, true, true, cx);
14661 } else {
14662 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14663 let buffer_ids = self
14664 .selections
14665 .disjoint_anchor_ranges()
14666 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14667 .collect::<HashSet<_>>();
14668 for buffer_id in buffer_ids {
14669 self.unfold_buffer(buffer_id, cx);
14670 }
14671 }
14672 }
14673
14674 pub fn unfold_recursive(
14675 &mut self,
14676 _: &UnfoldRecursive,
14677 _window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) {
14680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14681 let selections = self.selections.all::<Point>(cx);
14682 let ranges = selections
14683 .iter()
14684 .map(|s| {
14685 let mut range = s.display_range(&display_map).sorted();
14686 *range.start.column_mut() = 0;
14687 *range.end.column_mut() = display_map.line_len(range.end.row());
14688 let start = range.start.to_point(&display_map);
14689 let end = range.end.to_point(&display_map);
14690 start..end
14691 })
14692 .collect::<Vec<_>>();
14693
14694 self.unfold_ranges(&ranges, true, true, cx);
14695 }
14696
14697 pub fn unfold_at(
14698 &mut self,
14699 unfold_at: &UnfoldAt,
14700 _window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) {
14703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14704
14705 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14706 ..Point::new(
14707 unfold_at.buffer_row.0,
14708 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14709 );
14710
14711 let autoscroll = self
14712 .selections
14713 .all::<Point>(cx)
14714 .iter()
14715 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14716
14717 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14718 }
14719
14720 pub fn unfold_all(
14721 &mut self,
14722 _: &actions::UnfoldAll,
14723 _window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) {
14726 if self.buffer.read(cx).is_singleton() {
14727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14728 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14729 } else {
14730 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14731 editor
14732 .update(cx, |editor, cx| {
14733 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14734 editor.unfold_buffer(buffer_id, cx);
14735 }
14736 })
14737 .ok();
14738 });
14739 }
14740 }
14741
14742 pub fn fold_selected_ranges(
14743 &mut self,
14744 _: &FoldSelectedRanges,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 let selections = self.selections.all::<Point>(cx);
14749 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14750 let line_mode = self.selections.line_mode;
14751 let ranges = selections
14752 .into_iter()
14753 .map(|s| {
14754 if line_mode {
14755 let start = Point::new(s.start.row, 0);
14756 let end = Point::new(
14757 s.end.row,
14758 display_map
14759 .buffer_snapshot
14760 .line_len(MultiBufferRow(s.end.row)),
14761 );
14762 Crease::simple(start..end, display_map.fold_placeholder.clone())
14763 } else {
14764 Crease::simple(s.start..s.end, display_map.fold_placeholder.clone())
14765 }
14766 })
14767 .collect::<Vec<_>>();
14768 self.fold_creases(ranges, true, window, cx);
14769 }
14770
14771 pub fn fold_ranges<T: ToOffset + Clone>(
14772 &mut self,
14773 ranges: Vec<Range<T>>,
14774 auto_scroll: bool,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) {
14778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14779 let ranges = ranges
14780 .into_iter()
14781 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14782 .collect::<Vec<_>>();
14783 self.fold_creases(ranges, auto_scroll, window, cx);
14784 }
14785
14786 pub fn fold_creases<T: ToOffset + Clone>(
14787 &mut self,
14788 creases: Vec<Crease<T>>,
14789 auto_scroll: bool,
14790 window: &mut Window,
14791 cx: &mut Context<Self>,
14792 ) {
14793 if creases.is_empty() {
14794 return;
14795 }
14796
14797 let mut buffers_affected = HashSet::default();
14798 let multi_buffer = self.buffer().read(cx);
14799 for crease in &creases {
14800 if let Some((_, buffer, _)) =
14801 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14802 {
14803 buffers_affected.insert(buffer.read(cx).remote_id());
14804 };
14805 }
14806
14807 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14808
14809 if auto_scroll {
14810 self.request_autoscroll(Autoscroll::fit(), cx);
14811 }
14812
14813 cx.notify();
14814
14815 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14816 // Clear diagnostics block when folding a range that contains it.
14817 let snapshot = self.snapshot(window, cx);
14818 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14819 drop(snapshot);
14820 self.active_diagnostics = Some(active_diagnostics);
14821 self.dismiss_diagnostics(cx);
14822 } else {
14823 self.active_diagnostics = Some(active_diagnostics);
14824 }
14825 }
14826
14827 self.scrollbar_marker_state.dirty = true;
14828 self.folds_did_change(cx);
14829 }
14830
14831 /// Removes any folds whose ranges intersect any of the given ranges.
14832 pub fn unfold_ranges<T: ToOffset + Clone>(
14833 &mut self,
14834 ranges: &[Range<T>],
14835 inclusive: bool,
14836 auto_scroll: bool,
14837 cx: &mut Context<Self>,
14838 ) {
14839 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14840 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14841 });
14842 self.folds_did_change(cx);
14843 }
14844
14845 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14846 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14847 return;
14848 }
14849 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14850 self.display_map.update(cx, |display_map, cx| {
14851 display_map.fold_buffers([buffer_id], cx)
14852 });
14853 cx.emit(EditorEvent::BufferFoldToggled {
14854 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14855 folded: true,
14856 });
14857 cx.notify();
14858 }
14859
14860 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14861 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14862 return;
14863 }
14864 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14865 self.display_map.update(cx, |display_map, cx| {
14866 display_map.unfold_buffers([buffer_id], cx);
14867 });
14868 cx.emit(EditorEvent::BufferFoldToggled {
14869 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14870 folded: false,
14871 });
14872 cx.notify();
14873 }
14874
14875 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14876 self.display_map.read(cx).is_buffer_folded(buffer)
14877 }
14878
14879 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14880 self.display_map.read(cx).folded_buffers()
14881 }
14882
14883 /// Removes any folds with the given ranges.
14884 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14885 &mut self,
14886 ranges: &[Range<T>],
14887 type_id: TypeId,
14888 auto_scroll: bool,
14889 cx: &mut Context<Self>,
14890 ) {
14891 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14892 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14893 });
14894 self.folds_did_change(cx);
14895 }
14896
14897 fn remove_folds_with<T: ToOffset + Clone>(
14898 &mut self,
14899 ranges: &[Range<T>],
14900 auto_scroll: bool,
14901 cx: &mut Context<Self>,
14902 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14903 ) {
14904 if ranges.is_empty() {
14905 return;
14906 }
14907
14908 let mut buffers_affected = HashSet::default();
14909 let multi_buffer = self.buffer().read(cx);
14910 for range in ranges {
14911 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14912 buffers_affected.insert(buffer.read(cx).remote_id());
14913 };
14914 }
14915
14916 self.display_map.update(cx, update);
14917
14918 if auto_scroll {
14919 self.request_autoscroll(Autoscroll::fit(), cx);
14920 }
14921
14922 cx.notify();
14923 self.scrollbar_marker_state.dirty = true;
14924 self.active_indent_guides_state.dirty = true;
14925 }
14926
14927 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14928 self.display_map.read(cx).fold_placeholder.clone()
14929 }
14930
14931 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14932 self.buffer.update(cx, |buffer, cx| {
14933 buffer.set_all_diff_hunks_expanded(cx);
14934 });
14935 }
14936
14937 pub fn expand_all_diff_hunks(
14938 &mut self,
14939 _: &ExpandAllDiffHunks,
14940 _window: &mut Window,
14941 cx: &mut Context<Self>,
14942 ) {
14943 self.buffer.update(cx, |buffer, cx| {
14944 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14945 });
14946 }
14947
14948 pub fn toggle_selected_diff_hunks(
14949 &mut self,
14950 _: &ToggleSelectedDiffHunks,
14951 _window: &mut Window,
14952 cx: &mut Context<Self>,
14953 ) {
14954 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14955 self.toggle_diff_hunks_in_ranges(ranges, cx);
14956 }
14957
14958 pub fn diff_hunks_in_ranges<'a>(
14959 &'a self,
14960 ranges: &'a [Range<Anchor>],
14961 buffer: &'a MultiBufferSnapshot,
14962 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
14963 ranges.iter().flat_map(move |range| {
14964 let end_excerpt_id = range.end.excerpt_id;
14965 let range = range.to_point(buffer);
14966 let mut peek_end = range.end;
14967 if range.end.row < buffer.max_row().0 {
14968 peek_end = Point::new(range.end.row + 1, 0);
14969 }
14970 buffer
14971 .diff_hunks_in_range(range.start..peek_end)
14972 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
14973 })
14974 }
14975
14976 pub fn has_stageable_diff_hunks_in_ranges(
14977 &self,
14978 ranges: &[Range<Anchor>],
14979 snapshot: &MultiBufferSnapshot,
14980 ) -> bool {
14981 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
14982 hunks.any(|hunk| hunk.status().has_secondary_hunk())
14983 }
14984
14985 pub fn toggle_staged_selected_diff_hunks(
14986 &mut self,
14987 _: &::git::ToggleStaged,
14988 _: &mut Window,
14989 cx: &mut Context<Self>,
14990 ) {
14991 let snapshot = self.buffer.read(cx).snapshot(cx);
14992 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14993 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
14994 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
14995 }
14996
14997 pub fn set_render_diff_hunk_controls(
14998 &mut self,
14999 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15000 cx: &mut Context<Self>,
15001 ) {
15002 self.render_diff_hunk_controls = render_diff_hunk_controls;
15003 cx.notify();
15004 }
15005
15006 pub fn stage_and_next(
15007 &mut self,
15008 _: &::git::StageAndNext,
15009 window: &mut Window,
15010 cx: &mut Context<Self>,
15011 ) {
15012 self.do_stage_or_unstage_and_next(true, window, cx);
15013 }
15014
15015 pub fn unstage_and_next(
15016 &mut self,
15017 _: &::git::UnstageAndNext,
15018 window: &mut Window,
15019 cx: &mut Context<Self>,
15020 ) {
15021 self.do_stage_or_unstage_and_next(false, window, cx);
15022 }
15023
15024 pub fn stage_or_unstage_diff_hunks(
15025 &mut self,
15026 stage: bool,
15027 ranges: Vec<Range<Anchor>>,
15028 cx: &mut Context<Self>,
15029 ) {
15030 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15031 cx.spawn(async move |this, cx| {
15032 task.await?;
15033 this.update(cx, |this, cx| {
15034 let snapshot = this.buffer.read(cx).snapshot(cx);
15035 let chunk_by = this
15036 .diff_hunks_in_ranges(&ranges, &snapshot)
15037 .chunk_by(|hunk| hunk.buffer_id);
15038 for (buffer_id, hunks) in &chunk_by {
15039 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15040 }
15041 })
15042 })
15043 .detach_and_log_err(cx);
15044 }
15045
15046 fn save_buffers_for_ranges_if_needed(
15047 &mut self,
15048 ranges: &[Range<Anchor>],
15049 cx: &mut Context<Editor>,
15050 ) -> Task<Result<()>> {
15051 let multibuffer = self.buffer.read(cx);
15052 let snapshot = multibuffer.read(cx);
15053 let buffer_ids: HashSet<_> = ranges
15054 .iter()
15055 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15056 .collect();
15057 drop(snapshot);
15058
15059 let mut buffers = HashSet::default();
15060 for buffer_id in buffer_ids {
15061 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15062 let buffer = buffer_entity.read(cx);
15063 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15064 {
15065 buffers.insert(buffer_entity);
15066 }
15067 }
15068 }
15069
15070 if let Some(project) = &self.project {
15071 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15072 } else {
15073 Task::ready(Ok(()))
15074 }
15075 }
15076
15077 fn do_stage_or_unstage_and_next(
15078 &mut self,
15079 stage: bool,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15084
15085 if ranges.iter().any(|range| range.start != range.end) {
15086 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15087 return;
15088 }
15089
15090 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15091 let snapshot = self.snapshot(window, cx);
15092 let position = self.selections.newest::<Point>(cx).head();
15093 let mut row = snapshot
15094 .buffer_snapshot
15095 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15096 .find(|hunk| hunk.row_range.start.0 > position.row)
15097 .map(|hunk| hunk.row_range.start);
15098
15099 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15100 // Outside of the project diff editor, wrap around to the beginning.
15101 if !all_diff_hunks_expanded {
15102 row = row.or_else(|| {
15103 snapshot
15104 .buffer_snapshot
15105 .diff_hunks_in_range(Point::zero()..position)
15106 .find(|hunk| hunk.row_range.end.0 < position.row)
15107 .map(|hunk| hunk.row_range.start)
15108 });
15109 }
15110
15111 if let Some(row) = row {
15112 let destination = Point::new(row.0, 0);
15113 let autoscroll = Autoscroll::center();
15114
15115 self.unfold_ranges(&[destination..destination], false, false, cx);
15116 self.change_selections(Some(autoscroll), window, cx, |s| {
15117 s.select_ranges([destination..destination]);
15118 });
15119 }
15120 }
15121
15122 fn do_stage_or_unstage(
15123 &self,
15124 stage: bool,
15125 buffer_id: BufferId,
15126 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15127 cx: &mut App,
15128 ) -> Option<()> {
15129 let project = self.project.as_ref()?;
15130 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15131 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15132 let buffer_snapshot = buffer.read(cx).snapshot();
15133 let file_exists = buffer_snapshot
15134 .file()
15135 .is_some_and(|file| file.disk_state().exists());
15136 diff.update(cx, |diff, cx| {
15137 diff.stage_or_unstage_hunks(
15138 stage,
15139 &hunks
15140 .map(|hunk| buffer_diff::DiffHunk {
15141 buffer_range: hunk.buffer_range,
15142 diff_base_byte_range: hunk.diff_base_byte_range,
15143 secondary_status: hunk.secondary_status,
15144 range: Point::zero()..Point::zero(), // unused
15145 })
15146 .collect::<Vec<_>>(),
15147 &buffer_snapshot,
15148 file_exists,
15149 cx,
15150 )
15151 });
15152 None
15153 }
15154
15155 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15156 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15157 self.buffer
15158 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15159 }
15160
15161 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15162 self.buffer.update(cx, |buffer, cx| {
15163 let ranges = vec![Anchor::min()..Anchor::max()];
15164 if !buffer.all_diff_hunks_expanded()
15165 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15166 {
15167 buffer.collapse_diff_hunks(ranges, cx);
15168 true
15169 } else {
15170 false
15171 }
15172 })
15173 }
15174
15175 fn toggle_diff_hunks_in_ranges(
15176 &mut self,
15177 ranges: Vec<Range<Anchor>>,
15178 cx: &mut Context<Editor>,
15179 ) {
15180 self.buffer.update(cx, |buffer, cx| {
15181 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15182 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15183 })
15184 }
15185
15186 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15187 self.buffer.update(cx, |buffer, cx| {
15188 let snapshot = buffer.snapshot(cx);
15189 let excerpt_id = range.end.excerpt_id;
15190 let point_range = range.to_point(&snapshot);
15191 let expand = !buffer.single_hunk_is_expanded(range, cx);
15192 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15193 })
15194 }
15195
15196 pub(crate) fn apply_all_diff_hunks(
15197 &mut self,
15198 _: &ApplyAllDiffHunks,
15199 window: &mut Window,
15200 cx: &mut Context<Self>,
15201 ) {
15202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15203
15204 let buffers = self.buffer.read(cx).all_buffers();
15205 for branch_buffer in buffers {
15206 branch_buffer.update(cx, |branch_buffer, cx| {
15207 branch_buffer.merge_into_base(Vec::new(), cx);
15208 });
15209 }
15210
15211 if let Some(project) = self.project.clone() {
15212 self.save(true, project, window, cx).detach_and_log_err(cx);
15213 }
15214 }
15215
15216 pub(crate) fn apply_selected_diff_hunks(
15217 &mut self,
15218 _: &ApplyDiffHunk,
15219 window: &mut Window,
15220 cx: &mut Context<Self>,
15221 ) {
15222 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15223 let snapshot = self.snapshot(window, cx);
15224 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15225 let mut ranges_by_buffer = HashMap::default();
15226 self.transact(window, cx, |editor, _window, cx| {
15227 for hunk in hunks {
15228 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15229 ranges_by_buffer
15230 .entry(buffer.clone())
15231 .or_insert_with(Vec::new)
15232 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15233 }
15234 }
15235
15236 for (buffer, ranges) in ranges_by_buffer {
15237 buffer.update(cx, |buffer, cx| {
15238 buffer.merge_into_base(ranges, cx);
15239 });
15240 }
15241 });
15242
15243 if let Some(project) = self.project.clone() {
15244 self.save(true, project, window, cx).detach_and_log_err(cx);
15245 }
15246 }
15247
15248 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15249 if hovered != self.gutter_hovered {
15250 self.gutter_hovered = hovered;
15251 cx.notify();
15252 }
15253 }
15254
15255 pub fn insert_blocks(
15256 &mut self,
15257 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15258 autoscroll: Option<Autoscroll>,
15259 cx: &mut Context<Self>,
15260 ) -> Vec<CustomBlockId> {
15261 let blocks = self
15262 .display_map
15263 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15264 if let Some(autoscroll) = autoscroll {
15265 self.request_autoscroll(autoscroll, cx);
15266 }
15267 cx.notify();
15268 blocks
15269 }
15270
15271 pub fn resize_blocks(
15272 &mut self,
15273 heights: HashMap<CustomBlockId, u32>,
15274 autoscroll: Option<Autoscroll>,
15275 cx: &mut Context<Self>,
15276 ) {
15277 self.display_map
15278 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15279 if let Some(autoscroll) = autoscroll {
15280 self.request_autoscroll(autoscroll, cx);
15281 }
15282 cx.notify();
15283 }
15284
15285 pub fn replace_blocks(
15286 &mut self,
15287 renderers: HashMap<CustomBlockId, RenderBlock>,
15288 autoscroll: Option<Autoscroll>,
15289 cx: &mut Context<Self>,
15290 ) {
15291 self.display_map
15292 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15293 if let Some(autoscroll) = autoscroll {
15294 self.request_autoscroll(autoscroll, cx);
15295 }
15296 cx.notify();
15297 }
15298
15299 pub fn remove_blocks(
15300 &mut self,
15301 block_ids: HashSet<CustomBlockId>,
15302 autoscroll: Option<Autoscroll>,
15303 cx: &mut Context<Self>,
15304 ) {
15305 self.display_map.update(cx, |display_map, cx| {
15306 display_map.remove_blocks(block_ids, cx)
15307 });
15308 if let Some(autoscroll) = autoscroll {
15309 self.request_autoscroll(autoscroll, cx);
15310 }
15311 cx.notify();
15312 }
15313
15314 pub fn row_for_block(
15315 &self,
15316 block_id: CustomBlockId,
15317 cx: &mut Context<Self>,
15318 ) -> Option<DisplayRow> {
15319 self.display_map
15320 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15321 }
15322
15323 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15324 self.focused_block = Some(focused_block);
15325 }
15326
15327 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15328 self.focused_block.take()
15329 }
15330
15331 pub fn insert_creases(
15332 &mut self,
15333 creases: impl IntoIterator<Item = Crease<Anchor>>,
15334 cx: &mut Context<Self>,
15335 ) -> Vec<CreaseId> {
15336 self.display_map
15337 .update(cx, |map, cx| map.insert_creases(creases, cx))
15338 }
15339
15340 pub fn remove_creases(
15341 &mut self,
15342 ids: impl IntoIterator<Item = CreaseId>,
15343 cx: &mut Context<Self>,
15344 ) {
15345 self.display_map
15346 .update(cx, |map, cx| map.remove_creases(ids, cx));
15347 }
15348
15349 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15350 self.display_map
15351 .update(cx, |map, cx| map.snapshot(cx))
15352 .longest_row()
15353 }
15354
15355 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15356 self.display_map
15357 .update(cx, |map, cx| map.snapshot(cx))
15358 .max_point()
15359 }
15360
15361 pub fn text(&self, cx: &App) -> String {
15362 self.buffer.read(cx).read(cx).text()
15363 }
15364
15365 pub fn is_empty(&self, cx: &App) -> bool {
15366 self.buffer.read(cx).read(cx).is_empty()
15367 }
15368
15369 pub fn text_option(&self, cx: &App) -> Option<String> {
15370 let text = self.text(cx);
15371 let text = text.trim();
15372
15373 if text.is_empty() {
15374 return None;
15375 }
15376
15377 Some(text.to_string())
15378 }
15379
15380 pub fn set_text(
15381 &mut self,
15382 text: impl Into<Arc<str>>,
15383 window: &mut Window,
15384 cx: &mut Context<Self>,
15385 ) {
15386 self.transact(window, cx, |this, _, cx| {
15387 this.buffer
15388 .read(cx)
15389 .as_singleton()
15390 .expect("you can only call set_text on editors for singleton buffers")
15391 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15392 });
15393 }
15394
15395 pub fn display_text(&self, cx: &mut App) -> String {
15396 self.display_map
15397 .update(cx, |map, cx| map.snapshot(cx))
15398 .text()
15399 }
15400
15401 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15402 let mut wrap_guides = smallvec::smallvec![];
15403
15404 if self.show_wrap_guides == Some(false) {
15405 return wrap_guides;
15406 }
15407
15408 let settings = self.buffer.read(cx).language_settings(cx);
15409 if settings.show_wrap_guides {
15410 match self.soft_wrap_mode(cx) {
15411 SoftWrap::Column(soft_wrap) => {
15412 wrap_guides.push((soft_wrap as usize, true));
15413 }
15414 SoftWrap::Bounded(soft_wrap) => {
15415 wrap_guides.push((soft_wrap as usize, true));
15416 }
15417 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15418 }
15419 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15420 }
15421
15422 wrap_guides
15423 }
15424
15425 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15426 let settings = self.buffer.read(cx).language_settings(cx);
15427 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15428 match mode {
15429 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15430 SoftWrap::None
15431 }
15432 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15433 language_settings::SoftWrap::PreferredLineLength => {
15434 SoftWrap::Column(settings.preferred_line_length)
15435 }
15436 language_settings::SoftWrap::Bounded => {
15437 SoftWrap::Bounded(settings.preferred_line_length)
15438 }
15439 }
15440 }
15441
15442 pub fn set_soft_wrap_mode(
15443 &mut self,
15444 mode: language_settings::SoftWrap,
15445
15446 cx: &mut Context<Self>,
15447 ) {
15448 self.soft_wrap_mode_override = Some(mode);
15449 cx.notify();
15450 }
15451
15452 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15453 self.hard_wrap = hard_wrap;
15454 cx.notify();
15455 }
15456
15457 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15458 self.text_style_refinement = Some(style);
15459 }
15460
15461 /// called by the Element so we know what style we were most recently rendered with.
15462 pub(crate) fn set_style(
15463 &mut self,
15464 style: EditorStyle,
15465 window: &mut Window,
15466 cx: &mut Context<Self>,
15467 ) {
15468 let rem_size = window.rem_size();
15469 self.display_map.update(cx, |map, cx| {
15470 map.set_font(
15471 style.text.font(),
15472 style.text.font_size.to_pixels(rem_size),
15473 cx,
15474 )
15475 });
15476 self.style = Some(style);
15477 }
15478
15479 pub fn style(&self) -> Option<&EditorStyle> {
15480 self.style.as_ref()
15481 }
15482
15483 // Called by the element. This method is not designed to be called outside of the editor
15484 // element's layout code because it does not notify when rewrapping is computed synchronously.
15485 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15486 self.display_map
15487 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15488 }
15489
15490 pub fn set_soft_wrap(&mut self) {
15491 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15492 }
15493
15494 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15495 if self.soft_wrap_mode_override.is_some() {
15496 self.soft_wrap_mode_override.take();
15497 } else {
15498 let soft_wrap = match self.soft_wrap_mode(cx) {
15499 SoftWrap::GitDiff => return,
15500 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15501 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15502 language_settings::SoftWrap::None
15503 }
15504 };
15505 self.soft_wrap_mode_override = Some(soft_wrap);
15506 }
15507 cx.notify();
15508 }
15509
15510 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15511 let Some(workspace) = self.workspace() else {
15512 return;
15513 };
15514 let fs = workspace.read(cx).app_state().fs.clone();
15515 let current_show = TabBarSettings::get_global(cx).show;
15516 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15517 setting.show = Some(!current_show);
15518 });
15519 }
15520
15521 pub fn toggle_indent_guides(
15522 &mut self,
15523 _: &ToggleIndentGuides,
15524 _: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15528 self.buffer
15529 .read(cx)
15530 .language_settings(cx)
15531 .indent_guides
15532 .enabled
15533 });
15534 self.show_indent_guides = Some(!currently_enabled);
15535 cx.notify();
15536 }
15537
15538 fn should_show_indent_guides(&self) -> Option<bool> {
15539 self.show_indent_guides
15540 }
15541
15542 pub fn toggle_line_numbers(
15543 &mut self,
15544 _: &ToggleLineNumbers,
15545 _: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 let mut editor_settings = EditorSettings::get_global(cx).clone();
15549 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15550 EditorSettings::override_global(editor_settings, cx);
15551 }
15552
15553 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15554 if let Some(show_line_numbers) = self.show_line_numbers {
15555 return show_line_numbers;
15556 }
15557 EditorSettings::get_global(cx).gutter.line_numbers
15558 }
15559
15560 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15561 self.use_relative_line_numbers
15562 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15563 }
15564
15565 pub fn toggle_relative_line_numbers(
15566 &mut self,
15567 _: &ToggleRelativeLineNumbers,
15568 _: &mut Window,
15569 cx: &mut Context<Self>,
15570 ) {
15571 let is_relative = self.should_use_relative_line_numbers(cx);
15572 self.set_relative_line_number(Some(!is_relative), cx)
15573 }
15574
15575 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15576 self.use_relative_line_numbers = is_relative;
15577 cx.notify();
15578 }
15579
15580 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15581 self.show_gutter = show_gutter;
15582 cx.notify();
15583 }
15584
15585 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15586 self.show_scrollbars = show_scrollbars;
15587 cx.notify();
15588 }
15589
15590 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15591 self.show_line_numbers = Some(show_line_numbers);
15592 cx.notify();
15593 }
15594
15595 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15596 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15597 cx.notify();
15598 }
15599
15600 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15601 self.show_code_actions = Some(show_code_actions);
15602 cx.notify();
15603 }
15604
15605 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15606 self.show_runnables = Some(show_runnables);
15607 cx.notify();
15608 }
15609
15610 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15611 self.show_breakpoints = Some(show_breakpoints);
15612 cx.notify();
15613 }
15614
15615 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15616 if self.display_map.read(cx).masked != masked {
15617 self.display_map.update(cx, |map, _| map.masked = masked);
15618 }
15619 cx.notify()
15620 }
15621
15622 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15623 self.show_wrap_guides = Some(show_wrap_guides);
15624 cx.notify();
15625 }
15626
15627 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15628 self.show_indent_guides = Some(show_indent_guides);
15629 cx.notify();
15630 }
15631
15632 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15633 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15634 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15635 if let Some(dir) = file.abs_path(cx).parent() {
15636 return Some(dir.to_owned());
15637 }
15638 }
15639
15640 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15641 return Some(project_path.path.to_path_buf());
15642 }
15643 }
15644
15645 None
15646 }
15647
15648 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15649 self.active_excerpt(cx)?
15650 .1
15651 .read(cx)
15652 .file()
15653 .and_then(|f| f.as_local())
15654 }
15655
15656 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15657 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15658 let buffer = buffer.read(cx);
15659 if let Some(project_path) = buffer.project_path(cx) {
15660 let project = self.project.as_ref()?.read(cx);
15661 project.absolute_path(&project_path, cx)
15662 } else {
15663 buffer
15664 .file()
15665 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15666 }
15667 })
15668 }
15669
15670 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15671 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15672 let project_path = buffer.read(cx).project_path(cx)?;
15673 let project = self.project.as_ref()?.read(cx);
15674 let entry = project.entry_for_path(&project_path, cx)?;
15675 let path = entry.path.to_path_buf();
15676 Some(path)
15677 })
15678 }
15679
15680 pub fn reveal_in_finder(
15681 &mut self,
15682 _: &RevealInFileManager,
15683 _window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) {
15686 if let Some(target) = self.target_file(cx) {
15687 cx.reveal_path(&target.abs_path(cx));
15688 }
15689 }
15690
15691 pub fn copy_path(
15692 &mut self,
15693 _: &zed_actions::workspace::CopyPath,
15694 _window: &mut Window,
15695 cx: &mut Context<Self>,
15696 ) {
15697 if let Some(path) = self.target_file_abs_path(cx) {
15698 if let Some(path) = path.to_str() {
15699 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15700 }
15701 }
15702 }
15703
15704 pub fn copy_relative_path(
15705 &mut self,
15706 _: &zed_actions::workspace::CopyRelativePath,
15707 _window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) {
15710 if let Some(path) = self.target_file_path(cx) {
15711 if let Some(path) = path.to_str() {
15712 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15713 }
15714 }
15715 }
15716
15717 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15718 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15719 buffer.read(cx).project_path(cx)
15720 } else {
15721 None
15722 }
15723 }
15724
15725 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15726 let _ = maybe!({
15727 let breakpoint_store = self.breakpoint_store.as_ref()?;
15728
15729 let Some((_, _, active_position)) =
15730 breakpoint_store.read(cx).active_position().cloned()
15731 else {
15732 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15733 return None;
15734 };
15735
15736 let snapshot = self
15737 .project
15738 .as_ref()?
15739 .read(cx)
15740 .buffer_for_id(active_position.buffer_id?, cx)?
15741 .read(cx)
15742 .snapshot();
15743
15744 for (id, ExcerptRange { context, .. }) in self
15745 .buffer
15746 .read(cx)
15747 .excerpts_for_buffer(active_position.buffer_id?, cx)
15748 {
15749 if context.start.cmp(&active_position, &snapshot).is_ge()
15750 || context.end.cmp(&active_position, &snapshot).is_lt()
15751 {
15752 continue;
15753 }
15754 let snapshot = self.buffer.read(cx).snapshot(cx);
15755 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15756
15757 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15758 self.go_to_line::<DebugCurrentRowHighlight>(
15759 multibuffer_anchor,
15760 Some(cx.theme().colors().editor_debugger_active_line_background),
15761 window,
15762 cx,
15763 );
15764
15765 cx.notify();
15766 }
15767
15768 Some(())
15769 });
15770 }
15771
15772 pub fn copy_file_name_without_extension(
15773 &mut self,
15774 _: &CopyFileNameWithoutExtension,
15775 _: &mut Window,
15776 cx: &mut Context<Self>,
15777 ) {
15778 if let Some(file) = self.target_file(cx) {
15779 if let Some(file_stem) = file.path().file_stem() {
15780 if let Some(name) = file_stem.to_str() {
15781 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15782 }
15783 }
15784 }
15785 }
15786
15787 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15788 if let Some(file) = self.target_file(cx) {
15789 if let Some(file_name) = file.path().file_name() {
15790 if let Some(name) = file_name.to_str() {
15791 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15792 }
15793 }
15794 }
15795 }
15796
15797 pub fn toggle_git_blame(
15798 &mut self,
15799 _: &::git::Blame,
15800 window: &mut Window,
15801 cx: &mut Context<Self>,
15802 ) {
15803 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15804
15805 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15806 self.start_git_blame(true, window, cx);
15807 }
15808
15809 cx.notify();
15810 }
15811
15812 pub fn toggle_git_blame_inline(
15813 &mut self,
15814 _: &ToggleGitBlameInline,
15815 window: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 self.toggle_git_blame_inline_internal(true, window, cx);
15819 cx.notify();
15820 }
15821
15822 pub fn git_blame_inline_enabled(&self) -> bool {
15823 self.git_blame_inline_enabled
15824 }
15825
15826 pub fn toggle_selection_menu(
15827 &mut self,
15828 _: &ToggleSelectionMenu,
15829 _: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.show_selection_menu = self
15833 .show_selection_menu
15834 .map(|show_selections_menu| !show_selections_menu)
15835 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15836
15837 cx.notify();
15838 }
15839
15840 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15841 self.show_selection_menu
15842 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15843 }
15844
15845 fn start_git_blame(
15846 &mut self,
15847 user_triggered: bool,
15848 window: &mut Window,
15849 cx: &mut Context<Self>,
15850 ) {
15851 if let Some(project) = self.project.as_ref() {
15852 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15853 return;
15854 };
15855
15856 if buffer.read(cx).file().is_none() {
15857 return;
15858 }
15859
15860 let focused = self.focus_handle(cx).contains_focused(window, cx);
15861
15862 let project = project.clone();
15863 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15864 self.blame_subscription =
15865 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15866 self.blame = Some(blame);
15867 }
15868 }
15869
15870 fn toggle_git_blame_inline_internal(
15871 &mut self,
15872 user_triggered: bool,
15873 window: &mut Window,
15874 cx: &mut Context<Self>,
15875 ) {
15876 if self.git_blame_inline_enabled {
15877 self.git_blame_inline_enabled = false;
15878 self.show_git_blame_inline = false;
15879 self.show_git_blame_inline_delay_task.take();
15880 } else {
15881 self.git_blame_inline_enabled = true;
15882 self.start_git_blame_inline(user_triggered, window, cx);
15883 }
15884
15885 cx.notify();
15886 }
15887
15888 fn start_git_blame_inline(
15889 &mut self,
15890 user_triggered: bool,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) {
15894 self.start_git_blame(user_triggered, window, cx);
15895
15896 if ProjectSettings::get_global(cx)
15897 .git
15898 .inline_blame_delay()
15899 .is_some()
15900 {
15901 self.start_inline_blame_timer(window, cx);
15902 } else {
15903 self.show_git_blame_inline = true
15904 }
15905 }
15906
15907 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15908 self.blame.as_ref()
15909 }
15910
15911 pub fn show_git_blame_gutter(&self) -> bool {
15912 self.show_git_blame_gutter
15913 }
15914
15915 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15916 self.show_git_blame_gutter && self.has_blame_entries(cx)
15917 }
15918
15919 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15920 self.show_git_blame_inline
15921 && (self.focus_handle.is_focused(window)
15922 || self
15923 .git_blame_inline_tooltip
15924 .as_ref()
15925 .and_then(|t| t.upgrade())
15926 .is_some())
15927 && !self.newest_selection_head_on_empty_line(cx)
15928 && self.has_blame_entries(cx)
15929 }
15930
15931 fn has_blame_entries(&self, cx: &App) -> bool {
15932 self.blame()
15933 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15934 }
15935
15936 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15937 let cursor_anchor = self.selections.newest_anchor().head();
15938
15939 let snapshot = self.buffer.read(cx).snapshot(cx);
15940 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15941
15942 snapshot.line_len(buffer_row) == 0
15943 }
15944
15945 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15946 let buffer_and_selection = maybe!({
15947 let selection = self.selections.newest::<Point>(cx);
15948 let selection_range = selection.range();
15949
15950 let multi_buffer = self.buffer().read(cx);
15951 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15952 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15953
15954 let (buffer, range, _) = if selection.reversed {
15955 buffer_ranges.first()
15956 } else {
15957 buffer_ranges.last()
15958 }?;
15959
15960 let selection = text::ToPoint::to_point(&range.start, &buffer).row
15961 ..text::ToPoint::to_point(&range.end, &buffer).row;
15962 Some((
15963 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
15964 selection,
15965 ))
15966 });
15967
15968 let Some((buffer, selection)) = buffer_and_selection else {
15969 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
15970 };
15971
15972 let Some(project) = self.project.as_ref() else {
15973 return Task::ready(Err(anyhow!("editor does not have project")));
15974 };
15975
15976 project.update(cx, |project, cx| {
15977 project.get_permalink_to_line(&buffer, selection, cx)
15978 })
15979 }
15980
15981 pub fn copy_permalink_to_line(
15982 &mut self,
15983 _: &CopyPermalinkToLine,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) {
15987 let permalink_task = self.get_permalink_to_line(cx);
15988 let workspace = self.workspace();
15989
15990 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
15991 Ok(permalink) => {
15992 cx.update(|_, cx| {
15993 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
15994 })
15995 .ok();
15996 }
15997 Err(err) => {
15998 let message = format!("Failed to copy permalink: {err}");
15999
16000 Err::<(), anyhow::Error>(err).log_err();
16001
16002 if let Some(workspace) = workspace {
16003 workspace
16004 .update_in(cx, |workspace, _, cx| {
16005 struct CopyPermalinkToLine;
16006
16007 workspace.show_toast(
16008 Toast::new(
16009 NotificationId::unique::<CopyPermalinkToLine>(),
16010 message,
16011 ),
16012 cx,
16013 )
16014 })
16015 .ok();
16016 }
16017 }
16018 })
16019 .detach();
16020 }
16021
16022 pub fn copy_file_location(
16023 &mut self,
16024 _: &CopyFileLocation,
16025 _: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) {
16028 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16029 if let Some(file) = self.target_file(cx) {
16030 if let Some(path) = file.path().to_str() {
16031 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16032 }
16033 }
16034 }
16035
16036 pub fn open_permalink_to_line(
16037 &mut self,
16038 _: &OpenPermalinkToLine,
16039 window: &mut Window,
16040 cx: &mut Context<Self>,
16041 ) {
16042 let permalink_task = self.get_permalink_to_line(cx);
16043 let workspace = self.workspace();
16044
16045 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16046 Ok(permalink) => {
16047 cx.update(|_, cx| {
16048 cx.open_url(permalink.as_ref());
16049 })
16050 .ok();
16051 }
16052 Err(err) => {
16053 let message = format!("Failed to open permalink: {err}");
16054
16055 Err::<(), anyhow::Error>(err).log_err();
16056
16057 if let Some(workspace) = workspace {
16058 workspace
16059 .update(cx, |workspace, cx| {
16060 struct OpenPermalinkToLine;
16061
16062 workspace.show_toast(
16063 Toast::new(
16064 NotificationId::unique::<OpenPermalinkToLine>(),
16065 message,
16066 ),
16067 cx,
16068 )
16069 })
16070 .ok();
16071 }
16072 }
16073 })
16074 .detach();
16075 }
16076
16077 pub fn insert_uuid_v4(
16078 &mut self,
16079 _: &InsertUuidV4,
16080 window: &mut Window,
16081 cx: &mut Context<Self>,
16082 ) {
16083 self.insert_uuid(UuidVersion::V4, window, cx);
16084 }
16085
16086 pub fn insert_uuid_v7(
16087 &mut self,
16088 _: &InsertUuidV7,
16089 window: &mut Window,
16090 cx: &mut Context<Self>,
16091 ) {
16092 self.insert_uuid(UuidVersion::V7, window, cx);
16093 }
16094
16095 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16096 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16097 self.transact(window, cx, |this, window, cx| {
16098 let edits = this
16099 .selections
16100 .all::<Point>(cx)
16101 .into_iter()
16102 .map(|selection| {
16103 let uuid = match version {
16104 UuidVersion::V4 => uuid::Uuid::new_v4(),
16105 UuidVersion::V7 => uuid::Uuid::now_v7(),
16106 };
16107
16108 (selection.range(), uuid.to_string())
16109 });
16110 this.edit(edits, cx);
16111 this.refresh_inline_completion(true, false, window, cx);
16112 });
16113 }
16114
16115 pub fn open_selections_in_multibuffer(
16116 &mut self,
16117 _: &OpenSelectionsInMultibuffer,
16118 window: &mut Window,
16119 cx: &mut Context<Self>,
16120 ) {
16121 let multibuffer = self.buffer.read(cx);
16122
16123 let Some(buffer) = multibuffer.as_singleton() else {
16124 return;
16125 };
16126
16127 let Some(workspace) = self.workspace() else {
16128 return;
16129 };
16130
16131 let locations = self
16132 .selections
16133 .disjoint_anchors()
16134 .iter()
16135 .map(|range| Location {
16136 buffer: buffer.clone(),
16137 range: range.start.text_anchor..range.end.text_anchor,
16138 })
16139 .collect::<Vec<_>>();
16140
16141 let title = multibuffer.title(cx).to_string();
16142
16143 cx.spawn_in(window, async move |_, cx| {
16144 workspace.update_in(cx, |workspace, window, cx| {
16145 Self::open_locations_in_multibuffer(
16146 workspace,
16147 locations,
16148 format!("Selections for '{title}'"),
16149 false,
16150 MultibufferSelectionMode::All,
16151 window,
16152 cx,
16153 );
16154 })
16155 })
16156 .detach();
16157 }
16158
16159 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16160 /// last highlight added will be used.
16161 ///
16162 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16163 pub fn highlight_rows<T: 'static>(
16164 &mut self,
16165 range: Range<Anchor>,
16166 color: Hsla,
16167 should_autoscroll: bool,
16168 cx: &mut Context<Self>,
16169 ) {
16170 let snapshot = self.buffer().read(cx).snapshot(cx);
16171 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16172 let ix = row_highlights.binary_search_by(|highlight| {
16173 Ordering::Equal
16174 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16175 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16176 });
16177
16178 if let Err(mut ix) = ix {
16179 let index = post_inc(&mut self.highlight_order);
16180
16181 // If this range intersects with the preceding highlight, then merge it with
16182 // the preceding highlight. Otherwise insert a new highlight.
16183 let mut merged = false;
16184 if ix > 0 {
16185 let prev_highlight = &mut row_highlights[ix - 1];
16186 if prev_highlight
16187 .range
16188 .end
16189 .cmp(&range.start, &snapshot)
16190 .is_ge()
16191 {
16192 ix -= 1;
16193 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16194 prev_highlight.range.end = range.end;
16195 }
16196 merged = true;
16197 prev_highlight.index = index;
16198 prev_highlight.color = color;
16199 prev_highlight.should_autoscroll = should_autoscroll;
16200 }
16201 }
16202
16203 if !merged {
16204 row_highlights.insert(
16205 ix,
16206 RowHighlight {
16207 range: range.clone(),
16208 index,
16209 color,
16210 should_autoscroll,
16211 },
16212 );
16213 }
16214
16215 // If any of the following highlights intersect with this one, merge them.
16216 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16217 let highlight = &row_highlights[ix];
16218 if next_highlight
16219 .range
16220 .start
16221 .cmp(&highlight.range.end, &snapshot)
16222 .is_le()
16223 {
16224 if next_highlight
16225 .range
16226 .end
16227 .cmp(&highlight.range.end, &snapshot)
16228 .is_gt()
16229 {
16230 row_highlights[ix].range.end = next_highlight.range.end;
16231 }
16232 row_highlights.remove(ix + 1);
16233 } else {
16234 break;
16235 }
16236 }
16237 }
16238 }
16239
16240 /// Remove any highlighted row ranges of the given type that intersect the
16241 /// given ranges.
16242 pub fn remove_highlighted_rows<T: 'static>(
16243 &mut self,
16244 ranges_to_remove: Vec<Range<Anchor>>,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let snapshot = self.buffer().read(cx).snapshot(cx);
16248 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16249 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16250 row_highlights.retain(|highlight| {
16251 while let Some(range_to_remove) = ranges_to_remove.peek() {
16252 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16253 Ordering::Less | Ordering::Equal => {
16254 ranges_to_remove.next();
16255 }
16256 Ordering::Greater => {
16257 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16258 Ordering::Less | Ordering::Equal => {
16259 return false;
16260 }
16261 Ordering::Greater => break,
16262 }
16263 }
16264 }
16265 }
16266
16267 true
16268 })
16269 }
16270
16271 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16272 pub fn clear_row_highlights<T: 'static>(&mut self) {
16273 self.highlighted_rows.remove(&TypeId::of::<T>());
16274 }
16275
16276 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16277 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16278 self.highlighted_rows
16279 .get(&TypeId::of::<T>())
16280 .map_or(&[] as &[_], |vec| vec.as_slice())
16281 .iter()
16282 .map(|highlight| (highlight.range.clone(), highlight.color))
16283 }
16284
16285 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16286 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16287 /// Allows to ignore certain kinds of highlights.
16288 pub fn highlighted_display_rows(
16289 &self,
16290 window: &mut Window,
16291 cx: &mut App,
16292 ) -> BTreeMap<DisplayRow, LineHighlight> {
16293 let snapshot = self.snapshot(window, cx);
16294 let mut used_highlight_orders = HashMap::default();
16295 self.highlighted_rows
16296 .iter()
16297 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16298 .fold(
16299 BTreeMap::<DisplayRow, LineHighlight>::new(),
16300 |mut unique_rows, highlight| {
16301 let start = highlight.range.start.to_display_point(&snapshot);
16302 let end = highlight.range.end.to_display_point(&snapshot);
16303 let start_row = start.row().0;
16304 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16305 && end.column() == 0
16306 {
16307 end.row().0.saturating_sub(1)
16308 } else {
16309 end.row().0
16310 };
16311 for row in start_row..=end_row {
16312 let used_index =
16313 used_highlight_orders.entry(row).or_insert(highlight.index);
16314 if highlight.index >= *used_index {
16315 *used_index = highlight.index;
16316 unique_rows.insert(DisplayRow(row), highlight.color.into());
16317 }
16318 }
16319 unique_rows
16320 },
16321 )
16322 }
16323
16324 pub fn highlighted_display_row_for_autoscroll(
16325 &self,
16326 snapshot: &DisplaySnapshot,
16327 ) -> Option<DisplayRow> {
16328 self.highlighted_rows
16329 .values()
16330 .flat_map(|highlighted_rows| highlighted_rows.iter())
16331 .filter_map(|highlight| {
16332 if highlight.should_autoscroll {
16333 Some(highlight.range.start.to_display_point(snapshot).row())
16334 } else {
16335 None
16336 }
16337 })
16338 .min()
16339 }
16340
16341 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16342 self.highlight_background::<SearchWithinRange>(
16343 ranges,
16344 |colors| colors.editor_document_highlight_read_background,
16345 cx,
16346 )
16347 }
16348
16349 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16350 self.breadcrumb_header = Some(new_header);
16351 }
16352
16353 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16354 self.clear_background_highlights::<SearchWithinRange>(cx);
16355 }
16356
16357 pub fn highlight_background<T: 'static>(
16358 &mut self,
16359 ranges: &[Range<Anchor>],
16360 color_fetcher: fn(&ThemeColors) -> Hsla,
16361 cx: &mut Context<Self>,
16362 ) {
16363 self.background_highlights
16364 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16365 self.scrollbar_marker_state.dirty = true;
16366 cx.notify();
16367 }
16368
16369 pub fn clear_background_highlights<T: 'static>(
16370 &mut self,
16371 cx: &mut Context<Self>,
16372 ) -> Option<BackgroundHighlight> {
16373 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16374 if !text_highlights.1.is_empty() {
16375 self.scrollbar_marker_state.dirty = true;
16376 cx.notify();
16377 }
16378 Some(text_highlights)
16379 }
16380
16381 pub fn highlight_gutter<T: 'static>(
16382 &mut self,
16383 ranges: &[Range<Anchor>],
16384 color_fetcher: fn(&App) -> Hsla,
16385 cx: &mut Context<Self>,
16386 ) {
16387 self.gutter_highlights
16388 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16389 cx.notify();
16390 }
16391
16392 pub fn clear_gutter_highlights<T: 'static>(
16393 &mut self,
16394 cx: &mut Context<Self>,
16395 ) -> Option<GutterHighlight> {
16396 cx.notify();
16397 self.gutter_highlights.remove(&TypeId::of::<T>())
16398 }
16399
16400 #[cfg(feature = "test-support")]
16401 pub fn all_text_background_highlights(
16402 &self,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16406 let snapshot = self.snapshot(window, cx);
16407 let buffer = &snapshot.buffer_snapshot;
16408 let start = buffer.anchor_before(0);
16409 let end = buffer.anchor_after(buffer.len());
16410 let theme = cx.theme().colors();
16411 self.background_highlights_in_range(start..end, &snapshot, theme)
16412 }
16413
16414 #[cfg(feature = "test-support")]
16415 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16416 let snapshot = self.buffer().read(cx).snapshot(cx);
16417
16418 let highlights = self
16419 .background_highlights
16420 .get(&TypeId::of::<items::BufferSearchHighlights>());
16421
16422 if let Some((_color, ranges)) = highlights {
16423 ranges
16424 .iter()
16425 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16426 .collect_vec()
16427 } else {
16428 vec![]
16429 }
16430 }
16431
16432 fn document_highlights_for_position<'a>(
16433 &'a self,
16434 position: Anchor,
16435 buffer: &'a MultiBufferSnapshot,
16436 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16437 let read_highlights = self
16438 .background_highlights
16439 .get(&TypeId::of::<DocumentHighlightRead>())
16440 .map(|h| &h.1);
16441 let write_highlights = self
16442 .background_highlights
16443 .get(&TypeId::of::<DocumentHighlightWrite>())
16444 .map(|h| &h.1);
16445 let left_position = position.bias_left(buffer);
16446 let right_position = position.bias_right(buffer);
16447 read_highlights
16448 .into_iter()
16449 .chain(write_highlights)
16450 .flat_map(move |ranges| {
16451 let start_ix = match ranges.binary_search_by(|probe| {
16452 let cmp = probe.end.cmp(&left_position, buffer);
16453 if cmp.is_ge() {
16454 Ordering::Greater
16455 } else {
16456 Ordering::Less
16457 }
16458 }) {
16459 Ok(i) | Err(i) => i,
16460 };
16461
16462 ranges[start_ix..]
16463 .iter()
16464 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16465 })
16466 }
16467
16468 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16469 self.background_highlights
16470 .get(&TypeId::of::<T>())
16471 .map_or(false, |(_, highlights)| !highlights.is_empty())
16472 }
16473
16474 pub fn background_highlights_in_range(
16475 &self,
16476 search_range: Range<Anchor>,
16477 display_snapshot: &DisplaySnapshot,
16478 theme: &ThemeColors,
16479 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16480 let mut results = Vec::new();
16481 for (color_fetcher, ranges) in self.background_highlights.values() {
16482 let color = color_fetcher(theme);
16483 let start_ix = match ranges.binary_search_by(|probe| {
16484 let cmp = probe
16485 .end
16486 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16487 if cmp.is_gt() {
16488 Ordering::Greater
16489 } else {
16490 Ordering::Less
16491 }
16492 }) {
16493 Ok(i) | Err(i) => i,
16494 };
16495 for range in &ranges[start_ix..] {
16496 if range
16497 .start
16498 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16499 .is_ge()
16500 {
16501 break;
16502 }
16503
16504 let start = range.start.to_display_point(display_snapshot);
16505 let end = range.end.to_display_point(display_snapshot);
16506 results.push((start..end, color))
16507 }
16508 }
16509 results
16510 }
16511
16512 pub fn background_highlight_row_ranges<T: 'static>(
16513 &self,
16514 search_range: Range<Anchor>,
16515 display_snapshot: &DisplaySnapshot,
16516 count: usize,
16517 ) -> Vec<RangeInclusive<DisplayPoint>> {
16518 let mut results = Vec::new();
16519 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16520 return vec![];
16521 };
16522
16523 let start_ix = match ranges.binary_search_by(|probe| {
16524 let cmp = probe
16525 .end
16526 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16527 if cmp.is_gt() {
16528 Ordering::Greater
16529 } else {
16530 Ordering::Less
16531 }
16532 }) {
16533 Ok(i) | Err(i) => i,
16534 };
16535 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16536 if let (Some(start_display), Some(end_display)) = (start, end) {
16537 results.push(
16538 start_display.to_display_point(display_snapshot)
16539 ..=end_display.to_display_point(display_snapshot),
16540 );
16541 }
16542 };
16543 let mut start_row: Option<Point> = None;
16544 let mut end_row: Option<Point> = None;
16545 if ranges.len() > count {
16546 return Vec::new();
16547 }
16548 for range in &ranges[start_ix..] {
16549 if range
16550 .start
16551 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16552 .is_ge()
16553 {
16554 break;
16555 }
16556 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16557 if let Some(current_row) = &end_row {
16558 if end.row == current_row.row {
16559 continue;
16560 }
16561 }
16562 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16563 if start_row.is_none() {
16564 assert_eq!(end_row, None);
16565 start_row = Some(start);
16566 end_row = Some(end);
16567 continue;
16568 }
16569 if let Some(current_end) = end_row.as_mut() {
16570 if start.row > current_end.row + 1 {
16571 push_region(start_row, end_row);
16572 start_row = Some(start);
16573 end_row = Some(end);
16574 } else {
16575 // Merge two hunks.
16576 *current_end = end;
16577 }
16578 } else {
16579 unreachable!();
16580 }
16581 }
16582 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16583 push_region(start_row, end_row);
16584 results
16585 }
16586
16587 pub fn gutter_highlights_in_range(
16588 &self,
16589 search_range: Range<Anchor>,
16590 display_snapshot: &DisplaySnapshot,
16591 cx: &App,
16592 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16593 let mut results = Vec::new();
16594 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16595 let color = color_fetcher(cx);
16596 let start_ix = match ranges.binary_search_by(|probe| {
16597 let cmp = probe
16598 .end
16599 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16600 if cmp.is_gt() {
16601 Ordering::Greater
16602 } else {
16603 Ordering::Less
16604 }
16605 }) {
16606 Ok(i) | Err(i) => i,
16607 };
16608 for range in &ranges[start_ix..] {
16609 if range
16610 .start
16611 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16612 .is_ge()
16613 {
16614 break;
16615 }
16616
16617 let start = range.start.to_display_point(display_snapshot);
16618 let end = range.end.to_display_point(display_snapshot);
16619 results.push((start..end, color))
16620 }
16621 }
16622 results
16623 }
16624
16625 /// Get the text ranges corresponding to the redaction query
16626 pub fn redacted_ranges(
16627 &self,
16628 search_range: Range<Anchor>,
16629 display_snapshot: &DisplaySnapshot,
16630 cx: &App,
16631 ) -> Vec<Range<DisplayPoint>> {
16632 display_snapshot
16633 .buffer_snapshot
16634 .redacted_ranges(search_range, |file| {
16635 if let Some(file) = file {
16636 file.is_private()
16637 && EditorSettings::get(
16638 Some(SettingsLocation {
16639 worktree_id: file.worktree_id(cx),
16640 path: file.path().as_ref(),
16641 }),
16642 cx,
16643 )
16644 .redact_private_values
16645 } else {
16646 false
16647 }
16648 })
16649 .map(|range| {
16650 range.start.to_display_point(display_snapshot)
16651 ..range.end.to_display_point(display_snapshot)
16652 })
16653 .collect()
16654 }
16655
16656 pub fn highlight_text<T: 'static>(
16657 &mut self,
16658 ranges: Vec<Range<Anchor>>,
16659 style: HighlightStyle,
16660 cx: &mut Context<Self>,
16661 ) {
16662 self.display_map.update(cx, |map, _| {
16663 map.highlight_text(TypeId::of::<T>(), ranges, style)
16664 });
16665 cx.notify();
16666 }
16667
16668 pub(crate) fn highlight_inlays<T: 'static>(
16669 &mut self,
16670 highlights: Vec<InlayHighlight>,
16671 style: HighlightStyle,
16672 cx: &mut Context<Self>,
16673 ) {
16674 self.display_map.update(cx, |map, _| {
16675 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16676 });
16677 cx.notify();
16678 }
16679
16680 pub fn text_highlights<'a, T: 'static>(
16681 &'a self,
16682 cx: &'a App,
16683 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16684 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16685 }
16686
16687 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16688 let cleared = self
16689 .display_map
16690 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16691 if cleared {
16692 cx.notify();
16693 }
16694 }
16695
16696 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16697 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16698 && self.focus_handle.is_focused(window)
16699 }
16700
16701 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16702 self.show_cursor_when_unfocused = is_enabled;
16703 cx.notify();
16704 }
16705
16706 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16707 cx.notify();
16708 }
16709
16710 fn on_buffer_event(
16711 &mut self,
16712 multibuffer: &Entity<MultiBuffer>,
16713 event: &multi_buffer::Event,
16714 window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) {
16717 match event {
16718 multi_buffer::Event::Edited {
16719 singleton_buffer_edited,
16720 edited_buffer: buffer_edited,
16721 } => {
16722 self.scrollbar_marker_state.dirty = true;
16723 self.active_indent_guides_state.dirty = true;
16724 self.refresh_active_diagnostics(cx);
16725 self.refresh_code_actions(window, cx);
16726 if self.has_active_inline_completion() {
16727 self.update_visible_inline_completion(window, cx);
16728 }
16729 if let Some(buffer) = buffer_edited {
16730 let buffer_id = buffer.read(cx).remote_id();
16731 if !self.registered_buffers.contains_key(&buffer_id) {
16732 if let Some(project) = self.project.as_ref() {
16733 project.update(cx, |project, cx| {
16734 self.registered_buffers.insert(
16735 buffer_id,
16736 project.register_buffer_with_language_servers(&buffer, cx),
16737 );
16738 })
16739 }
16740 }
16741 }
16742 cx.emit(EditorEvent::BufferEdited);
16743 cx.emit(SearchEvent::MatchesInvalidated);
16744 if *singleton_buffer_edited {
16745 if let Some(project) = &self.project {
16746 #[allow(clippy::mutable_key_type)]
16747 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16748 multibuffer
16749 .all_buffers()
16750 .into_iter()
16751 .filter_map(|buffer| {
16752 buffer.update(cx, |buffer, cx| {
16753 let language = buffer.language()?;
16754 let should_discard = project.update(cx, |project, cx| {
16755 project.is_local()
16756 && !project.has_language_servers_for(buffer, cx)
16757 });
16758 should_discard.not().then_some(language.clone())
16759 })
16760 })
16761 .collect::<HashSet<_>>()
16762 });
16763 if !languages_affected.is_empty() {
16764 self.refresh_inlay_hints(
16765 InlayHintRefreshReason::BufferEdited(languages_affected),
16766 cx,
16767 );
16768 }
16769 }
16770 }
16771
16772 let Some(project) = &self.project else { return };
16773 let (telemetry, is_via_ssh) = {
16774 let project = project.read(cx);
16775 let telemetry = project.client().telemetry().clone();
16776 let is_via_ssh = project.is_via_ssh();
16777 (telemetry, is_via_ssh)
16778 };
16779 refresh_linked_ranges(self, window, cx);
16780 telemetry.log_edit_event("editor", is_via_ssh);
16781 }
16782 multi_buffer::Event::ExcerptsAdded {
16783 buffer,
16784 predecessor,
16785 excerpts,
16786 } => {
16787 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16788 let buffer_id = buffer.read(cx).remote_id();
16789 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16790 if let Some(project) = &self.project {
16791 get_uncommitted_diff_for_buffer(
16792 project,
16793 [buffer.clone()],
16794 self.buffer.clone(),
16795 cx,
16796 )
16797 .detach();
16798 }
16799 }
16800 cx.emit(EditorEvent::ExcerptsAdded {
16801 buffer: buffer.clone(),
16802 predecessor: *predecessor,
16803 excerpts: excerpts.clone(),
16804 });
16805 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16806 }
16807 multi_buffer::Event::ExcerptsRemoved { ids } => {
16808 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16809 let buffer = self.buffer.read(cx);
16810 self.registered_buffers
16811 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16812 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16813 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16814 }
16815 multi_buffer::Event::ExcerptsEdited {
16816 excerpt_ids,
16817 buffer_ids,
16818 } => {
16819 self.display_map.update(cx, |map, cx| {
16820 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16821 });
16822 cx.emit(EditorEvent::ExcerptsEdited {
16823 ids: excerpt_ids.clone(),
16824 })
16825 }
16826 multi_buffer::Event::ExcerptsExpanded { ids } => {
16827 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16828 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16829 }
16830 multi_buffer::Event::Reparsed(buffer_id) => {
16831 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16832 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16833
16834 cx.emit(EditorEvent::Reparsed(*buffer_id));
16835 }
16836 multi_buffer::Event::DiffHunksToggled => {
16837 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16838 }
16839 multi_buffer::Event::LanguageChanged(buffer_id) => {
16840 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16841 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16842 cx.emit(EditorEvent::Reparsed(*buffer_id));
16843 cx.notify();
16844 }
16845 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16846 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16847 multi_buffer::Event::FileHandleChanged
16848 | multi_buffer::Event::Reloaded
16849 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16850 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16851 multi_buffer::Event::DiagnosticsUpdated => {
16852 self.refresh_active_diagnostics(cx);
16853 self.refresh_inline_diagnostics(true, window, cx);
16854 self.scrollbar_marker_state.dirty = true;
16855 cx.notify();
16856 }
16857 _ => {}
16858 };
16859 }
16860
16861 fn on_display_map_changed(
16862 &mut self,
16863 _: Entity<DisplayMap>,
16864 _: &mut Window,
16865 cx: &mut Context<Self>,
16866 ) {
16867 cx.notify();
16868 }
16869
16870 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16871 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16872 self.update_edit_prediction_settings(cx);
16873 self.refresh_inline_completion(true, false, window, cx);
16874 self.refresh_inlay_hints(
16875 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16876 self.selections.newest_anchor().head(),
16877 &self.buffer.read(cx).snapshot(cx),
16878 cx,
16879 )),
16880 cx,
16881 );
16882
16883 let old_cursor_shape = self.cursor_shape;
16884
16885 {
16886 let editor_settings = EditorSettings::get_global(cx);
16887 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16888 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16889 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16890 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
16891 }
16892
16893 if old_cursor_shape != self.cursor_shape {
16894 cx.emit(EditorEvent::CursorShapeChanged);
16895 }
16896
16897 let project_settings = ProjectSettings::get_global(cx);
16898 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16899
16900 if self.mode == EditorMode::Full {
16901 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16902 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16903 if self.show_inline_diagnostics != show_inline_diagnostics {
16904 self.show_inline_diagnostics = show_inline_diagnostics;
16905 self.refresh_inline_diagnostics(false, window, cx);
16906 }
16907
16908 if self.git_blame_inline_enabled != inline_blame_enabled {
16909 self.toggle_git_blame_inline_internal(false, window, cx);
16910 }
16911 }
16912
16913 cx.notify();
16914 }
16915
16916 pub fn set_searchable(&mut self, searchable: bool) {
16917 self.searchable = searchable;
16918 }
16919
16920 pub fn searchable(&self) -> bool {
16921 self.searchable
16922 }
16923
16924 fn open_proposed_changes_editor(
16925 &mut self,
16926 _: &OpenProposedChangesEditor,
16927 window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 let Some(workspace) = self.workspace() else {
16931 cx.propagate();
16932 return;
16933 };
16934
16935 let selections = self.selections.all::<usize>(cx);
16936 let multi_buffer = self.buffer.read(cx);
16937 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16938 let mut new_selections_by_buffer = HashMap::default();
16939 for selection in selections {
16940 for (buffer, range, _) in
16941 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16942 {
16943 let mut range = range.to_point(buffer);
16944 range.start.column = 0;
16945 range.end.column = buffer.line_len(range.end.row);
16946 new_selections_by_buffer
16947 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16948 .or_insert(Vec::new())
16949 .push(range)
16950 }
16951 }
16952
16953 let proposed_changes_buffers = new_selections_by_buffer
16954 .into_iter()
16955 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16956 .collect::<Vec<_>>();
16957 let proposed_changes_editor = cx.new(|cx| {
16958 ProposedChangesEditor::new(
16959 "Proposed changes",
16960 proposed_changes_buffers,
16961 self.project.clone(),
16962 window,
16963 cx,
16964 )
16965 });
16966
16967 window.defer(cx, move |window, cx| {
16968 workspace.update(cx, |workspace, cx| {
16969 workspace.active_pane().update(cx, |pane, cx| {
16970 pane.add_item(
16971 Box::new(proposed_changes_editor),
16972 true,
16973 true,
16974 None,
16975 window,
16976 cx,
16977 );
16978 });
16979 });
16980 });
16981 }
16982
16983 pub fn open_excerpts_in_split(
16984 &mut self,
16985 _: &OpenExcerptsSplit,
16986 window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) {
16989 self.open_excerpts_common(None, true, window, cx)
16990 }
16991
16992 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
16993 self.open_excerpts_common(None, false, window, cx)
16994 }
16995
16996 fn open_excerpts_common(
16997 &mut self,
16998 jump_data: Option<JumpData>,
16999 split: bool,
17000 window: &mut Window,
17001 cx: &mut Context<Self>,
17002 ) {
17003 let Some(workspace) = self.workspace() else {
17004 cx.propagate();
17005 return;
17006 };
17007
17008 if self.buffer.read(cx).is_singleton() {
17009 cx.propagate();
17010 return;
17011 }
17012
17013 let mut new_selections_by_buffer = HashMap::default();
17014 match &jump_data {
17015 Some(JumpData::MultiBufferPoint {
17016 excerpt_id,
17017 position,
17018 anchor,
17019 line_offset_from_top,
17020 }) => {
17021 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17022 if let Some(buffer) = multi_buffer_snapshot
17023 .buffer_id_for_excerpt(*excerpt_id)
17024 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17025 {
17026 let buffer_snapshot = buffer.read(cx).snapshot();
17027 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17028 language::ToPoint::to_point(anchor, &buffer_snapshot)
17029 } else {
17030 buffer_snapshot.clip_point(*position, Bias::Left)
17031 };
17032 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17033 new_selections_by_buffer.insert(
17034 buffer,
17035 (
17036 vec![jump_to_offset..jump_to_offset],
17037 Some(*line_offset_from_top),
17038 ),
17039 );
17040 }
17041 }
17042 Some(JumpData::MultiBufferRow {
17043 row,
17044 line_offset_from_top,
17045 }) => {
17046 let point = MultiBufferPoint::new(row.0, 0);
17047 if let Some((buffer, buffer_point, _)) =
17048 self.buffer.read(cx).point_to_buffer_point(point, cx)
17049 {
17050 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17051 new_selections_by_buffer
17052 .entry(buffer)
17053 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17054 .0
17055 .push(buffer_offset..buffer_offset)
17056 }
17057 }
17058 None => {
17059 let selections = self.selections.all::<usize>(cx);
17060 let multi_buffer = self.buffer.read(cx);
17061 for selection in selections {
17062 for (snapshot, range, _, anchor) in multi_buffer
17063 .snapshot(cx)
17064 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17065 {
17066 if let Some(anchor) = anchor {
17067 // selection is in a deleted hunk
17068 let Some(buffer_id) = anchor.buffer_id else {
17069 continue;
17070 };
17071 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17072 continue;
17073 };
17074 let offset = text::ToOffset::to_offset(
17075 &anchor.text_anchor,
17076 &buffer_handle.read(cx).snapshot(),
17077 );
17078 let range = offset..offset;
17079 new_selections_by_buffer
17080 .entry(buffer_handle)
17081 .or_insert((Vec::new(), None))
17082 .0
17083 .push(range)
17084 } else {
17085 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17086 else {
17087 continue;
17088 };
17089 new_selections_by_buffer
17090 .entry(buffer_handle)
17091 .or_insert((Vec::new(), None))
17092 .0
17093 .push(range)
17094 }
17095 }
17096 }
17097 }
17098 }
17099
17100 if new_selections_by_buffer.is_empty() {
17101 return;
17102 }
17103
17104 // We defer the pane interaction because we ourselves are a workspace item
17105 // and activating a new item causes the pane to call a method on us reentrantly,
17106 // which panics if we're on the stack.
17107 window.defer(cx, move |window, cx| {
17108 workspace.update(cx, |workspace, cx| {
17109 let pane = if split {
17110 workspace.adjacent_pane(window, cx)
17111 } else {
17112 workspace.active_pane().clone()
17113 };
17114
17115 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17116 let editor = buffer
17117 .read(cx)
17118 .file()
17119 .is_none()
17120 .then(|| {
17121 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17122 // so `workspace.open_project_item` will never find them, always opening a new editor.
17123 // Instead, we try to activate the existing editor in the pane first.
17124 let (editor, pane_item_index) =
17125 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17126 let editor = item.downcast::<Editor>()?;
17127 let singleton_buffer =
17128 editor.read(cx).buffer().read(cx).as_singleton()?;
17129 if singleton_buffer == buffer {
17130 Some((editor, i))
17131 } else {
17132 None
17133 }
17134 })?;
17135 pane.update(cx, |pane, cx| {
17136 pane.activate_item(pane_item_index, true, true, window, cx)
17137 });
17138 Some(editor)
17139 })
17140 .flatten()
17141 .unwrap_or_else(|| {
17142 workspace.open_project_item::<Self>(
17143 pane.clone(),
17144 buffer,
17145 true,
17146 true,
17147 window,
17148 cx,
17149 )
17150 });
17151
17152 editor.update(cx, |editor, cx| {
17153 let autoscroll = match scroll_offset {
17154 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17155 None => Autoscroll::newest(),
17156 };
17157 let nav_history = editor.nav_history.take();
17158 editor.change_selections(Some(autoscroll), window, cx, |s| {
17159 s.select_ranges(ranges);
17160 });
17161 editor.nav_history = nav_history;
17162 });
17163 }
17164 })
17165 });
17166 }
17167
17168 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17169 let snapshot = self.buffer.read(cx).read(cx);
17170 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17171 Some(
17172 ranges
17173 .iter()
17174 .map(move |range| {
17175 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17176 })
17177 .collect(),
17178 )
17179 }
17180
17181 fn selection_replacement_ranges(
17182 &self,
17183 range: Range<OffsetUtf16>,
17184 cx: &mut App,
17185 ) -> Vec<Range<OffsetUtf16>> {
17186 let selections = self.selections.all::<OffsetUtf16>(cx);
17187 let newest_selection = selections
17188 .iter()
17189 .max_by_key(|selection| selection.id)
17190 .unwrap();
17191 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17192 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17193 let snapshot = self.buffer.read(cx).read(cx);
17194 selections
17195 .into_iter()
17196 .map(|mut selection| {
17197 selection.start.0 =
17198 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17199 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17200 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17201 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17202 })
17203 .collect()
17204 }
17205
17206 fn report_editor_event(
17207 &self,
17208 event_type: &'static str,
17209 file_extension: Option<String>,
17210 cx: &App,
17211 ) {
17212 if cfg!(any(test, feature = "test-support")) {
17213 return;
17214 }
17215
17216 let Some(project) = &self.project else { return };
17217
17218 // If None, we are in a file without an extension
17219 let file = self
17220 .buffer
17221 .read(cx)
17222 .as_singleton()
17223 .and_then(|b| b.read(cx).file());
17224 let file_extension = file_extension.or(file
17225 .as_ref()
17226 .and_then(|file| Path::new(file.file_name(cx)).extension())
17227 .and_then(|e| e.to_str())
17228 .map(|a| a.to_string()));
17229
17230 let vim_mode = cx
17231 .global::<SettingsStore>()
17232 .raw_user_settings()
17233 .get("vim_mode")
17234 == Some(&serde_json::Value::Bool(true));
17235
17236 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17237 let copilot_enabled = edit_predictions_provider
17238 == language::language_settings::EditPredictionProvider::Copilot;
17239 let copilot_enabled_for_language = self
17240 .buffer
17241 .read(cx)
17242 .language_settings(cx)
17243 .show_edit_predictions;
17244
17245 let project = project.read(cx);
17246 telemetry::event!(
17247 event_type,
17248 file_extension,
17249 vim_mode,
17250 copilot_enabled,
17251 copilot_enabled_for_language,
17252 edit_predictions_provider,
17253 is_via_ssh = project.is_via_ssh(),
17254 );
17255 }
17256
17257 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17258 /// with each line being an array of {text, highlight} objects.
17259 fn copy_highlight_json(
17260 &mut self,
17261 _: &CopyHighlightJson,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) {
17265 #[derive(Serialize)]
17266 struct Chunk<'a> {
17267 text: String,
17268 highlight: Option<&'a str>,
17269 }
17270
17271 let snapshot = self.buffer.read(cx).snapshot(cx);
17272 let range = self
17273 .selected_text_range(false, window, cx)
17274 .and_then(|selection| {
17275 if selection.range.is_empty() {
17276 None
17277 } else {
17278 Some(selection.range)
17279 }
17280 })
17281 .unwrap_or_else(|| 0..snapshot.len());
17282
17283 let chunks = snapshot.chunks(range, true);
17284 let mut lines = Vec::new();
17285 let mut line: VecDeque<Chunk> = VecDeque::new();
17286
17287 let Some(style) = self.style.as_ref() else {
17288 return;
17289 };
17290
17291 for chunk in chunks {
17292 let highlight = chunk
17293 .syntax_highlight_id
17294 .and_then(|id| id.name(&style.syntax));
17295 let mut chunk_lines = chunk.text.split('\n').peekable();
17296 while let Some(text) = chunk_lines.next() {
17297 let mut merged_with_last_token = false;
17298 if let Some(last_token) = line.back_mut() {
17299 if last_token.highlight == highlight {
17300 last_token.text.push_str(text);
17301 merged_with_last_token = true;
17302 }
17303 }
17304
17305 if !merged_with_last_token {
17306 line.push_back(Chunk {
17307 text: text.into(),
17308 highlight,
17309 });
17310 }
17311
17312 if chunk_lines.peek().is_some() {
17313 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17314 line.pop_front();
17315 }
17316 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17317 line.pop_back();
17318 }
17319
17320 lines.push(mem::take(&mut line));
17321 }
17322 }
17323 }
17324
17325 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17326 return;
17327 };
17328 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17329 }
17330
17331 pub fn open_context_menu(
17332 &mut self,
17333 _: &OpenContextMenu,
17334 window: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) {
17337 self.request_autoscroll(Autoscroll::newest(), cx);
17338 let position = self.selections.newest_display(cx).start;
17339 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17340 }
17341
17342 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17343 &self.inlay_hint_cache
17344 }
17345
17346 pub fn replay_insert_event(
17347 &mut self,
17348 text: &str,
17349 relative_utf16_range: Option<Range<isize>>,
17350 window: &mut Window,
17351 cx: &mut Context<Self>,
17352 ) {
17353 if !self.input_enabled {
17354 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17355 return;
17356 }
17357 if let Some(relative_utf16_range) = relative_utf16_range {
17358 let selections = self.selections.all::<OffsetUtf16>(cx);
17359 self.change_selections(None, window, cx, |s| {
17360 let new_ranges = selections.into_iter().map(|range| {
17361 let start = OffsetUtf16(
17362 range
17363 .head()
17364 .0
17365 .saturating_add_signed(relative_utf16_range.start),
17366 );
17367 let end = OffsetUtf16(
17368 range
17369 .head()
17370 .0
17371 .saturating_add_signed(relative_utf16_range.end),
17372 );
17373 start..end
17374 });
17375 s.select_ranges(new_ranges);
17376 });
17377 }
17378
17379 self.handle_input(text, window, cx);
17380 }
17381
17382 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17383 let Some(provider) = self.semantics_provider.as_ref() else {
17384 return false;
17385 };
17386
17387 let mut supports = false;
17388 self.buffer().update(cx, |this, cx| {
17389 this.for_each_buffer(|buffer| {
17390 supports |= provider.supports_inlay_hints(buffer, cx);
17391 });
17392 });
17393
17394 supports
17395 }
17396
17397 pub fn is_focused(&self, window: &Window) -> bool {
17398 self.focus_handle.is_focused(window)
17399 }
17400
17401 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17402 cx.emit(EditorEvent::Focused);
17403
17404 if let Some(descendant) = self
17405 .last_focused_descendant
17406 .take()
17407 .and_then(|descendant| descendant.upgrade())
17408 {
17409 window.focus(&descendant);
17410 } else {
17411 if let Some(blame) = self.blame.as_ref() {
17412 blame.update(cx, GitBlame::focus)
17413 }
17414
17415 self.blink_manager.update(cx, BlinkManager::enable);
17416 self.show_cursor_names(window, cx);
17417 self.buffer.update(cx, |buffer, cx| {
17418 buffer.finalize_last_transaction(cx);
17419 if self.leader_peer_id.is_none() {
17420 buffer.set_active_selections(
17421 &self.selections.disjoint_anchors(),
17422 self.selections.line_mode,
17423 self.cursor_shape,
17424 cx,
17425 );
17426 }
17427 });
17428 }
17429 }
17430
17431 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17432 cx.emit(EditorEvent::FocusedIn)
17433 }
17434
17435 fn handle_focus_out(
17436 &mut self,
17437 event: FocusOutEvent,
17438 _window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 if event.blurred != self.focus_handle {
17442 self.last_focused_descendant = Some(event.blurred);
17443 }
17444 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17445 }
17446
17447 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17448 self.blink_manager.update(cx, BlinkManager::disable);
17449 self.buffer
17450 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17451
17452 if let Some(blame) = self.blame.as_ref() {
17453 blame.update(cx, GitBlame::blur)
17454 }
17455 if !self.hover_state.focused(window, cx) {
17456 hide_hover(self, cx);
17457 }
17458 if !self
17459 .context_menu
17460 .borrow()
17461 .as_ref()
17462 .is_some_and(|context_menu| context_menu.focused(window, cx))
17463 {
17464 self.hide_context_menu(window, cx);
17465 }
17466 self.discard_inline_completion(false, cx);
17467 cx.emit(EditorEvent::Blurred);
17468 cx.notify();
17469 }
17470
17471 pub fn register_action<A: Action>(
17472 &mut self,
17473 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17474 ) -> Subscription {
17475 let id = self.next_editor_action_id.post_inc();
17476 let listener = Arc::new(listener);
17477 self.editor_actions.borrow_mut().insert(
17478 id,
17479 Box::new(move |window, _| {
17480 let listener = listener.clone();
17481 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17482 let action = action.downcast_ref().unwrap();
17483 if phase == DispatchPhase::Bubble {
17484 listener(action, window, cx)
17485 }
17486 })
17487 }),
17488 );
17489
17490 let editor_actions = self.editor_actions.clone();
17491 Subscription::new(move || {
17492 editor_actions.borrow_mut().remove(&id);
17493 })
17494 }
17495
17496 pub fn file_header_size(&self) -> u32 {
17497 FILE_HEADER_HEIGHT
17498 }
17499
17500 pub fn restore(
17501 &mut self,
17502 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 let workspace = self.workspace();
17507 let project = self.project.as_ref();
17508 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17509 let mut tasks = Vec::new();
17510 for (buffer_id, changes) in revert_changes {
17511 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17512 buffer.update(cx, |buffer, cx| {
17513 buffer.edit(
17514 changes
17515 .into_iter()
17516 .map(|(range, text)| (range, text.to_string())),
17517 None,
17518 cx,
17519 );
17520 });
17521
17522 if let Some(project) =
17523 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17524 {
17525 project.update(cx, |project, cx| {
17526 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17527 })
17528 }
17529 }
17530 }
17531 tasks
17532 });
17533 cx.spawn_in(window, async move |_, cx| {
17534 for (buffer, task) in save_tasks {
17535 let result = task.await;
17536 if result.is_err() {
17537 let Some(path) = buffer
17538 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17539 .ok()
17540 else {
17541 continue;
17542 };
17543 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17544 let Some(task) = cx
17545 .update_window_entity(&workspace, |workspace, window, cx| {
17546 workspace
17547 .open_path_preview(path, None, false, false, false, window, cx)
17548 })
17549 .ok()
17550 else {
17551 continue;
17552 };
17553 task.await.log_err();
17554 }
17555 }
17556 }
17557 })
17558 .detach();
17559 self.change_selections(None, window, cx, |selections| selections.refresh());
17560 }
17561
17562 pub fn to_pixel_point(
17563 &self,
17564 source: multi_buffer::Anchor,
17565 editor_snapshot: &EditorSnapshot,
17566 window: &mut Window,
17567 ) -> Option<gpui::Point<Pixels>> {
17568 let source_point = source.to_display_point(editor_snapshot);
17569 self.display_to_pixel_point(source_point, editor_snapshot, window)
17570 }
17571
17572 pub fn display_to_pixel_point(
17573 &self,
17574 source: DisplayPoint,
17575 editor_snapshot: &EditorSnapshot,
17576 window: &mut Window,
17577 ) -> Option<gpui::Point<Pixels>> {
17578 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17579 let text_layout_details = self.text_layout_details(window);
17580 let scroll_top = text_layout_details
17581 .scroll_anchor
17582 .scroll_position(editor_snapshot)
17583 .y;
17584
17585 if source.row().as_f32() < scroll_top.floor() {
17586 return None;
17587 }
17588 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17589 let source_y = line_height * (source.row().as_f32() - scroll_top);
17590 Some(gpui::Point::new(source_x, source_y))
17591 }
17592
17593 pub fn has_visible_completions_menu(&self) -> bool {
17594 !self.edit_prediction_preview_is_active()
17595 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17596 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17597 })
17598 }
17599
17600 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17601 self.addons
17602 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17603 }
17604
17605 pub fn unregister_addon<T: Addon>(&mut self) {
17606 self.addons.remove(&std::any::TypeId::of::<T>());
17607 }
17608
17609 pub fn addon<T: Addon>(&self) -> Option<&T> {
17610 let type_id = std::any::TypeId::of::<T>();
17611 self.addons
17612 .get(&type_id)
17613 .and_then(|item| item.to_any().downcast_ref::<T>())
17614 }
17615
17616 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17617 let text_layout_details = self.text_layout_details(window);
17618 let style = &text_layout_details.editor_style;
17619 let font_id = window.text_system().resolve_font(&style.text.font());
17620 let font_size = style.text.font_size.to_pixels(window.rem_size());
17621 let line_height = style.text.line_height_in_pixels(window.rem_size());
17622 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17623
17624 gpui::Size::new(em_width, line_height)
17625 }
17626
17627 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17628 self.load_diff_task.clone()
17629 }
17630
17631 fn read_metadata_from_db(
17632 &mut self,
17633 item_id: u64,
17634 workspace_id: WorkspaceId,
17635 window: &mut Window,
17636 cx: &mut Context<Editor>,
17637 ) {
17638 if self.is_singleton(cx)
17639 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17640 {
17641 let buffer_snapshot = OnceCell::new();
17642
17643 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17644 if !folds.is_empty() {
17645 let snapshot =
17646 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17647 self.fold_ranges(
17648 folds
17649 .into_iter()
17650 .map(|(start, end)| {
17651 snapshot.clip_offset(start, Bias::Left)
17652 ..snapshot.clip_offset(end, Bias::Right)
17653 })
17654 .collect(),
17655 false,
17656 window,
17657 cx,
17658 );
17659 }
17660 }
17661
17662 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17663 if !selections.is_empty() {
17664 let snapshot =
17665 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17666 self.change_selections(None, window, cx, |s| {
17667 s.select_ranges(selections.into_iter().map(|(start, end)| {
17668 snapshot.clip_offset(start, Bias::Left)
17669 ..snapshot.clip_offset(end, Bias::Right)
17670 }));
17671 });
17672 }
17673 };
17674 }
17675
17676 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17677 }
17678}
17679
17680fn insert_extra_newline_brackets(
17681 buffer: &MultiBufferSnapshot,
17682 range: Range<usize>,
17683 language: &language::LanguageScope,
17684) -> bool {
17685 let leading_whitespace_len = buffer
17686 .reversed_chars_at(range.start)
17687 .take_while(|c| c.is_whitespace() && *c != '\n')
17688 .map(|c| c.len_utf8())
17689 .sum::<usize>();
17690 let trailing_whitespace_len = buffer
17691 .chars_at(range.end)
17692 .take_while(|c| c.is_whitespace() && *c != '\n')
17693 .map(|c| c.len_utf8())
17694 .sum::<usize>();
17695 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17696
17697 language.brackets().any(|(pair, enabled)| {
17698 let pair_start = pair.start.trim_end();
17699 let pair_end = pair.end.trim_start();
17700
17701 enabled
17702 && pair.newline
17703 && buffer.contains_str_at(range.end, pair_end)
17704 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17705 })
17706}
17707
17708fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17709 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17710 [(buffer, range, _)] => (*buffer, range.clone()),
17711 _ => return false,
17712 };
17713 let pair = {
17714 let mut result: Option<BracketMatch> = None;
17715
17716 for pair in buffer
17717 .all_bracket_ranges(range.clone())
17718 .filter(move |pair| {
17719 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17720 })
17721 {
17722 let len = pair.close_range.end - pair.open_range.start;
17723
17724 if let Some(existing) = &result {
17725 let existing_len = existing.close_range.end - existing.open_range.start;
17726 if len > existing_len {
17727 continue;
17728 }
17729 }
17730
17731 result = Some(pair);
17732 }
17733
17734 result
17735 };
17736 let Some(pair) = pair else {
17737 return false;
17738 };
17739 pair.newline_only
17740 && buffer
17741 .chars_for_range(pair.open_range.end..range.start)
17742 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17743 .all(|c| c.is_whitespace() && c != '\n')
17744}
17745
17746fn get_uncommitted_diff_for_buffer(
17747 project: &Entity<Project>,
17748 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17749 buffer: Entity<MultiBuffer>,
17750 cx: &mut App,
17751) -> Task<()> {
17752 let mut tasks = Vec::new();
17753 project.update(cx, |project, cx| {
17754 for buffer in buffers {
17755 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17756 }
17757 });
17758 cx.spawn(async move |cx| {
17759 let diffs = future::join_all(tasks).await;
17760 buffer
17761 .update(cx, |buffer, cx| {
17762 for diff in diffs.into_iter().flatten() {
17763 buffer.add_diff(diff, cx);
17764 }
17765 })
17766 .ok();
17767 })
17768}
17769
17770fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17771 let tab_size = tab_size.get() as usize;
17772 let mut width = offset;
17773
17774 for ch in text.chars() {
17775 width += if ch == '\t' {
17776 tab_size - (width % tab_size)
17777 } else {
17778 1
17779 };
17780 }
17781
17782 width - offset
17783}
17784
17785#[cfg(test)]
17786mod tests {
17787 use super::*;
17788
17789 #[test]
17790 fn test_string_size_with_expanded_tabs() {
17791 let nz = |val| NonZeroU32::new(val).unwrap();
17792 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17793 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17794 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17795 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17796 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17797 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17798 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17799 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17800 }
17801}
17802
17803/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17804struct WordBreakingTokenizer<'a> {
17805 input: &'a str,
17806}
17807
17808impl<'a> WordBreakingTokenizer<'a> {
17809 fn new(input: &'a str) -> Self {
17810 Self { input }
17811 }
17812}
17813
17814fn is_char_ideographic(ch: char) -> bool {
17815 use unicode_script::Script::*;
17816 use unicode_script::UnicodeScript;
17817 matches!(ch.script(), Han | Tangut | Yi)
17818}
17819
17820fn is_grapheme_ideographic(text: &str) -> bool {
17821 text.chars().any(is_char_ideographic)
17822}
17823
17824fn is_grapheme_whitespace(text: &str) -> bool {
17825 text.chars().any(|x| x.is_whitespace())
17826}
17827
17828fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17829 text.chars().next().map_or(false, |ch| {
17830 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17831 })
17832}
17833
17834#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17835enum WordBreakToken<'a> {
17836 Word { token: &'a str, grapheme_len: usize },
17837 InlineWhitespace { token: &'a str, grapheme_len: usize },
17838 Newline,
17839}
17840
17841impl<'a> Iterator for WordBreakingTokenizer<'a> {
17842 /// Yields a span, the count of graphemes in the token, and whether it was
17843 /// whitespace. Note that it also breaks at word boundaries.
17844 type Item = WordBreakToken<'a>;
17845
17846 fn next(&mut self) -> Option<Self::Item> {
17847 use unicode_segmentation::UnicodeSegmentation;
17848 if self.input.is_empty() {
17849 return None;
17850 }
17851
17852 let mut iter = self.input.graphemes(true).peekable();
17853 let mut offset = 0;
17854 let mut grapheme_len = 0;
17855 if let Some(first_grapheme) = iter.next() {
17856 let is_newline = first_grapheme == "\n";
17857 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17858 offset += first_grapheme.len();
17859 grapheme_len += 1;
17860 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17861 if let Some(grapheme) = iter.peek().copied() {
17862 if should_stay_with_preceding_ideograph(grapheme) {
17863 offset += grapheme.len();
17864 grapheme_len += 1;
17865 }
17866 }
17867 } else {
17868 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17869 let mut next_word_bound = words.peek().copied();
17870 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17871 next_word_bound = words.next();
17872 }
17873 while let Some(grapheme) = iter.peek().copied() {
17874 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17875 break;
17876 };
17877 if is_grapheme_whitespace(grapheme) != is_whitespace
17878 || (grapheme == "\n") != is_newline
17879 {
17880 break;
17881 };
17882 offset += grapheme.len();
17883 grapheme_len += 1;
17884 iter.next();
17885 }
17886 }
17887 let token = &self.input[..offset];
17888 self.input = &self.input[offset..];
17889 if token == "\n" {
17890 Some(WordBreakToken::Newline)
17891 } else if is_whitespace {
17892 Some(WordBreakToken::InlineWhitespace {
17893 token,
17894 grapheme_len,
17895 })
17896 } else {
17897 Some(WordBreakToken::Word {
17898 token,
17899 grapheme_len,
17900 })
17901 }
17902 } else {
17903 None
17904 }
17905 }
17906}
17907
17908#[test]
17909fn test_word_breaking_tokenizer() {
17910 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17911 ("", &[]),
17912 (" ", &[whitespace(" ", 2)]),
17913 ("Ʒ", &[word("Ʒ", 1)]),
17914 ("Ǽ", &[word("Ǽ", 1)]),
17915 ("⋑", &[word("⋑", 1)]),
17916 ("⋑⋑", &[word("⋑⋑", 2)]),
17917 (
17918 "原理,进而",
17919 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17920 ),
17921 (
17922 "hello world",
17923 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17924 ),
17925 (
17926 "hello, world",
17927 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17928 ),
17929 (
17930 " hello world",
17931 &[
17932 whitespace(" ", 2),
17933 word("hello", 5),
17934 whitespace(" ", 1),
17935 word("world", 5),
17936 ],
17937 ),
17938 (
17939 "这是什么 \n 钢笔",
17940 &[
17941 word("这", 1),
17942 word("是", 1),
17943 word("什", 1),
17944 word("么", 1),
17945 whitespace(" ", 1),
17946 newline(),
17947 whitespace(" ", 1),
17948 word("钢", 1),
17949 word("笔", 1),
17950 ],
17951 ),
17952 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17953 ];
17954
17955 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17956 WordBreakToken::Word {
17957 token,
17958 grapheme_len,
17959 }
17960 }
17961
17962 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17963 WordBreakToken::InlineWhitespace {
17964 token,
17965 grapheme_len,
17966 }
17967 }
17968
17969 fn newline() -> WordBreakToken<'static> {
17970 WordBreakToken::Newline
17971 }
17972
17973 for (input, result) in tests {
17974 assert_eq!(
17975 WordBreakingTokenizer::new(input)
17976 .collect::<Vec<_>>()
17977 .as_slice(),
17978 *result,
17979 );
17980 }
17981}
17982
17983fn wrap_with_prefix(
17984 line_prefix: String,
17985 unwrapped_text: String,
17986 wrap_column: usize,
17987 tab_size: NonZeroU32,
17988 preserve_existing_whitespace: bool,
17989) -> String {
17990 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
17991 let mut wrapped_text = String::new();
17992 let mut current_line = line_prefix.clone();
17993
17994 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
17995 let mut current_line_len = line_prefix_len;
17996 let mut in_whitespace = false;
17997 for token in tokenizer {
17998 let have_preceding_whitespace = in_whitespace;
17999 match token {
18000 WordBreakToken::Word {
18001 token,
18002 grapheme_len,
18003 } => {
18004 in_whitespace = false;
18005 if current_line_len + grapheme_len > wrap_column
18006 && current_line_len != line_prefix_len
18007 {
18008 wrapped_text.push_str(current_line.trim_end());
18009 wrapped_text.push('\n');
18010 current_line.truncate(line_prefix.len());
18011 current_line_len = line_prefix_len;
18012 }
18013 current_line.push_str(token);
18014 current_line_len += grapheme_len;
18015 }
18016 WordBreakToken::InlineWhitespace {
18017 mut token,
18018 mut grapheme_len,
18019 } => {
18020 in_whitespace = true;
18021 if have_preceding_whitespace && !preserve_existing_whitespace {
18022 continue;
18023 }
18024 if !preserve_existing_whitespace {
18025 token = " ";
18026 grapheme_len = 1;
18027 }
18028 if current_line_len + grapheme_len > wrap_column {
18029 wrapped_text.push_str(current_line.trim_end());
18030 wrapped_text.push('\n');
18031 current_line.truncate(line_prefix.len());
18032 current_line_len = line_prefix_len;
18033 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18034 current_line.push_str(token);
18035 current_line_len += grapheme_len;
18036 }
18037 }
18038 WordBreakToken::Newline => {
18039 in_whitespace = true;
18040 if preserve_existing_whitespace {
18041 wrapped_text.push_str(current_line.trim_end());
18042 wrapped_text.push('\n');
18043 current_line.truncate(line_prefix.len());
18044 current_line_len = line_prefix_len;
18045 } else if have_preceding_whitespace {
18046 continue;
18047 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18048 {
18049 wrapped_text.push_str(current_line.trim_end());
18050 wrapped_text.push('\n');
18051 current_line.truncate(line_prefix.len());
18052 current_line_len = line_prefix_len;
18053 } else if current_line_len != line_prefix_len {
18054 current_line.push(' ');
18055 current_line_len += 1;
18056 }
18057 }
18058 }
18059 }
18060
18061 if !current_line.is_empty() {
18062 wrapped_text.push_str(¤t_line);
18063 }
18064 wrapped_text
18065}
18066
18067#[test]
18068fn test_wrap_with_prefix() {
18069 assert_eq!(
18070 wrap_with_prefix(
18071 "# ".to_string(),
18072 "abcdefg".to_string(),
18073 4,
18074 NonZeroU32::new(4).unwrap(),
18075 false,
18076 ),
18077 "# abcdefg"
18078 );
18079 assert_eq!(
18080 wrap_with_prefix(
18081 "".to_string(),
18082 "\thello world".to_string(),
18083 8,
18084 NonZeroU32::new(4).unwrap(),
18085 false,
18086 ),
18087 "hello\nworld"
18088 );
18089 assert_eq!(
18090 wrap_with_prefix(
18091 "// ".to_string(),
18092 "xx \nyy zz aa bb cc".to_string(),
18093 12,
18094 NonZeroU32::new(4).unwrap(),
18095 false,
18096 ),
18097 "// xx yy zz\n// aa bb cc"
18098 );
18099 assert_eq!(
18100 wrap_with_prefix(
18101 String::new(),
18102 "这是什么 \n 钢笔".to_string(),
18103 3,
18104 NonZeroU32::new(4).unwrap(),
18105 false,
18106 ),
18107 "这是什\n么 钢\n笔"
18108 );
18109}
18110
18111pub trait CollaborationHub {
18112 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18113 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18114 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18115}
18116
18117impl CollaborationHub for Entity<Project> {
18118 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18119 self.read(cx).collaborators()
18120 }
18121
18122 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18123 self.read(cx).user_store().read(cx).participant_indices()
18124 }
18125
18126 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18127 let this = self.read(cx);
18128 let user_ids = this.collaborators().values().map(|c| c.user_id);
18129 this.user_store().read_with(cx, |user_store, cx| {
18130 user_store.participant_names(user_ids, cx)
18131 })
18132 }
18133}
18134
18135pub trait SemanticsProvider {
18136 fn hover(
18137 &self,
18138 buffer: &Entity<Buffer>,
18139 position: text::Anchor,
18140 cx: &mut App,
18141 ) -> Option<Task<Vec<project::Hover>>>;
18142
18143 fn inlay_hints(
18144 &self,
18145 buffer_handle: Entity<Buffer>,
18146 range: Range<text::Anchor>,
18147 cx: &mut App,
18148 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18149
18150 fn resolve_inlay_hint(
18151 &self,
18152 hint: InlayHint,
18153 buffer_handle: Entity<Buffer>,
18154 server_id: LanguageServerId,
18155 cx: &mut App,
18156 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18157
18158 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18159
18160 fn document_highlights(
18161 &self,
18162 buffer: &Entity<Buffer>,
18163 position: text::Anchor,
18164 cx: &mut App,
18165 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18166
18167 fn definitions(
18168 &self,
18169 buffer: &Entity<Buffer>,
18170 position: text::Anchor,
18171 kind: GotoDefinitionKind,
18172 cx: &mut App,
18173 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18174
18175 fn range_for_rename(
18176 &self,
18177 buffer: &Entity<Buffer>,
18178 position: text::Anchor,
18179 cx: &mut App,
18180 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18181
18182 fn perform_rename(
18183 &self,
18184 buffer: &Entity<Buffer>,
18185 position: text::Anchor,
18186 new_name: String,
18187 cx: &mut App,
18188 ) -> Option<Task<Result<ProjectTransaction>>>;
18189}
18190
18191pub trait CompletionProvider {
18192 fn completions(
18193 &self,
18194 excerpt_id: ExcerptId,
18195 buffer: &Entity<Buffer>,
18196 buffer_position: text::Anchor,
18197 trigger: CompletionContext,
18198 window: &mut Window,
18199 cx: &mut Context<Editor>,
18200 ) -> Task<Result<Option<Vec<Completion>>>>;
18201
18202 fn resolve_completions(
18203 &self,
18204 buffer: Entity<Buffer>,
18205 completion_indices: Vec<usize>,
18206 completions: Rc<RefCell<Box<[Completion]>>>,
18207 cx: &mut Context<Editor>,
18208 ) -> Task<Result<bool>>;
18209
18210 fn apply_additional_edits_for_completion(
18211 &self,
18212 _buffer: Entity<Buffer>,
18213 _completions: Rc<RefCell<Box<[Completion]>>>,
18214 _completion_index: usize,
18215 _push_to_history: bool,
18216 _cx: &mut Context<Editor>,
18217 ) -> Task<Result<Option<language::Transaction>>> {
18218 Task::ready(Ok(None))
18219 }
18220
18221 fn is_completion_trigger(
18222 &self,
18223 buffer: &Entity<Buffer>,
18224 position: language::Anchor,
18225 text: &str,
18226 trigger_in_words: bool,
18227 cx: &mut Context<Editor>,
18228 ) -> bool;
18229
18230 fn sort_completions(&self) -> bool {
18231 true
18232 }
18233
18234 fn filter_completions(&self) -> bool {
18235 true
18236 }
18237}
18238
18239pub trait CodeActionProvider {
18240 fn id(&self) -> Arc<str>;
18241
18242 fn code_actions(
18243 &self,
18244 buffer: &Entity<Buffer>,
18245 range: Range<text::Anchor>,
18246 window: &mut Window,
18247 cx: &mut App,
18248 ) -> Task<Result<Vec<CodeAction>>>;
18249
18250 fn apply_code_action(
18251 &self,
18252 buffer_handle: Entity<Buffer>,
18253 action: CodeAction,
18254 excerpt_id: ExcerptId,
18255 push_to_history: bool,
18256 window: &mut Window,
18257 cx: &mut App,
18258 ) -> Task<Result<ProjectTransaction>>;
18259}
18260
18261impl CodeActionProvider for Entity<Project> {
18262 fn id(&self) -> Arc<str> {
18263 "project".into()
18264 }
18265
18266 fn code_actions(
18267 &self,
18268 buffer: &Entity<Buffer>,
18269 range: Range<text::Anchor>,
18270 _window: &mut Window,
18271 cx: &mut App,
18272 ) -> Task<Result<Vec<CodeAction>>> {
18273 self.update(cx, |project, cx| {
18274 let code_lens = project.code_lens(buffer, range.clone(), cx);
18275 let code_actions = project.code_actions(buffer, range, None, cx);
18276 cx.background_spawn(async move {
18277 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18278 Ok(code_lens
18279 .context("code lens fetch")?
18280 .into_iter()
18281 .chain(code_actions.context("code action fetch")?)
18282 .collect())
18283 })
18284 })
18285 }
18286
18287 fn apply_code_action(
18288 &self,
18289 buffer_handle: Entity<Buffer>,
18290 action: CodeAction,
18291 _excerpt_id: ExcerptId,
18292 push_to_history: bool,
18293 _window: &mut Window,
18294 cx: &mut App,
18295 ) -> Task<Result<ProjectTransaction>> {
18296 self.update(cx, |project, cx| {
18297 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18298 })
18299 }
18300}
18301
18302fn snippet_completions(
18303 project: &Project,
18304 buffer: &Entity<Buffer>,
18305 buffer_position: text::Anchor,
18306 cx: &mut App,
18307) -> Task<Result<Vec<Completion>>> {
18308 let language = buffer.read(cx).language_at(buffer_position);
18309 let language_name = language.as_ref().map(|language| language.lsp_id());
18310 let snippet_store = project.snippets().read(cx);
18311 let snippets = snippet_store.snippets_for(language_name, cx);
18312
18313 if snippets.is_empty() {
18314 return Task::ready(Ok(vec![]));
18315 }
18316 let snapshot = buffer.read(cx).text_snapshot();
18317 let chars: String = snapshot
18318 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18319 .collect();
18320
18321 let scope = language.map(|language| language.default_scope());
18322 let executor = cx.background_executor().clone();
18323
18324 cx.background_spawn(async move {
18325 let classifier = CharClassifier::new(scope).for_completion(true);
18326 let mut last_word = chars
18327 .chars()
18328 .take_while(|c| classifier.is_word(*c))
18329 .collect::<String>();
18330 last_word = last_word.chars().rev().collect();
18331
18332 if last_word.is_empty() {
18333 return Ok(vec![]);
18334 }
18335
18336 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18337 let to_lsp = |point: &text::Anchor| {
18338 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18339 point_to_lsp(end)
18340 };
18341 let lsp_end = to_lsp(&buffer_position);
18342
18343 let candidates = snippets
18344 .iter()
18345 .enumerate()
18346 .flat_map(|(ix, snippet)| {
18347 snippet
18348 .prefix
18349 .iter()
18350 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18351 })
18352 .collect::<Vec<StringMatchCandidate>>();
18353
18354 let mut matches = fuzzy::match_strings(
18355 &candidates,
18356 &last_word,
18357 last_word.chars().any(|c| c.is_uppercase()),
18358 100,
18359 &Default::default(),
18360 executor,
18361 )
18362 .await;
18363
18364 // Remove all candidates where the query's start does not match the start of any word in the candidate
18365 if let Some(query_start) = last_word.chars().next() {
18366 matches.retain(|string_match| {
18367 split_words(&string_match.string).any(|word| {
18368 // Check that the first codepoint of the word as lowercase matches the first
18369 // codepoint of the query as lowercase
18370 word.chars()
18371 .flat_map(|codepoint| codepoint.to_lowercase())
18372 .zip(query_start.to_lowercase())
18373 .all(|(word_cp, query_cp)| word_cp == query_cp)
18374 })
18375 });
18376 }
18377
18378 let matched_strings = matches
18379 .into_iter()
18380 .map(|m| m.string)
18381 .collect::<HashSet<_>>();
18382
18383 let result: Vec<Completion> = snippets
18384 .into_iter()
18385 .filter_map(|snippet| {
18386 let matching_prefix = snippet
18387 .prefix
18388 .iter()
18389 .find(|prefix| matched_strings.contains(*prefix))?;
18390 let start = as_offset - last_word.len();
18391 let start = snapshot.anchor_before(start);
18392 let range = start..buffer_position;
18393 let lsp_start = to_lsp(&start);
18394 let lsp_range = lsp::Range {
18395 start: lsp_start,
18396 end: lsp_end,
18397 };
18398 Some(Completion {
18399 old_range: range,
18400 new_text: snippet.body.clone(),
18401 source: CompletionSource::Lsp {
18402 server_id: LanguageServerId(usize::MAX),
18403 resolved: true,
18404 lsp_completion: Box::new(lsp::CompletionItem {
18405 label: snippet.prefix.first().unwrap().clone(),
18406 kind: Some(CompletionItemKind::SNIPPET),
18407 label_details: snippet.description.as_ref().map(|description| {
18408 lsp::CompletionItemLabelDetails {
18409 detail: Some(description.clone()),
18410 description: None,
18411 }
18412 }),
18413 insert_text_format: Some(InsertTextFormat::SNIPPET),
18414 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18415 lsp::InsertReplaceEdit {
18416 new_text: snippet.body.clone(),
18417 insert: lsp_range,
18418 replace: lsp_range,
18419 },
18420 )),
18421 filter_text: Some(snippet.body.clone()),
18422 sort_text: Some(char::MAX.to_string()),
18423 ..lsp::CompletionItem::default()
18424 }),
18425 lsp_defaults: None,
18426 },
18427 label: CodeLabel {
18428 text: matching_prefix.clone(),
18429 runs: Vec::new(),
18430 filter_range: 0..matching_prefix.len(),
18431 },
18432 icon_path: None,
18433 documentation: snippet
18434 .description
18435 .clone()
18436 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18437 confirm: None,
18438 })
18439 })
18440 .collect();
18441
18442 Ok(result)
18443 })
18444}
18445
18446impl CompletionProvider for Entity<Project> {
18447 fn completions(
18448 &self,
18449 _excerpt_id: ExcerptId,
18450 buffer: &Entity<Buffer>,
18451 buffer_position: text::Anchor,
18452 options: CompletionContext,
18453 _window: &mut Window,
18454 cx: &mut Context<Editor>,
18455 ) -> Task<Result<Option<Vec<Completion>>>> {
18456 self.update(cx, |project, cx| {
18457 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18458 let project_completions = project.completions(buffer, buffer_position, options, cx);
18459 cx.background_spawn(async move {
18460 let snippets_completions = snippets.await?;
18461 match project_completions.await? {
18462 Some(mut completions) => {
18463 completions.extend(snippets_completions);
18464 Ok(Some(completions))
18465 }
18466 None => {
18467 if snippets_completions.is_empty() {
18468 Ok(None)
18469 } else {
18470 Ok(Some(snippets_completions))
18471 }
18472 }
18473 }
18474 })
18475 })
18476 }
18477
18478 fn resolve_completions(
18479 &self,
18480 buffer: Entity<Buffer>,
18481 completion_indices: Vec<usize>,
18482 completions: Rc<RefCell<Box<[Completion]>>>,
18483 cx: &mut Context<Editor>,
18484 ) -> Task<Result<bool>> {
18485 self.update(cx, |project, cx| {
18486 project.lsp_store().update(cx, |lsp_store, cx| {
18487 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18488 })
18489 })
18490 }
18491
18492 fn apply_additional_edits_for_completion(
18493 &self,
18494 buffer: Entity<Buffer>,
18495 completions: Rc<RefCell<Box<[Completion]>>>,
18496 completion_index: usize,
18497 push_to_history: bool,
18498 cx: &mut Context<Editor>,
18499 ) -> Task<Result<Option<language::Transaction>>> {
18500 self.update(cx, |project, cx| {
18501 project.lsp_store().update(cx, |lsp_store, cx| {
18502 lsp_store.apply_additional_edits_for_completion(
18503 buffer,
18504 completions,
18505 completion_index,
18506 push_to_history,
18507 cx,
18508 )
18509 })
18510 })
18511 }
18512
18513 fn is_completion_trigger(
18514 &self,
18515 buffer: &Entity<Buffer>,
18516 position: language::Anchor,
18517 text: &str,
18518 trigger_in_words: bool,
18519 cx: &mut Context<Editor>,
18520 ) -> bool {
18521 let mut chars = text.chars();
18522 let char = if let Some(char) = chars.next() {
18523 char
18524 } else {
18525 return false;
18526 };
18527 if chars.next().is_some() {
18528 return false;
18529 }
18530
18531 let buffer = buffer.read(cx);
18532 let snapshot = buffer.snapshot();
18533 if !snapshot.settings_at(position, cx).show_completions_on_input {
18534 return false;
18535 }
18536 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18537 if trigger_in_words && classifier.is_word(char) {
18538 return true;
18539 }
18540
18541 buffer.completion_triggers().contains(text)
18542 }
18543}
18544
18545impl SemanticsProvider for Entity<Project> {
18546 fn hover(
18547 &self,
18548 buffer: &Entity<Buffer>,
18549 position: text::Anchor,
18550 cx: &mut App,
18551 ) -> Option<Task<Vec<project::Hover>>> {
18552 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18553 }
18554
18555 fn document_highlights(
18556 &self,
18557 buffer: &Entity<Buffer>,
18558 position: text::Anchor,
18559 cx: &mut App,
18560 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18561 Some(self.update(cx, |project, cx| {
18562 project.document_highlights(buffer, position, cx)
18563 }))
18564 }
18565
18566 fn definitions(
18567 &self,
18568 buffer: &Entity<Buffer>,
18569 position: text::Anchor,
18570 kind: GotoDefinitionKind,
18571 cx: &mut App,
18572 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18573 Some(self.update(cx, |project, cx| match kind {
18574 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18575 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18576 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18577 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18578 }))
18579 }
18580
18581 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18582 // TODO: make this work for remote projects
18583 self.update(cx, |this, cx| {
18584 buffer.update(cx, |buffer, cx| {
18585 this.any_language_server_supports_inlay_hints(buffer, cx)
18586 })
18587 })
18588 }
18589
18590 fn inlay_hints(
18591 &self,
18592 buffer_handle: Entity<Buffer>,
18593 range: Range<text::Anchor>,
18594 cx: &mut App,
18595 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18596 Some(self.update(cx, |project, cx| {
18597 project.inlay_hints(buffer_handle, range, cx)
18598 }))
18599 }
18600
18601 fn resolve_inlay_hint(
18602 &self,
18603 hint: InlayHint,
18604 buffer_handle: Entity<Buffer>,
18605 server_id: LanguageServerId,
18606 cx: &mut App,
18607 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18608 Some(self.update(cx, |project, cx| {
18609 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18610 }))
18611 }
18612
18613 fn range_for_rename(
18614 &self,
18615 buffer: &Entity<Buffer>,
18616 position: text::Anchor,
18617 cx: &mut App,
18618 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18619 Some(self.update(cx, |project, cx| {
18620 let buffer = buffer.clone();
18621 let task = project.prepare_rename(buffer.clone(), position, cx);
18622 cx.spawn(async move |_, cx| {
18623 Ok(match task.await? {
18624 PrepareRenameResponse::Success(range) => Some(range),
18625 PrepareRenameResponse::InvalidPosition => None,
18626 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18627 // Fallback on using TreeSitter info to determine identifier range
18628 buffer.update(cx, |buffer, _| {
18629 let snapshot = buffer.snapshot();
18630 let (range, kind) = snapshot.surrounding_word(position);
18631 if kind != Some(CharKind::Word) {
18632 return None;
18633 }
18634 Some(
18635 snapshot.anchor_before(range.start)
18636 ..snapshot.anchor_after(range.end),
18637 )
18638 })?
18639 }
18640 })
18641 })
18642 }))
18643 }
18644
18645 fn perform_rename(
18646 &self,
18647 buffer: &Entity<Buffer>,
18648 position: text::Anchor,
18649 new_name: String,
18650 cx: &mut App,
18651 ) -> Option<Task<Result<ProjectTransaction>>> {
18652 Some(self.update(cx, |project, cx| {
18653 project.perform_rename(buffer.clone(), position, new_name, cx)
18654 }))
18655 }
18656}
18657
18658fn inlay_hint_settings(
18659 location: Anchor,
18660 snapshot: &MultiBufferSnapshot,
18661 cx: &mut Context<Editor>,
18662) -> InlayHintSettings {
18663 let file = snapshot.file_at(location);
18664 let language = snapshot.language_at(location).map(|l| l.name());
18665 language_settings(language, file, cx).inlay_hints
18666}
18667
18668fn consume_contiguous_rows(
18669 contiguous_row_selections: &mut Vec<Selection<Point>>,
18670 selection: &Selection<Point>,
18671 display_map: &DisplaySnapshot,
18672 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18673) -> (MultiBufferRow, MultiBufferRow) {
18674 contiguous_row_selections.push(selection.clone());
18675 let start_row = MultiBufferRow(selection.start.row);
18676 let mut end_row = ending_row(selection, display_map);
18677
18678 while let Some(next_selection) = selections.peek() {
18679 if next_selection.start.row <= end_row.0 {
18680 end_row = ending_row(next_selection, display_map);
18681 contiguous_row_selections.push(selections.next().unwrap().clone());
18682 } else {
18683 break;
18684 }
18685 }
18686 (start_row, end_row)
18687}
18688
18689fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18690 if next_selection.end.column > 0 || next_selection.is_empty() {
18691 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18692 } else {
18693 MultiBufferRow(next_selection.end.row)
18694 }
18695}
18696
18697impl EditorSnapshot {
18698 pub fn remote_selections_in_range<'a>(
18699 &'a self,
18700 range: &'a Range<Anchor>,
18701 collaboration_hub: &dyn CollaborationHub,
18702 cx: &'a App,
18703 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18704 let participant_names = collaboration_hub.user_names(cx);
18705 let participant_indices = collaboration_hub.user_participant_indices(cx);
18706 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18707 let collaborators_by_replica_id = collaborators_by_peer_id
18708 .iter()
18709 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18710 .collect::<HashMap<_, _>>();
18711 self.buffer_snapshot
18712 .selections_in_range(range, false)
18713 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18714 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18715 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18716 let user_name = participant_names.get(&collaborator.user_id).cloned();
18717 Some(RemoteSelection {
18718 replica_id,
18719 selection,
18720 cursor_shape,
18721 line_mode,
18722 participant_index,
18723 peer_id: collaborator.peer_id,
18724 user_name,
18725 })
18726 })
18727 }
18728
18729 pub fn hunks_for_ranges(
18730 &self,
18731 ranges: impl IntoIterator<Item = Range<Point>>,
18732 ) -> Vec<MultiBufferDiffHunk> {
18733 let mut hunks = Vec::new();
18734 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18735 HashMap::default();
18736 for query_range in ranges {
18737 let query_rows =
18738 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18739 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18740 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18741 ) {
18742 // Include deleted hunks that are adjacent to the query range, because
18743 // otherwise they would be missed.
18744 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18745 if hunk.status().is_deleted() {
18746 intersects_range |= hunk.row_range.start == query_rows.end;
18747 intersects_range |= hunk.row_range.end == query_rows.start;
18748 }
18749 if intersects_range {
18750 if !processed_buffer_rows
18751 .entry(hunk.buffer_id)
18752 .or_default()
18753 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18754 {
18755 continue;
18756 }
18757 hunks.push(hunk);
18758 }
18759 }
18760 }
18761
18762 hunks
18763 }
18764
18765 fn display_diff_hunks_for_rows<'a>(
18766 &'a self,
18767 display_rows: Range<DisplayRow>,
18768 folded_buffers: &'a HashSet<BufferId>,
18769 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18770 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18771 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18772
18773 self.buffer_snapshot
18774 .diff_hunks_in_range(buffer_start..buffer_end)
18775 .filter_map(|hunk| {
18776 if folded_buffers.contains(&hunk.buffer_id) {
18777 return None;
18778 }
18779
18780 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18781 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18782
18783 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18784 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18785
18786 let display_hunk = if hunk_display_start.column() != 0 {
18787 DisplayDiffHunk::Folded {
18788 display_row: hunk_display_start.row(),
18789 }
18790 } else {
18791 let mut end_row = hunk_display_end.row();
18792 if hunk_display_end.column() > 0 {
18793 end_row.0 += 1;
18794 }
18795 let is_created_file = hunk.is_created_file();
18796 DisplayDiffHunk::Unfolded {
18797 status: hunk.status(),
18798 diff_base_byte_range: hunk.diff_base_byte_range,
18799 display_row_range: hunk_display_start.row()..end_row,
18800 multi_buffer_range: Anchor::range_in_buffer(
18801 hunk.excerpt_id,
18802 hunk.buffer_id,
18803 hunk.buffer_range,
18804 ),
18805 is_created_file,
18806 }
18807 };
18808
18809 Some(display_hunk)
18810 })
18811 }
18812
18813 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18814 self.display_snapshot.buffer_snapshot.language_at(position)
18815 }
18816
18817 pub fn is_focused(&self) -> bool {
18818 self.is_focused
18819 }
18820
18821 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18822 self.placeholder_text.as_ref()
18823 }
18824
18825 pub fn scroll_position(&self) -> gpui::Point<f32> {
18826 self.scroll_anchor.scroll_position(&self.display_snapshot)
18827 }
18828
18829 fn gutter_dimensions(
18830 &self,
18831 font_id: FontId,
18832 font_size: Pixels,
18833 max_line_number_width: Pixels,
18834 cx: &App,
18835 ) -> Option<GutterDimensions> {
18836 if !self.show_gutter {
18837 return None;
18838 }
18839
18840 let descent = cx.text_system().descent(font_id, font_size);
18841 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18842 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18843
18844 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18845 matches!(
18846 ProjectSettings::get_global(cx).git.git_gutter,
18847 Some(GitGutterSetting::TrackedFiles)
18848 )
18849 });
18850 let gutter_settings = EditorSettings::get_global(cx).gutter;
18851 let show_line_numbers = self
18852 .show_line_numbers
18853 .unwrap_or(gutter_settings.line_numbers);
18854 let line_gutter_width = if show_line_numbers {
18855 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18856 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18857 max_line_number_width.max(min_width_for_number_on_gutter)
18858 } else {
18859 0.0.into()
18860 };
18861
18862 let show_code_actions = self
18863 .show_code_actions
18864 .unwrap_or(gutter_settings.code_actions);
18865
18866 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18867 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18868
18869 let git_blame_entries_width =
18870 self.git_blame_gutter_max_author_length
18871 .map(|max_author_length| {
18872 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18873
18874 /// The number of characters to dedicate to gaps and margins.
18875 const SPACING_WIDTH: usize = 4;
18876
18877 let max_char_count = max_author_length
18878 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18879 + ::git::SHORT_SHA_LENGTH
18880 + MAX_RELATIVE_TIMESTAMP.len()
18881 + SPACING_WIDTH;
18882
18883 em_advance * max_char_count
18884 });
18885
18886 let is_singleton = self.buffer_snapshot.is_singleton();
18887
18888 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18889 left_padding += if !is_singleton {
18890 em_width * 4.0
18891 } else if show_code_actions || show_runnables || show_breakpoints {
18892 em_width * 3.0
18893 } else if show_git_gutter && show_line_numbers {
18894 em_width * 2.0
18895 } else if show_git_gutter || show_line_numbers {
18896 em_width
18897 } else {
18898 px(0.)
18899 };
18900
18901 let shows_folds = is_singleton && gutter_settings.folds;
18902
18903 let right_padding = if shows_folds && show_line_numbers {
18904 em_width * 4.0
18905 } else if shows_folds || (!is_singleton && show_line_numbers) {
18906 em_width * 3.0
18907 } else if show_line_numbers {
18908 em_width
18909 } else {
18910 px(0.)
18911 };
18912
18913 Some(GutterDimensions {
18914 left_padding,
18915 right_padding,
18916 width: line_gutter_width + left_padding + right_padding,
18917 margin: -descent,
18918 git_blame_entries_width,
18919 })
18920 }
18921
18922 pub fn render_crease_toggle(
18923 &self,
18924 buffer_row: MultiBufferRow,
18925 row_contains_cursor: bool,
18926 editor: Entity<Editor>,
18927 window: &mut Window,
18928 cx: &mut App,
18929 ) -> Option<AnyElement> {
18930 let folded = self.is_line_folded(buffer_row);
18931 let mut is_foldable = false;
18932
18933 if let Some(crease) = self
18934 .crease_snapshot
18935 .query_row(buffer_row, &self.buffer_snapshot)
18936 {
18937 is_foldable = true;
18938 match crease {
18939 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18940 if let Some(render_toggle) = render_toggle {
18941 let toggle_callback =
18942 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18943 if folded {
18944 editor.update(cx, |editor, cx| {
18945 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18946 });
18947 } else {
18948 editor.update(cx, |editor, cx| {
18949 editor.unfold_at(
18950 &crate::UnfoldAt { buffer_row },
18951 window,
18952 cx,
18953 )
18954 });
18955 }
18956 });
18957 return Some((render_toggle)(
18958 buffer_row,
18959 folded,
18960 toggle_callback,
18961 window,
18962 cx,
18963 ));
18964 }
18965 }
18966 }
18967 }
18968
18969 is_foldable |= self.starts_indent(buffer_row);
18970
18971 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
18972 Some(
18973 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
18974 .toggle_state(folded)
18975 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
18976 if folded {
18977 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
18978 } else {
18979 this.fold_at(&FoldAt { buffer_row }, window, cx);
18980 }
18981 }))
18982 .into_any_element(),
18983 )
18984 } else {
18985 None
18986 }
18987 }
18988
18989 pub fn render_crease_trailer(
18990 &self,
18991 buffer_row: MultiBufferRow,
18992 window: &mut Window,
18993 cx: &mut App,
18994 ) -> Option<AnyElement> {
18995 let folded = self.is_line_folded(buffer_row);
18996 if let Crease::Inline { render_trailer, .. } = self
18997 .crease_snapshot
18998 .query_row(buffer_row, &self.buffer_snapshot)?
18999 {
19000 let render_trailer = render_trailer.as_ref()?;
19001 Some(render_trailer(buffer_row, folded, window, cx))
19002 } else {
19003 None
19004 }
19005 }
19006}
19007
19008impl Deref for EditorSnapshot {
19009 type Target = DisplaySnapshot;
19010
19011 fn deref(&self) -> &Self::Target {
19012 &self.display_snapshot
19013 }
19014}
19015
19016#[derive(Clone, Debug, PartialEq, Eq)]
19017pub enum EditorEvent {
19018 InputIgnored {
19019 text: Arc<str>,
19020 },
19021 InputHandled {
19022 utf16_range_to_replace: Option<Range<isize>>,
19023 text: Arc<str>,
19024 },
19025 ExcerptsAdded {
19026 buffer: Entity<Buffer>,
19027 predecessor: ExcerptId,
19028 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19029 },
19030 ExcerptsRemoved {
19031 ids: Vec<ExcerptId>,
19032 },
19033 BufferFoldToggled {
19034 ids: Vec<ExcerptId>,
19035 folded: bool,
19036 },
19037 ExcerptsEdited {
19038 ids: Vec<ExcerptId>,
19039 },
19040 ExcerptsExpanded {
19041 ids: Vec<ExcerptId>,
19042 },
19043 BufferEdited,
19044 Edited {
19045 transaction_id: clock::Lamport,
19046 },
19047 Reparsed(BufferId),
19048 Focused,
19049 FocusedIn,
19050 Blurred,
19051 DirtyChanged,
19052 Saved,
19053 TitleChanged,
19054 DiffBaseChanged,
19055 SelectionsChanged {
19056 local: bool,
19057 },
19058 ScrollPositionChanged {
19059 local: bool,
19060 autoscroll: bool,
19061 },
19062 Closed,
19063 TransactionUndone {
19064 transaction_id: clock::Lamport,
19065 },
19066 TransactionBegun {
19067 transaction_id: clock::Lamport,
19068 },
19069 Reloaded,
19070 CursorShapeChanged,
19071 PushedToNavHistory {
19072 anchor: Anchor,
19073 is_deactivate: bool,
19074 },
19075}
19076
19077impl EventEmitter<EditorEvent> for Editor {}
19078
19079impl Focusable for Editor {
19080 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19081 self.focus_handle.clone()
19082 }
19083}
19084
19085impl Render for Editor {
19086 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19087 let settings = ThemeSettings::get_global(cx);
19088
19089 let mut text_style = match self.mode {
19090 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19091 color: cx.theme().colors().editor_foreground,
19092 font_family: settings.ui_font.family.clone(),
19093 font_features: settings.ui_font.features.clone(),
19094 font_fallbacks: settings.ui_font.fallbacks.clone(),
19095 font_size: rems(0.875).into(),
19096 font_weight: settings.ui_font.weight,
19097 line_height: relative(settings.buffer_line_height.value()),
19098 ..Default::default()
19099 },
19100 EditorMode::Full => TextStyle {
19101 color: cx.theme().colors().editor_foreground,
19102 font_family: settings.buffer_font.family.clone(),
19103 font_features: settings.buffer_font.features.clone(),
19104 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19105 font_size: settings.buffer_font_size(cx).into(),
19106 font_weight: settings.buffer_font.weight,
19107 line_height: relative(settings.buffer_line_height.value()),
19108 ..Default::default()
19109 },
19110 };
19111 if let Some(text_style_refinement) = &self.text_style_refinement {
19112 text_style.refine(text_style_refinement)
19113 }
19114
19115 let background = match self.mode {
19116 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19117 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19118 EditorMode::Full => cx.theme().colors().editor_background,
19119 };
19120
19121 EditorElement::new(
19122 &cx.entity(),
19123 EditorStyle {
19124 background,
19125 local_player: cx.theme().players().local(),
19126 text: text_style,
19127 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19128 syntax: cx.theme().syntax().clone(),
19129 status: cx.theme().status().clone(),
19130 inlay_hints_style: make_inlay_hints_style(cx),
19131 inline_completion_styles: make_suggestion_styles(cx),
19132 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19133 },
19134 )
19135 }
19136}
19137
19138impl EntityInputHandler for Editor {
19139 fn text_for_range(
19140 &mut self,
19141 range_utf16: Range<usize>,
19142 adjusted_range: &mut Option<Range<usize>>,
19143 _: &mut Window,
19144 cx: &mut Context<Self>,
19145 ) -> Option<String> {
19146 let snapshot = self.buffer.read(cx).read(cx);
19147 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19148 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19149 if (start.0..end.0) != range_utf16 {
19150 adjusted_range.replace(start.0..end.0);
19151 }
19152 Some(snapshot.text_for_range(start..end).collect())
19153 }
19154
19155 fn selected_text_range(
19156 &mut self,
19157 ignore_disabled_input: bool,
19158 _: &mut Window,
19159 cx: &mut Context<Self>,
19160 ) -> Option<UTF16Selection> {
19161 // Prevent the IME menu from appearing when holding down an alphabetic key
19162 // while input is disabled.
19163 if !ignore_disabled_input && !self.input_enabled {
19164 return None;
19165 }
19166
19167 let selection = self.selections.newest::<OffsetUtf16>(cx);
19168 let range = selection.range();
19169
19170 Some(UTF16Selection {
19171 range: range.start.0..range.end.0,
19172 reversed: selection.reversed,
19173 })
19174 }
19175
19176 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19177 let snapshot = self.buffer.read(cx).read(cx);
19178 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19179 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19180 }
19181
19182 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19183 self.clear_highlights::<InputComposition>(cx);
19184 self.ime_transaction.take();
19185 }
19186
19187 fn replace_text_in_range(
19188 &mut self,
19189 range_utf16: Option<Range<usize>>,
19190 text: &str,
19191 window: &mut Window,
19192 cx: &mut Context<Self>,
19193 ) {
19194 if !self.input_enabled {
19195 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19196 return;
19197 }
19198
19199 self.transact(window, cx, |this, window, cx| {
19200 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19201 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19202 Some(this.selection_replacement_ranges(range_utf16, cx))
19203 } else {
19204 this.marked_text_ranges(cx)
19205 };
19206
19207 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19208 let newest_selection_id = this.selections.newest_anchor().id;
19209 this.selections
19210 .all::<OffsetUtf16>(cx)
19211 .iter()
19212 .zip(ranges_to_replace.iter())
19213 .find_map(|(selection, range)| {
19214 if selection.id == newest_selection_id {
19215 Some(
19216 (range.start.0 as isize - selection.head().0 as isize)
19217 ..(range.end.0 as isize - selection.head().0 as isize),
19218 )
19219 } else {
19220 None
19221 }
19222 })
19223 });
19224
19225 cx.emit(EditorEvent::InputHandled {
19226 utf16_range_to_replace: range_to_replace,
19227 text: text.into(),
19228 });
19229
19230 if let Some(new_selected_ranges) = new_selected_ranges {
19231 this.change_selections(None, window, cx, |selections| {
19232 selections.select_ranges(new_selected_ranges)
19233 });
19234 this.backspace(&Default::default(), window, cx);
19235 }
19236
19237 this.handle_input(text, window, cx);
19238 });
19239
19240 if let Some(transaction) = self.ime_transaction {
19241 self.buffer.update(cx, |buffer, cx| {
19242 buffer.group_until_transaction(transaction, cx);
19243 });
19244 }
19245
19246 self.unmark_text(window, cx);
19247 }
19248
19249 fn replace_and_mark_text_in_range(
19250 &mut self,
19251 range_utf16: Option<Range<usize>>,
19252 text: &str,
19253 new_selected_range_utf16: Option<Range<usize>>,
19254 window: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) {
19257 if !self.input_enabled {
19258 return;
19259 }
19260
19261 let transaction = self.transact(window, cx, |this, window, cx| {
19262 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19263 let snapshot = this.buffer.read(cx).read(cx);
19264 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19265 for marked_range in &mut marked_ranges {
19266 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19267 marked_range.start.0 += relative_range_utf16.start;
19268 marked_range.start =
19269 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19270 marked_range.end =
19271 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19272 }
19273 }
19274 Some(marked_ranges)
19275 } else if let Some(range_utf16) = range_utf16 {
19276 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19277 Some(this.selection_replacement_ranges(range_utf16, cx))
19278 } else {
19279 None
19280 };
19281
19282 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19283 let newest_selection_id = this.selections.newest_anchor().id;
19284 this.selections
19285 .all::<OffsetUtf16>(cx)
19286 .iter()
19287 .zip(ranges_to_replace.iter())
19288 .find_map(|(selection, range)| {
19289 if selection.id == newest_selection_id {
19290 Some(
19291 (range.start.0 as isize - selection.head().0 as isize)
19292 ..(range.end.0 as isize - selection.head().0 as isize),
19293 )
19294 } else {
19295 None
19296 }
19297 })
19298 });
19299
19300 cx.emit(EditorEvent::InputHandled {
19301 utf16_range_to_replace: range_to_replace,
19302 text: text.into(),
19303 });
19304
19305 if let Some(ranges) = ranges_to_replace {
19306 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19307 }
19308
19309 let marked_ranges = {
19310 let snapshot = this.buffer.read(cx).read(cx);
19311 this.selections
19312 .disjoint_anchors()
19313 .iter()
19314 .map(|selection| {
19315 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19316 })
19317 .collect::<Vec<_>>()
19318 };
19319
19320 if text.is_empty() {
19321 this.unmark_text(window, cx);
19322 } else {
19323 this.highlight_text::<InputComposition>(
19324 marked_ranges.clone(),
19325 HighlightStyle {
19326 underline: Some(UnderlineStyle {
19327 thickness: px(1.),
19328 color: None,
19329 wavy: false,
19330 }),
19331 ..Default::default()
19332 },
19333 cx,
19334 );
19335 }
19336
19337 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19338 let use_autoclose = this.use_autoclose;
19339 let use_auto_surround = this.use_auto_surround;
19340 this.set_use_autoclose(false);
19341 this.set_use_auto_surround(false);
19342 this.handle_input(text, window, cx);
19343 this.set_use_autoclose(use_autoclose);
19344 this.set_use_auto_surround(use_auto_surround);
19345
19346 if let Some(new_selected_range) = new_selected_range_utf16 {
19347 let snapshot = this.buffer.read(cx).read(cx);
19348 let new_selected_ranges = marked_ranges
19349 .into_iter()
19350 .map(|marked_range| {
19351 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19352 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19353 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19354 snapshot.clip_offset_utf16(new_start, Bias::Left)
19355 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19356 })
19357 .collect::<Vec<_>>();
19358
19359 drop(snapshot);
19360 this.change_selections(None, window, cx, |selections| {
19361 selections.select_ranges(new_selected_ranges)
19362 });
19363 }
19364 });
19365
19366 self.ime_transaction = self.ime_transaction.or(transaction);
19367 if let Some(transaction) = self.ime_transaction {
19368 self.buffer.update(cx, |buffer, cx| {
19369 buffer.group_until_transaction(transaction, cx);
19370 });
19371 }
19372
19373 if self.text_highlights::<InputComposition>(cx).is_none() {
19374 self.ime_transaction.take();
19375 }
19376 }
19377
19378 fn bounds_for_range(
19379 &mut self,
19380 range_utf16: Range<usize>,
19381 element_bounds: gpui::Bounds<Pixels>,
19382 window: &mut Window,
19383 cx: &mut Context<Self>,
19384 ) -> Option<gpui::Bounds<Pixels>> {
19385 let text_layout_details = self.text_layout_details(window);
19386 let gpui::Size {
19387 width: em_width,
19388 height: line_height,
19389 } = self.character_size(window);
19390
19391 let snapshot = self.snapshot(window, cx);
19392 let scroll_position = snapshot.scroll_position();
19393 let scroll_left = scroll_position.x * em_width;
19394
19395 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19396 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19397 + self.gutter_dimensions.width
19398 + self.gutter_dimensions.margin;
19399 let y = line_height * (start.row().as_f32() - scroll_position.y);
19400
19401 Some(Bounds {
19402 origin: element_bounds.origin + point(x, y),
19403 size: size(em_width, line_height),
19404 })
19405 }
19406
19407 fn character_index_for_point(
19408 &mut self,
19409 point: gpui::Point<Pixels>,
19410 _window: &mut Window,
19411 _cx: &mut Context<Self>,
19412 ) -> Option<usize> {
19413 let position_map = self.last_position_map.as_ref()?;
19414 if !position_map.text_hitbox.contains(&point) {
19415 return None;
19416 }
19417 let display_point = position_map.point_for_position(point).previous_valid;
19418 let anchor = position_map
19419 .snapshot
19420 .display_point_to_anchor(display_point, Bias::Left);
19421 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19422 Some(utf16_offset.0)
19423 }
19424}
19425
19426trait SelectionExt {
19427 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19428 fn spanned_rows(
19429 &self,
19430 include_end_if_at_line_start: bool,
19431 map: &DisplaySnapshot,
19432 ) -> Range<MultiBufferRow>;
19433}
19434
19435impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19436 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19437 let start = self
19438 .start
19439 .to_point(&map.buffer_snapshot)
19440 .to_display_point(map);
19441 let end = self
19442 .end
19443 .to_point(&map.buffer_snapshot)
19444 .to_display_point(map);
19445 if self.reversed {
19446 end..start
19447 } else {
19448 start..end
19449 }
19450 }
19451
19452 fn spanned_rows(
19453 &self,
19454 include_end_if_at_line_start: bool,
19455 map: &DisplaySnapshot,
19456 ) -> Range<MultiBufferRow> {
19457 let start = self.start.to_point(&map.buffer_snapshot);
19458 let mut end = self.end.to_point(&map.buffer_snapshot);
19459 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19460 end.row -= 1;
19461 }
19462
19463 let buffer_start = map.prev_line_boundary(start).0;
19464 let buffer_end = map.next_line_boundary(end).0;
19465 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19466 }
19467}
19468
19469impl<T: InvalidationRegion> InvalidationStack<T> {
19470 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19471 where
19472 S: Clone + ToOffset,
19473 {
19474 while let Some(region) = self.last() {
19475 let all_selections_inside_invalidation_ranges =
19476 if selections.len() == region.ranges().len() {
19477 selections
19478 .iter()
19479 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19480 .all(|(selection, invalidation_range)| {
19481 let head = selection.head().to_offset(buffer);
19482 invalidation_range.start <= head && invalidation_range.end >= head
19483 })
19484 } else {
19485 false
19486 };
19487
19488 if all_selections_inside_invalidation_ranges {
19489 break;
19490 } else {
19491 self.pop();
19492 }
19493 }
19494 }
19495}
19496
19497impl<T> Default for InvalidationStack<T> {
19498 fn default() -> Self {
19499 Self(Default::default())
19500 }
19501}
19502
19503impl<T> Deref for InvalidationStack<T> {
19504 type Target = Vec<T>;
19505
19506 fn deref(&self) -> &Self::Target {
19507 &self.0
19508 }
19509}
19510
19511impl<T> DerefMut for InvalidationStack<T> {
19512 fn deref_mut(&mut self) -> &mut Self::Target {
19513 &mut self.0
19514 }
19515}
19516
19517impl InvalidationRegion for SnippetState {
19518 fn ranges(&self) -> &[Range<Anchor>] {
19519 &self.ranges[self.active_index]
19520 }
19521}
19522
19523pub fn diagnostic_block_renderer(
19524 diagnostic: Diagnostic,
19525 max_message_rows: Option<u8>,
19526 allow_closing: bool,
19527) -> RenderBlock {
19528 let (text_without_backticks, code_ranges) =
19529 highlight_diagnostic_message(&diagnostic, max_message_rows);
19530
19531 Arc::new(move |cx: &mut BlockContext| {
19532 let group_id: SharedString = cx.block_id.to_string().into();
19533
19534 let mut text_style = cx.window.text_style().clone();
19535 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19536 let theme_settings = ThemeSettings::get_global(cx);
19537 text_style.font_family = theme_settings.buffer_font.family.clone();
19538 text_style.font_style = theme_settings.buffer_font.style;
19539 text_style.font_features = theme_settings.buffer_font.features.clone();
19540 text_style.font_weight = theme_settings.buffer_font.weight;
19541
19542 let multi_line_diagnostic = diagnostic.message.contains('\n');
19543
19544 let buttons = |diagnostic: &Diagnostic| {
19545 if multi_line_diagnostic {
19546 v_flex()
19547 } else {
19548 h_flex()
19549 }
19550 .when(allow_closing, |div| {
19551 div.children(diagnostic.is_primary.then(|| {
19552 IconButton::new("close-block", IconName::XCircle)
19553 .icon_color(Color::Muted)
19554 .size(ButtonSize::Compact)
19555 .style(ButtonStyle::Transparent)
19556 .visible_on_hover(group_id.clone())
19557 .on_click(move |_click, window, cx| {
19558 window.dispatch_action(Box::new(Cancel), cx)
19559 })
19560 .tooltip(|window, cx| {
19561 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19562 })
19563 }))
19564 })
19565 .child(
19566 IconButton::new("copy-block", IconName::Copy)
19567 .icon_color(Color::Muted)
19568 .size(ButtonSize::Compact)
19569 .style(ButtonStyle::Transparent)
19570 .visible_on_hover(group_id.clone())
19571 .on_click({
19572 let message = diagnostic.message.clone();
19573 move |_click, _, cx| {
19574 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19575 }
19576 })
19577 .tooltip(Tooltip::text("Copy diagnostic message")),
19578 )
19579 };
19580
19581 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19582 AvailableSpace::min_size(),
19583 cx.window,
19584 cx.app,
19585 );
19586
19587 h_flex()
19588 .id(cx.block_id)
19589 .group(group_id.clone())
19590 .relative()
19591 .size_full()
19592 .block_mouse_down()
19593 .pl(cx.gutter_dimensions.width)
19594 .w(cx.max_width - cx.gutter_dimensions.full_width())
19595 .child(
19596 div()
19597 .flex()
19598 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19599 .flex_shrink(),
19600 )
19601 .child(buttons(&diagnostic))
19602 .child(div().flex().flex_shrink_0().child(
19603 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19604 &text_style,
19605 code_ranges.iter().map(|range| {
19606 (
19607 range.clone(),
19608 HighlightStyle {
19609 font_weight: Some(FontWeight::BOLD),
19610 ..Default::default()
19611 },
19612 )
19613 }),
19614 ),
19615 ))
19616 .into_any_element()
19617 })
19618}
19619
19620fn inline_completion_edit_text(
19621 current_snapshot: &BufferSnapshot,
19622 edits: &[(Range<Anchor>, String)],
19623 edit_preview: &EditPreview,
19624 include_deletions: bool,
19625 cx: &App,
19626) -> HighlightedText {
19627 let edits = edits
19628 .iter()
19629 .map(|(anchor, text)| {
19630 (
19631 anchor.start.text_anchor..anchor.end.text_anchor,
19632 text.clone(),
19633 )
19634 })
19635 .collect::<Vec<_>>();
19636
19637 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19638}
19639
19640pub fn highlight_diagnostic_message(
19641 diagnostic: &Diagnostic,
19642 mut max_message_rows: Option<u8>,
19643) -> (SharedString, Vec<Range<usize>>) {
19644 let mut text_without_backticks = String::new();
19645 let mut code_ranges = Vec::new();
19646
19647 if let Some(source) = &diagnostic.source {
19648 text_without_backticks.push_str(source);
19649 code_ranges.push(0..source.len());
19650 text_without_backticks.push_str(": ");
19651 }
19652
19653 let mut prev_offset = 0;
19654 let mut in_code_block = false;
19655 let has_row_limit = max_message_rows.is_some();
19656 let mut newline_indices = diagnostic
19657 .message
19658 .match_indices('\n')
19659 .filter(|_| has_row_limit)
19660 .map(|(ix, _)| ix)
19661 .fuse()
19662 .peekable();
19663
19664 for (quote_ix, _) in diagnostic
19665 .message
19666 .match_indices('`')
19667 .chain([(diagnostic.message.len(), "")])
19668 {
19669 let mut first_newline_ix = None;
19670 let mut last_newline_ix = None;
19671 while let Some(newline_ix) = newline_indices.peek() {
19672 if *newline_ix < quote_ix {
19673 if first_newline_ix.is_none() {
19674 first_newline_ix = Some(*newline_ix);
19675 }
19676 last_newline_ix = Some(*newline_ix);
19677
19678 if let Some(rows_left) = &mut max_message_rows {
19679 if *rows_left == 0 {
19680 break;
19681 } else {
19682 *rows_left -= 1;
19683 }
19684 }
19685 let _ = newline_indices.next();
19686 } else {
19687 break;
19688 }
19689 }
19690 let prev_len = text_without_backticks.len();
19691 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19692 text_without_backticks.push_str(new_text);
19693 if in_code_block {
19694 code_ranges.push(prev_len..text_without_backticks.len());
19695 }
19696 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19697 in_code_block = !in_code_block;
19698 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19699 text_without_backticks.push_str("...");
19700 break;
19701 }
19702 }
19703
19704 (text_without_backticks.into(), code_ranges)
19705}
19706
19707fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19708 match severity {
19709 DiagnosticSeverity::ERROR => colors.error,
19710 DiagnosticSeverity::WARNING => colors.warning,
19711 DiagnosticSeverity::INFORMATION => colors.info,
19712 DiagnosticSeverity::HINT => colors.info,
19713 _ => colors.ignored,
19714 }
19715}
19716
19717pub fn styled_runs_for_code_label<'a>(
19718 label: &'a CodeLabel,
19719 syntax_theme: &'a theme::SyntaxTheme,
19720) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19721 let fade_out = HighlightStyle {
19722 fade_out: Some(0.35),
19723 ..Default::default()
19724 };
19725
19726 let mut prev_end = label.filter_range.end;
19727 label
19728 .runs
19729 .iter()
19730 .enumerate()
19731 .flat_map(move |(ix, (range, highlight_id))| {
19732 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19733 style
19734 } else {
19735 return Default::default();
19736 };
19737 let mut muted_style = style;
19738 muted_style.highlight(fade_out);
19739
19740 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19741 if range.start >= label.filter_range.end {
19742 if range.start > prev_end {
19743 runs.push((prev_end..range.start, fade_out));
19744 }
19745 runs.push((range.clone(), muted_style));
19746 } else if range.end <= label.filter_range.end {
19747 runs.push((range.clone(), style));
19748 } else {
19749 runs.push((range.start..label.filter_range.end, style));
19750 runs.push((label.filter_range.end..range.end, muted_style));
19751 }
19752 prev_end = cmp::max(prev_end, range.end);
19753
19754 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19755 runs.push((prev_end..label.text.len(), fade_out));
19756 }
19757
19758 runs
19759 })
19760}
19761
19762pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19763 let mut prev_index = 0;
19764 let mut prev_codepoint: Option<char> = None;
19765 text.char_indices()
19766 .chain([(text.len(), '\0')])
19767 .filter_map(move |(index, codepoint)| {
19768 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19769 let is_boundary = index == text.len()
19770 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19771 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19772 if is_boundary {
19773 let chunk = &text[prev_index..index];
19774 prev_index = index;
19775 Some(chunk)
19776 } else {
19777 None
19778 }
19779 })
19780}
19781
19782pub trait RangeToAnchorExt: Sized {
19783 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19784
19785 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19786 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19787 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19788 }
19789}
19790
19791impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19792 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19793 let start_offset = self.start.to_offset(snapshot);
19794 let end_offset = self.end.to_offset(snapshot);
19795 if start_offset == end_offset {
19796 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19797 } else {
19798 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19799 }
19800 }
19801}
19802
19803pub trait RowExt {
19804 fn as_f32(&self) -> f32;
19805
19806 fn next_row(&self) -> Self;
19807
19808 fn previous_row(&self) -> Self;
19809
19810 fn minus(&self, other: Self) -> u32;
19811}
19812
19813impl RowExt for DisplayRow {
19814 fn as_f32(&self) -> f32 {
19815 self.0 as f32
19816 }
19817
19818 fn next_row(&self) -> Self {
19819 Self(self.0 + 1)
19820 }
19821
19822 fn previous_row(&self) -> Self {
19823 Self(self.0.saturating_sub(1))
19824 }
19825
19826 fn minus(&self, other: Self) -> u32 {
19827 self.0 - other.0
19828 }
19829}
19830
19831impl RowExt for MultiBufferRow {
19832 fn as_f32(&self) -> f32 {
19833 self.0 as f32
19834 }
19835
19836 fn next_row(&self) -> Self {
19837 Self(self.0 + 1)
19838 }
19839
19840 fn previous_row(&self) -> Self {
19841 Self(self.0.saturating_sub(1))
19842 }
19843
19844 fn minus(&self, other: Self) -> u32 {
19845 self.0 - other.0
19846 }
19847}
19848
19849trait RowRangeExt {
19850 type Row;
19851
19852 fn len(&self) -> usize;
19853
19854 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19855}
19856
19857impl RowRangeExt for Range<MultiBufferRow> {
19858 type Row = MultiBufferRow;
19859
19860 fn len(&self) -> usize {
19861 (self.end.0 - self.start.0) as usize
19862 }
19863
19864 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19865 (self.start.0..self.end.0).map(MultiBufferRow)
19866 }
19867}
19868
19869impl RowRangeExt for Range<DisplayRow> {
19870 type Row = DisplayRow;
19871
19872 fn len(&self) -> usize {
19873 (self.end.0 - self.start.0) as usize
19874 }
19875
19876 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19877 (self.start.0..self.end.0).map(DisplayRow)
19878 }
19879}
19880
19881/// If select range has more than one line, we
19882/// just point the cursor to range.start.
19883fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19884 if range.start.row == range.end.row {
19885 range
19886 } else {
19887 range.start..range.start
19888 }
19889}
19890pub struct KillRing(ClipboardItem);
19891impl Global for KillRing {}
19892
19893const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19894
19895struct BreakpointPromptEditor {
19896 pub(crate) prompt: Entity<Editor>,
19897 editor: WeakEntity<Editor>,
19898 breakpoint_anchor: Anchor,
19899 breakpoint: Breakpoint,
19900 block_ids: HashSet<CustomBlockId>,
19901 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19902 _subscriptions: Vec<Subscription>,
19903}
19904
19905impl BreakpointPromptEditor {
19906 const MAX_LINES: u8 = 4;
19907
19908 fn new(
19909 editor: WeakEntity<Editor>,
19910 breakpoint_anchor: Anchor,
19911 breakpoint: Breakpoint,
19912 window: &mut Window,
19913 cx: &mut Context<Self>,
19914 ) -> Self {
19915 let buffer = cx.new(|cx| {
19916 Buffer::local(
19917 breakpoint
19918 .message
19919 .as_ref()
19920 .map(|msg| msg.to_string())
19921 .unwrap_or_default(),
19922 cx,
19923 )
19924 });
19925 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19926
19927 let prompt = cx.new(|cx| {
19928 let mut prompt = Editor::new(
19929 EditorMode::AutoHeight {
19930 max_lines: Self::MAX_LINES as usize,
19931 },
19932 buffer,
19933 None,
19934 window,
19935 cx,
19936 );
19937 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19938 prompt.set_show_cursor_when_unfocused(false, cx);
19939 prompt.set_placeholder_text(
19940 "Message to log when breakpoint is hit. Expressions within {} are interpolated.",
19941 cx,
19942 );
19943
19944 prompt
19945 });
19946
19947 Self {
19948 prompt,
19949 editor,
19950 breakpoint_anchor,
19951 breakpoint,
19952 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
19953 block_ids: Default::default(),
19954 _subscriptions: vec![],
19955 }
19956 }
19957
19958 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
19959 self.block_ids.extend(block_ids)
19960 }
19961
19962 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
19963 if let Some(editor) = self.editor.upgrade() {
19964 let log_message = self
19965 .prompt
19966 .read(cx)
19967 .buffer
19968 .read(cx)
19969 .as_singleton()
19970 .expect("A multi buffer in breakpoint prompt isn't possible")
19971 .read(cx)
19972 .as_rope()
19973 .to_string();
19974
19975 editor.update(cx, |editor, cx| {
19976 editor.edit_breakpoint_at_anchor(
19977 self.breakpoint_anchor,
19978 self.breakpoint.clone(),
19979 BreakpointEditAction::EditLogMessage(log_message.into()),
19980 cx,
19981 );
19982
19983 editor.remove_blocks(self.block_ids.clone(), None, cx);
19984 cx.focus_self(window);
19985 });
19986 }
19987 }
19988
19989 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
19990 self.editor
19991 .update(cx, |editor, cx| {
19992 editor.remove_blocks(self.block_ids.clone(), None, cx);
19993 window.focus(&editor.focus_handle);
19994 })
19995 .log_err();
19996 }
19997
19998 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
19999 let settings = ThemeSettings::get_global(cx);
20000 let text_style = TextStyle {
20001 color: if self.prompt.read(cx).read_only(cx) {
20002 cx.theme().colors().text_disabled
20003 } else {
20004 cx.theme().colors().text
20005 },
20006 font_family: settings.buffer_font.family.clone(),
20007 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20008 font_size: settings.buffer_font_size(cx).into(),
20009 font_weight: settings.buffer_font.weight,
20010 line_height: relative(settings.buffer_line_height.value()),
20011 ..Default::default()
20012 };
20013 EditorElement::new(
20014 &self.prompt,
20015 EditorStyle {
20016 background: cx.theme().colors().editor_background,
20017 local_player: cx.theme().players().local(),
20018 text: text_style,
20019 ..Default::default()
20020 },
20021 )
20022 }
20023}
20024
20025impl Render for BreakpointPromptEditor {
20026 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20027 let gutter_dimensions = *self.gutter_dimensions.lock();
20028 h_flex()
20029 .key_context("Editor")
20030 .bg(cx.theme().colors().editor_background)
20031 .border_y_1()
20032 .border_color(cx.theme().status().info_border)
20033 .size_full()
20034 .py(window.line_height() / 2.5)
20035 .on_action(cx.listener(Self::confirm))
20036 .on_action(cx.listener(Self::cancel))
20037 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20038 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20039 }
20040}
20041
20042impl Focusable for BreakpointPromptEditor {
20043 fn focus_handle(&self, cx: &App) -> FocusHandle {
20044 self.prompt.focus_handle(cx)
20045 }
20046}
20047
20048fn all_edits_insertions_or_deletions(
20049 edits: &Vec<(Range<Anchor>, String)>,
20050 snapshot: &MultiBufferSnapshot,
20051) -> bool {
20052 let mut all_insertions = true;
20053 let mut all_deletions = true;
20054
20055 for (range, new_text) in edits.iter() {
20056 let range_is_empty = range.to_offset(&snapshot).is_empty();
20057 let text_is_empty = new_text.is_empty();
20058
20059 if range_is_empty != text_is_empty {
20060 if range_is_empty {
20061 all_deletions = false;
20062 } else {
20063 all_insertions = false;
20064 }
20065 } else {
20066 return false;
20067 }
20068
20069 if !all_insertions && !all_deletions {
20070 return false;
20071 }
20072 }
20073 all_insertions || all_deletions
20074}
20075
20076struct MissingEditPredictionKeybindingTooltip;
20077
20078impl Render for MissingEditPredictionKeybindingTooltip {
20079 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20080 ui::tooltip_container(window, cx, |container, _, cx| {
20081 container
20082 .flex_shrink_0()
20083 .max_w_80()
20084 .min_h(rems_from_px(124.))
20085 .justify_between()
20086 .child(
20087 v_flex()
20088 .flex_1()
20089 .text_ui_sm(cx)
20090 .child(Label::new("Conflict with Accept Keybinding"))
20091 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20092 )
20093 .child(
20094 h_flex()
20095 .pb_1()
20096 .gap_1()
20097 .items_end()
20098 .w_full()
20099 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20100 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20101 }))
20102 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20103 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20104 })),
20105 )
20106 })
20107 }
20108}
20109
20110#[derive(Debug, Clone, Copy, PartialEq)]
20111pub struct LineHighlight {
20112 pub background: Background,
20113 pub border: Option<gpui::Hsla>,
20114}
20115
20116impl From<Hsla> for LineHighlight {
20117 fn from(hsla: Hsla) -> Self {
20118 Self {
20119 background: hsla.into(),
20120 border: None,
20121 }
20122 }
20123}
20124
20125impl From<Background> for LineHighlight {
20126 fn from(background: Background) -> Self {
20127 Self {
20128 background,
20129 border: None,
20130 }
20131 }
20132}
20133
20134fn render_diff_hunk_controls(
20135 row: u32,
20136 status: &DiffHunkStatus,
20137 hunk_range: Range<Anchor>,
20138 is_created_file: bool,
20139 line_height: Pixels,
20140 editor: &Entity<Editor>,
20141 _window: &mut Window,
20142 cx: &mut App,
20143) -> AnyElement {
20144 h_flex()
20145 .h(line_height)
20146 .mr_1()
20147 .gap_1()
20148 .px_0p5()
20149 .pb_1()
20150 .border_x_1()
20151 .border_b_1()
20152 .border_color(cx.theme().colors().border_variant)
20153 .rounded_b_lg()
20154 .bg(cx.theme().colors().editor_background)
20155 .gap_1()
20156 .occlude()
20157 .shadow_md()
20158 .child(if status.has_secondary_hunk() {
20159 Button::new(("stage", row as u64), "Stage")
20160 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20161 .tooltip({
20162 let focus_handle = editor.focus_handle(cx);
20163 move |window, cx| {
20164 Tooltip::for_action_in(
20165 "Stage Hunk",
20166 &::git::ToggleStaged,
20167 &focus_handle,
20168 window,
20169 cx,
20170 )
20171 }
20172 })
20173 .on_click({
20174 let editor = editor.clone();
20175 move |_event, _window, cx| {
20176 editor.update(cx, |editor, cx| {
20177 editor.stage_or_unstage_diff_hunks(
20178 true,
20179 vec![hunk_range.start..hunk_range.start],
20180 cx,
20181 );
20182 });
20183 }
20184 })
20185 } else {
20186 Button::new(("unstage", row as u64), "Unstage")
20187 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20188 .tooltip({
20189 let focus_handle = editor.focus_handle(cx);
20190 move |window, cx| {
20191 Tooltip::for_action_in(
20192 "Unstage Hunk",
20193 &::git::ToggleStaged,
20194 &focus_handle,
20195 window,
20196 cx,
20197 )
20198 }
20199 })
20200 .on_click({
20201 let editor = editor.clone();
20202 move |_event, _window, cx| {
20203 editor.update(cx, |editor, cx| {
20204 editor.stage_or_unstage_diff_hunks(
20205 false,
20206 vec![hunk_range.start..hunk_range.start],
20207 cx,
20208 );
20209 });
20210 }
20211 })
20212 })
20213 .child(
20214 Button::new("restore", "Restore")
20215 .tooltip({
20216 let focus_handle = editor.focus_handle(cx);
20217 move |window, cx| {
20218 Tooltip::for_action_in(
20219 "Restore Hunk",
20220 &::git::Restore,
20221 &focus_handle,
20222 window,
20223 cx,
20224 )
20225 }
20226 })
20227 .on_click({
20228 let editor = editor.clone();
20229 move |_event, window, cx| {
20230 editor.update(cx, |editor, cx| {
20231 let snapshot = editor.snapshot(window, cx);
20232 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20233 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20234 });
20235 }
20236 })
20237 .disabled(is_created_file),
20238 )
20239 .when(
20240 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20241 |el| {
20242 el.child(
20243 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20244 .shape(IconButtonShape::Square)
20245 .icon_size(IconSize::Small)
20246 // .disabled(!has_multiple_hunks)
20247 .tooltip({
20248 let focus_handle = editor.focus_handle(cx);
20249 move |window, cx| {
20250 Tooltip::for_action_in(
20251 "Next Hunk",
20252 &GoToHunk,
20253 &focus_handle,
20254 window,
20255 cx,
20256 )
20257 }
20258 })
20259 .on_click({
20260 let editor = editor.clone();
20261 move |_event, window, cx| {
20262 editor.update(cx, |editor, cx| {
20263 let snapshot = editor.snapshot(window, cx);
20264 let position =
20265 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20266 editor.go_to_hunk_before_or_after_position(
20267 &snapshot,
20268 position,
20269 Direction::Next,
20270 window,
20271 cx,
20272 );
20273 editor.expand_selected_diff_hunks(cx);
20274 });
20275 }
20276 }),
20277 )
20278 .child(
20279 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20280 .shape(IconButtonShape::Square)
20281 .icon_size(IconSize::Small)
20282 // .disabled(!has_multiple_hunks)
20283 .tooltip({
20284 let focus_handle = editor.focus_handle(cx);
20285 move |window, cx| {
20286 Tooltip::for_action_in(
20287 "Previous Hunk",
20288 &GoToPreviousHunk,
20289 &focus_handle,
20290 window,
20291 cx,
20292 )
20293 }
20294 })
20295 .on_click({
20296 let editor = editor.clone();
20297 move |_event, window, cx| {
20298 editor.update(cx, |editor, cx| {
20299 let snapshot = editor.snapshot(window, cx);
20300 let point =
20301 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20302 editor.go_to_hunk_before_or_after_position(
20303 &snapshot,
20304 point,
20305 Direction::Prev,
20306 window,
20307 cx,
20308 );
20309 editor.expand_selected_diff_hunks(cx);
20310 });
20311 }
20312 }),
20313 )
20314 },
20315 )
20316 .into_any_element()
20317}