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::{Context as _, Result, anyhow};
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::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
70pub use element::{
71 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
72};
73use feature_flags::{Debugger, FeatureFlagAppExt};
74use futures::{
75 FutureExt,
76 future::{self, Shared, join},
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 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
88 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
89 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
90 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
91 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
92 Stateful, Styled, StyledText, Subscription, Task, TextStyle, TextStyleRefinement,
93 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
94 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
95};
96use highlight_matching_bracket::refresh_matching_bracket_highlights;
97use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
98use hover_popover::{HoverState, hide_hover};
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 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
107 CursorShape, Diagnostic, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
108 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
109 TransactionId, TreeSitterOptions, WordsQuery,
110 language_settings::{
111 self, InlayHintSettings, RewrapBehavior, WordsCompletionMode, all_language_settings,
112 language_settings,
113 },
114 point_from_lsp, text_diff_with_options,
115};
116use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
117use linked_editing_ranges::refresh_linked_ranges;
118use mouse_context_menu::MouseContextMenu;
119use persistence::DB;
120use project::{
121 ProjectPath,
122 debugger::breakpoint_store::{
123 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
124 },
125};
126
127pub use proposed_changes_editor::{
128 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
129};
130use smallvec::smallvec;
131use std::{cell::OnceCell, iter::Peekable};
132use task::{ResolvedTask, TaskTemplate, TaskVariables};
133
134pub use lsp::CompletionContext;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
137 InsertTextFormat, LanguageServerId, LanguageServerName,
138};
139
140use language::BufferSnapshot;
141use movement::TextLayoutDetails;
142pub use multi_buffer::{
143 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, RowInfo,
144 ToOffset, ToPoint,
145};
146use multi_buffer::{
147 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
148 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
149};
150use parking_lot::Mutex;
151use project::{
152 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
153 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
154 TaskSourceKind,
155 debugger::breakpoint_store::Breakpoint,
156 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
157 project_settings::{GitGutterSetting, ProjectSettings},
158};
159use rand::prelude::*;
160use rpc::{ErrorExt, proto::*};
161use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
162use selections_collection::{
163 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
164};
165use serde::{Deserialize, Serialize};
166use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::SmallVec;
168use snippet::Snippet;
169use std::sync::Arc;
170use std::{
171 any::TypeId,
172 borrow::Cow,
173 cell::RefCell,
174 cmp::{self, Ordering, Reverse},
175 mem,
176 num::NonZeroU32,
177 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 time::{Duration, Instant},
181};
182pub use sum_tree::Bias;
183use sum_tree::TreeMap;
184use text::{BufferId, OffsetUtf16, Rope};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, Disclosure, IconButton, IconButtonShape, IconName, IconSize, Key,
191 Tooltip, h_flex, prelude::*,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemHandle, PreviewTabsSettings},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::hover_links::{find_url, find_url_from_range};
204use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
205
206pub const FILE_HEADER_HEIGHT: u32 = 2;
207pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
208pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
209const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
210const MAX_LINE_LEN: usize = 1024;
211const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
212const MAX_SELECTION_HISTORY_LEN: usize = 1024;
213pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
214#[doc(hidden)]
215pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
216
217pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
218pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
219pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
220
221pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
222pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
223pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
224
225pub type RenderDiffHunkControlsFn = Arc<
226 dyn Fn(
227 u32,
228 &DiffHunkStatus,
229 Range<Anchor>,
230 bool,
231 Pixels,
232 &Entity<Editor>,
233 &mut Window,
234 &mut App,
235 ) -> AnyElement,
236>;
237
238const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
239 alt: true,
240 shift: true,
241 control: false,
242 platform: false,
243 function: false,
244};
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
247pub enum InlayId {
248 InlineCompletion(usize),
249 Hint(usize),
250}
251
252impl InlayId {
253 fn id(&self) -> usize {
254 match self {
255 Self::InlineCompletion(id) => *id,
256 Self::Hint(id) => *id,
257 }
258 }
259}
260
261pub enum DebugCurrentRowHighlight {}
262enum DocumentHighlightRead {}
263enum DocumentHighlightWrite {}
264enum InputComposition {}
265enum SelectedTextHighlight {}
266
267#[derive(Debug, Copy, Clone, PartialEq, Eq)]
268pub enum Navigated {
269 Yes,
270 No,
271}
272
273impl Navigated {
274 pub fn from_bool(yes: bool) -> Navigated {
275 if yes { Navigated::Yes } else { Navigated::No }
276 }
277}
278
279#[derive(Debug, Clone, PartialEq, Eq)]
280enum DisplayDiffHunk {
281 Folded {
282 display_row: DisplayRow,
283 },
284 Unfolded {
285 is_created_file: bool,
286 diff_base_byte_range: Range<usize>,
287 display_row_range: Range<DisplayRow>,
288 multi_buffer_range: Range<Anchor>,
289 status: DiffHunkStatus,
290 },
291}
292
293pub enum HideMouseCursorOrigin {
294 TypingAction,
295 MovementAction,
296}
297
298pub fn init_settings(cx: &mut App) {
299 EditorSettings::register(cx);
300}
301
302pub fn init(cx: &mut App) {
303 init_settings(cx);
304
305 workspace::register_project_item::<Editor>(cx);
306 workspace::FollowableViewRegistry::register::<Editor>(cx);
307 workspace::register_serializable_item::<Editor>(cx);
308
309 cx.observe_new(
310 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
311 workspace.register_action(Editor::new_file);
312 workspace.register_action(Editor::new_file_vertical);
313 workspace.register_action(Editor::new_file_horizontal);
314 workspace.register_action(Editor::cancel_language_server_work);
315 },
316 )
317 .detach();
318
319 cx.on_action(move |_: &workspace::NewFile, cx| {
320 let app_state = workspace::AppState::global(cx);
321 if let Some(app_state) = app_state.upgrade() {
322 workspace::open_new(
323 Default::default(),
324 app_state,
325 cx,
326 |workspace, window, cx| {
327 Editor::new_file(workspace, &Default::default(), window, cx)
328 },
329 )
330 .detach();
331 }
332 });
333 cx.on_action(move |_: &workspace::NewWindow, cx| {
334 let app_state = workspace::AppState::global(cx);
335 if let Some(app_state) = app_state.upgrade() {
336 workspace::open_new(
337 Default::default(),
338 app_state,
339 cx,
340 |workspace, window, cx| {
341 cx.activate(true);
342 Editor::new_file(workspace, &Default::default(), window, cx)
343 },
344 )
345 .detach();
346 }
347 });
348}
349
350pub struct SearchWithinRange;
351
352trait InvalidationRegion {
353 fn ranges(&self) -> &[Range<Anchor>];
354}
355
356#[derive(Clone, Debug, PartialEq)]
357pub enum SelectPhase {
358 Begin {
359 position: DisplayPoint,
360 add: bool,
361 click_count: usize,
362 },
363 BeginColumnar {
364 position: DisplayPoint,
365 reset: bool,
366 goal_column: u32,
367 },
368 Extend {
369 position: DisplayPoint,
370 click_count: usize,
371 },
372 Update {
373 position: DisplayPoint,
374 goal_column: u32,
375 scroll_delta: gpui::Point<f32>,
376 },
377 End,
378}
379
380#[derive(Clone, Debug)]
381pub enum SelectMode {
382 Character,
383 Word(Range<Anchor>),
384 Line(Range<Anchor>),
385 All,
386}
387
388#[derive(Copy, Clone, PartialEq, Eq, Debug)]
389pub enum EditorMode {
390 SingleLine { auto_width: bool },
391 AutoHeight { max_lines: usize },
392 Full,
393}
394
395#[derive(Copy, Clone, Debug)]
396pub enum SoftWrap {
397 /// Prefer not to wrap at all.
398 ///
399 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
400 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
401 GitDiff,
402 /// Prefer a single line generally, unless an overly long line is encountered.
403 None,
404 /// Soft wrap lines that exceed the editor width.
405 EditorWidth,
406 /// Soft wrap lines at the preferred line length.
407 Column(u32),
408 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
409 Bounded(u32),
410}
411
412#[derive(Clone)]
413pub struct EditorStyle {
414 pub background: Hsla,
415 pub local_player: PlayerColor,
416 pub text: TextStyle,
417 pub scrollbar_width: Pixels,
418 pub syntax: Arc<SyntaxTheme>,
419 pub status: StatusColors,
420 pub inlay_hints_style: HighlightStyle,
421 pub inline_completion_styles: InlineCompletionStyles,
422 pub unnecessary_code_fade: f32,
423}
424
425impl Default for EditorStyle {
426 fn default() -> Self {
427 Self {
428 background: Hsla::default(),
429 local_player: PlayerColor::default(),
430 text: TextStyle::default(),
431 scrollbar_width: Pixels::default(),
432 syntax: Default::default(),
433 // HACK: Status colors don't have a real default.
434 // We should look into removing the status colors from the editor
435 // style and retrieve them directly from the theme.
436 status: StatusColors::dark(),
437 inlay_hints_style: HighlightStyle::default(),
438 inline_completion_styles: InlineCompletionStyles {
439 insertion: HighlightStyle::default(),
440 whitespace: HighlightStyle::default(),
441 },
442 unnecessary_code_fade: Default::default(),
443 }
444 }
445}
446
447pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
448 let show_background = language_settings::language_settings(None, None, cx)
449 .inlay_hints
450 .show_background;
451
452 HighlightStyle {
453 color: Some(cx.theme().status().hint),
454 background_color: show_background.then(|| cx.theme().status().hint_background),
455 ..HighlightStyle::default()
456 }
457}
458
459pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
460 InlineCompletionStyles {
461 insertion: HighlightStyle {
462 color: Some(cx.theme().status().predictive),
463 ..HighlightStyle::default()
464 },
465 whitespace: HighlightStyle {
466 background_color: Some(cx.theme().status().created_background),
467 ..HighlightStyle::default()
468 },
469 }
470}
471
472type CompletionId = usize;
473
474pub(crate) enum EditDisplayMode {
475 TabAccept,
476 DiffPopover,
477 Inline,
478}
479
480enum InlineCompletion {
481 Edit {
482 edits: Vec<(Range<Anchor>, String)>,
483 edit_preview: Option<EditPreview>,
484 display_mode: EditDisplayMode,
485 snapshot: BufferSnapshot,
486 },
487 Move {
488 target: Anchor,
489 snapshot: BufferSnapshot,
490 },
491}
492
493struct InlineCompletionState {
494 inlay_ids: Vec<InlayId>,
495 completion: InlineCompletion,
496 completion_id: Option<SharedString>,
497 invalidation_range: Range<Anchor>,
498}
499
500enum EditPredictionSettings {
501 Disabled,
502 Enabled {
503 show_in_menu: bool,
504 preview_requires_modifier: bool,
505 },
506}
507
508enum InlineCompletionHighlight {}
509
510#[derive(Debug, Clone)]
511struct InlineDiagnostic {
512 message: SharedString,
513 group_id: usize,
514 is_primary: bool,
515 start: Point,
516 severity: DiagnosticSeverity,
517}
518
519pub enum MenuInlineCompletionsPolicy {
520 Never,
521 ByProvider,
522}
523
524pub enum EditPredictionPreview {
525 /// Modifier is not pressed
526 Inactive { released_too_fast: bool },
527 /// Modifier pressed
528 Active {
529 since: Instant,
530 previous_scroll_position: Option<ScrollAnchor>,
531 },
532}
533
534impl EditPredictionPreview {
535 pub fn released_too_fast(&self) -> bool {
536 match self {
537 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
538 EditPredictionPreview::Active { .. } => false,
539 }
540 }
541
542 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
543 if let EditPredictionPreview::Active {
544 previous_scroll_position,
545 ..
546 } = self
547 {
548 *previous_scroll_position = scroll_position;
549 }
550 }
551}
552
553pub struct ContextMenuOptions {
554 pub min_entries_visible: usize,
555 pub max_entries_visible: usize,
556 pub placement: Option<ContextMenuPlacement>,
557}
558
559#[derive(Debug, Clone, PartialEq, Eq)]
560pub enum ContextMenuPlacement {
561 Above,
562 Below,
563}
564
565#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
566struct EditorActionId(usize);
567
568impl EditorActionId {
569 pub fn post_inc(&mut self) -> Self {
570 let answer = self.0;
571
572 *self = Self(answer + 1);
573
574 Self(answer)
575 }
576}
577
578// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
579// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
580
581type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
582type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
583
584#[derive(Default)]
585struct ScrollbarMarkerState {
586 scrollbar_size: Size<Pixels>,
587 dirty: bool,
588 markers: Arc<[PaintQuad]>,
589 pending_refresh: Option<Task<Result<()>>>,
590}
591
592impl ScrollbarMarkerState {
593 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
594 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
595 }
596}
597
598#[derive(Clone, Debug)]
599struct RunnableTasks {
600 templates: Vec<(TaskSourceKind, TaskTemplate)>,
601 offset: multi_buffer::Anchor,
602 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
603 column: u32,
604 // Values of all named captures, including those starting with '_'
605 extra_variables: HashMap<String, String>,
606 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
607 context_range: Range<BufferOffset>,
608}
609
610impl RunnableTasks {
611 fn resolve<'a>(
612 &'a self,
613 cx: &'a task::TaskContext,
614 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
615 self.templates.iter().filter_map(|(kind, template)| {
616 template
617 .resolve_task(&kind.to_id_base(), cx)
618 .map(|task| (kind.clone(), task))
619 })
620 }
621}
622
623#[derive(Clone)]
624struct ResolvedTasks {
625 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
626 position: Anchor,
627}
628
629#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
630struct BufferOffset(usize);
631
632// Addons allow storing per-editor state in other crates (e.g. Vim)
633pub trait Addon: 'static {
634 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
635
636 fn render_buffer_header_controls(
637 &self,
638 _: &ExcerptInfo,
639 _: &Window,
640 _: &App,
641 ) -> Option<AnyElement> {
642 None
643 }
644
645 fn to_any(&self) -> &dyn std::any::Any;
646}
647
648/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
649///
650/// See the [module level documentation](self) for more information.
651pub struct Editor {
652 focus_handle: FocusHandle,
653 last_focused_descendant: Option<WeakFocusHandle>,
654 /// The text buffer being edited
655 buffer: Entity<MultiBuffer>,
656 /// Map of how text in the buffer should be displayed.
657 /// Handles soft wraps, folds, fake inlay text insertions, etc.
658 pub display_map: Entity<DisplayMap>,
659 pub selections: SelectionsCollection,
660 pub scroll_manager: ScrollManager,
661 /// When inline assist editors are linked, they all render cursors because
662 /// typing enters text into each of them, even the ones that aren't focused.
663 pub(crate) show_cursor_when_unfocused: bool,
664 columnar_selection_tail: Option<Anchor>,
665 add_selections_state: Option<AddSelectionsState>,
666 select_next_state: Option<SelectNextState>,
667 select_prev_state: Option<SelectNextState>,
668 selection_history: SelectionHistory,
669 autoclose_regions: Vec<AutocloseRegion>,
670 snippet_stack: InvalidationStack<SnippetState>,
671 select_syntax_node_history: SelectSyntaxNodeHistory,
672 ime_transaction: Option<TransactionId>,
673 active_diagnostics: Option<ActiveDiagnosticGroup>,
674 show_inline_diagnostics: bool,
675 inline_diagnostics_update: Task<()>,
676 inline_diagnostics_enabled: bool,
677 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
678 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
679 hard_wrap: Option<usize>,
680
681 // TODO: make this a access method
682 pub project: Option<Entity<Project>>,
683 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
684 completion_provider: Option<Box<dyn CompletionProvider>>,
685 collaboration_hub: Option<Box<dyn CollaborationHub>>,
686 blink_manager: Entity<BlinkManager>,
687 show_cursor_names: bool,
688 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
689 pub show_local_selections: bool,
690 mode: EditorMode,
691 show_breadcrumbs: bool,
692 show_gutter: bool,
693 show_scrollbars: bool,
694 show_line_numbers: Option<bool>,
695 use_relative_line_numbers: Option<bool>,
696 show_git_diff_gutter: Option<bool>,
697 show_code_actions: Option<bool>,
698 show_runnables: Option<bool>,
699 show_breakpoints: Option<bool>,
700 show_wrap_guides: Option<bool>,
701 show_indent_guides: Option<bool>,
702 placeholder_text: Option<Arc<str>>,
703 highlight_order: usize,
704 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
705 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
706 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
707 scrollbar_marker_state: ScrollbarMarkerState,
708 active_indent_guides_state: ActiveIndentGuidesState,
709 nav_history: Option<ItemNavHistory>,
710 context_menu: RefCell<Option<CodeContextMenu>>,
711 context_menu_options: Option<ContextMenuOptions>,
712 mouse_context_menu: Option<MouseContextMenu>,
713 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
714 signature_help_state: SignatureHelpState,
715 auto_signature_help: Option<bool>,
716 find_all_references_task_sources: Vec<Anchor>,
717 next_completion_id: CompletionId,
718 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
719 code_actions_task: Option<Task<Result<()>>>,
720 selection_highlight_task: Option<Task<()>>,
721 document_highlights_task: Option<Task<()>>,
722 linked_editing_range_task: Option<Task<Option<()>>>,
723 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
724 pending_rename: Option<RenameState>,
725 searchable: bool,
726 cursor_shape: CursorShape,
727 current_line_highlight: Option<CurrentLineHighlight>,
728 collapse_matches: bool,
729 autoindent_mode: Option<AutoindentMode>,
730 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
731 input_enabled: bool,
732 use_modal_editing: bool,
733 read_only: bool,
734 leader_peer_id: Option<PeerId>,
735 remote_id: Option<ViewId>,
736 hover_state: HoverState,
737 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
738 gutter_hovered: bool,
739 hovered_link_state: Option<HoveredLinkState>,
740 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
741 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
742 active_inline_completion: Option<InlineCompletionState>,
743 /// Used to prevent flickering as the user types while the menu is open
744 stale_inline_completion_in_menu: Option<InlineCompletionState>,
745 edit_prediction_settings: EditPredictionSettings,
746 inline_completions_hidden_for_vim_mode: bool,
747 show_inline_completions_override: Option<bool>,
748 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
749 edit_prediction_preview: EditPredictionPreview,
750 edit_prediction_indent_conflict: bool,
751 edit_prediction_requires_modifier_in_indent_conflict: bool,
752 inlay_hint_cache: InlayHintCache,
753 next_inlay_id: usize,
754 _subscriptions: Vec<Subscription>,
755 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
756 gutter_dimensions: GutterDimensions,
757 style: Option<EditorStyle>,
758 text_style_refinement: Option<TextStyleRefinement>,
759 next_editor_action_id: EditorActionId,
760 editor_actions:
761 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
762 use_autoclose: bool,
763 use_auto_surround: bool,
764 auto_replace_emoji_shortcode: bool,
765 jsx_tag_auto_close_enabled_in_any_buffer: bool,
766 show_git_blame_gutter: bool,
767 show_git_blame_inline: bool,
768 show_git_blame_inline_delay_task: Option<Task<()>>,
769 git_blame_inline_tooltip: Option<WeakEntity<crate::commit_tooltip::CommitTooltip>>,
770 git_blame_inline_enabled: bool,
771 render_diff_hunk_controls: RenderDiffHunkControlsFn,
772 serialize_dirty_buffers: bool,
773 show_selection_menu: Option<bool>,
774 blame: Option<Entity<GitBlame>>,
775 blame_subscription: Option<Subscription>,
776 custom_context_menu: Option<
777 Box<
778 dyn 'static
779 + Fn(
780 &mut Self,
781 DisplayPoint,
782 &mut Window,
783 &mut Context<Self>,
784 ) -> Option<Entity<ui::ContextMenu>>,
785 >,
786 >,
787 last_bounds: Option<Bounds<Pixels>>,
788 last_position_map: Option<Rc<PositionMap>>,
789 expect_bounds_change: Option<Bounds<Pixels>>,
790 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
791 tasks_update_task: Option<Task<()>>,
792 breakpoint_store: Option<Entity<BreakpointStore>>,
793 /// Allow's a user to create a breakpoint by selecting this indicator
794 /// It should be None while a user is not hovering over the gutter
795 /// Otherwise it represents the point that the breakpoint will be shown
796 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
797 in_project_search: bool,
798 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
799 breadcrumb_header: Option<String>,
800 focused_block: Option<FocusedBlock>,
801 next_scroll_position: NextScrollCursorCenterTopBottom,
802 addons: HashMap<TypeId, Box<dyn Addon>>,
803 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
804 load_diff_task: Option<Shared<Task<()>>>,
805 selection_mark_mode: bool,
806 toggle_fold_multiple_buffers: Task<()>,
807 _scroll_cursor_center_top_bottom_task: Task<()>,
808 serialize_selections: Task<()>,
809 serialize_folds: Task<()>,
810 mouse_cursor_hidden: bool,
811 hide_mouse_mode: HideMouseMode,
812}
813
814#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
815enum NextScrollCursorCenterTopBottom {
816 #[default]
817 Center,
818 Top,
819 Bottom,
820}
821
822impl NextScrollCursorCenterTopBottom {
823 fn next(&self) -> Self {
824 match self {
825 Self::Center => Self::Top,
826 Self::Top => Self::Bottom,
827 Self::Bottom => Self::Center,
828 }
829 }
830}
831
832#[derive(Clone)]
833pub struct EditorSnapshot {
834 pub mode: EditorMode,
835 show_gutter: bool,
836 show_line_numbers: Option<bool>,
837 show_git_diff_gutter: Option<bool>,
838 show_code_actions: Option<bool>,
839 show_runnables: Option<bool>,
840 show_breakpoints: Option<bool>,
841 git_blame_gutter_max_author_length: Option<usize>,
842 pub display_snapshot: DisplaySnapshot,
843 pub placeholder_text: Option<Arc<str>>,
844 is_focused: bool,
845 scroll_anchor: ScrollAnchor,
846 ongoing_scroll: OngoingScroll,
847 current_line_highlight: CurrentLineHighlight,
848 gutter_hovered: bool,
849}
850
851const GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED: usize = 20;
852
853#[derive(Default, Debug, Clone, Copy)]
854pub struct GutterDimensions {
855 pub left_padding: Pixels,
856 pub right_padding: Pixels,
857 pub width: Pixels,
858 pub margin: Pixels,
859 pub git_blame_entries_width: Option<Pixels>,
860}
861
862impl GutterDimensions {
863 /// The full width of the space taken up by the gutter.
864 pub fn full_width(&self) -> Pixels {
865 self.margin + self.width
866 }
867
868 /// The width of the space reserved for the fold indicators,
869 /// use alongside 'justify_end' and `gutter_width` to
870 /// right align content with the line numbers
871 pub fn fold_area_width(&self) -> Pixels {
872 self.margin + self.right_padding
873 }
874}
875
876#[derive(Debug)]
877pub struct RemoteSelection {
878 pub replica_id: ReplicaId,
879 pub selection: Selection<Anchor>,
880 pub cursor_shape: CursorShape,
881 pub peer_id: PeerId,
882 pub line_mode: bool,
883 pub participant_index: Option<ParticipantIndex>,
884 pub user_name: Option<SharedString>,
885}
886
887#[derive(Clone, Debug)]
888struct SelectionHistoryEntry {
889 selections: Arc<[Selection<Anchor>]>,
890 select_next_state: Option<SelectNextState>,
891 select_prev_state: Option<SelectNextState>,
892 add_selections_state: Option<AddSelectionsState>,
893}
894
895enum SelectionHistoryMode {
896 Normal,
897 Undoing,
898 Redoing,
899}
900
901#[derive(Clone, PartialEq, Eq, Hash)]
902struct HoveredCursor {
903 replica_id: u16,
904 selection_id: usize,
905}
906
907impl Default for SelectionHistoryMode {
908 fn default() -> Self {
909 Self::Normal
910 }
911}
912
913#[derive(Default)]
914struct SelectionHistory {
915 #[allow(clippy::type_complexity)]
916 selections_by_transaction:
917 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
918 mode: SelectionHistoryMode,
919 undo_stack: VecDeque<SelectionHistoryEntry>,
920 redo_stack: VecDeque<SelectionHistoryEntry>,
921}
922
923impl SelectionHistory {
924 fn insert_transaction(
925 &mut self,
926 transaction_id: TransactionId,
927 selections: Arc<[Selection<Anchor>]>,
928 ) {
929 self.selections_by_transaction
930 .insert(transaction_id, (selections, None));
931 }
932
933 #[allow(clippy::type_complexity)]
934 fn transaction(
935 &self,
936 transaction_id: TransactionId,
937 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
938 self.selections_by_transaction.get(&transaction_id)
939 }
940
941 #[allow(clippy::type_complexity)]
942 fn transaction_mut(
943 &mut self,
944 transaction_id: TransactionId,
945 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
946 self.selections_by_transaction.get_mut(&transaction_id)
947 }
948
949 fn push(&mut self, entry: SelectionHistoryEntry) {
950 if !entry.selections.is_empty() {
951 match self.mode {
952 SelectionHistoryMode::Normal => {
953 self.push_undo(entry);
954 self.redo_stack.clear();
955 }
956 SelectionHistoryMode::Undoing => self.push_redo(entry),
957 SelectionHistoryMode::Redoing => self.push_undo(entry),
958 }
959 }
960 }
961
962 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
963 if self
964 .undo_stack
965 .back()
966 .map_or(true, |e| e.selections != entry.selections)
967 {
968 self.undo_stack.push_back(entry);
969 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
970 self.undo_stack.pop_front();
971 }
972 }
973 }
974
975 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
976 if self
977 .redo_stack
978 .back()
979 .map_or(true, |e| e.selections != entry.selections)
980 {
981 self.redo_stack.push_back(entry);
982 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
983 self.redo_stack.pop_front();
984 }
985 }
986 }
987}
988
989struct RowHighlight {
990 index: usize,
991 range: Range<Anchor>,
992 color: Hsla,
993 should_autoscroll: bool,
994}
995
996#[derive(Clone, Debug)]
997struct AddSelectionsState {
998 above: bool,
999 stack: Vec<usize>,
1000}
1001
1002#[derive(Clone)]
1003struct SelectNextState {
1004 query: AhoCorasick,
1005 wordwise: bool,
1006 done: bool,
1007}
1008
1009impl std::fmt::Debug for SelectNextState {
1010 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1011 f.debug_struct(std::any::type_name::<Self>())
1012 .field("wordwise", &self.wordwise)
1013 .field("done", &self.done)
1014 .finish()
1015 }
1016}
1017
1018#[derive(Debug)]
1019struct AutocloseRegion {
1020 selection_id: usize,
1021 range: Range<Anchor>,
1022 pair: BracketPair,
1023}
1024
1025#[derive(Debug)]
1026struct SnippetState {
1027 ranges: Vec<Vec<Range<Anchor>>>,
1028 active_index: usize,
1029 choices: Vec<Option<Vec<String>>>,
1030}
1031
1032#[doc(hidden)]
1033pub struct RenameState {
1034 pub range: Range<Anchor>,
1035 pub old_name: Arc<str>,
1036 pub editor: Entity<Editor>,
1037 block_id: CustomBlockId,
1038}
1039
1040struct InvalidationStack<T>(Vec<T>);
1041
1042struct RegisteredInlineCompletionProvider {
1043 provider: Arc<dyn InlineCompletionProviderHandle>,
1044 _subscription: Subscription,
1045}
1046
1047#[derive(Debug, PartialEq, Eq)]
1048struct ActiveDiagnosticGroup {
1049 primary_range: Range<Anchor>,
1050 primary_message: String,
1051 group_id: usize,
1052 blocks: HashMap<CustomBlockId, Diagnostic>,
1053 is_valid: bool,
1054}
1055
1056#[derive(Serialize, Deserialize, Clone, Debug)]
1057pub struct ClipboardSelection {
1058 /// The number of bytes in this selection.
1059 pub len: usize,
1060 /// Whether this was a full-line selection.
1061 pub is_entire_line: bool,
1062 /// The indentation of the first line when this content was originally copied.
1063 pub first_line_indent: u32,
1064}
1065
1066// selections, scroll behavior, was newest selection reversed
1067type SelectSyntaxNodeHistoryState = (
1068 Box<[Selection<usize>]>,
1069 SelectSyntaxNodeScrollBehavior,
1070 bool,
1071);
1072
1073#[derive(Default)]
1074struct SelectSyntaxNodeHistory {
1075 stack: Vec<SelectSyntaxNodeHistoryState>,
1076 // disable temporarily to allow changing selections without losing the stack
1077 pub disable_clearing: bool,
1078}
1079
1080impl SelectSyntaxNodeHistory {
1081 pub fn try_clear(&mut self) {
1082 if !self.disable_clearing {
1083 self.stack.clear();
1084 }
1085 }
1086
1087 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1088 self.stack.push(selection);
1089 }
1090
1091 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1092 self.stack.pop()
1093 }
1094}
1095
1096enum SelectSyntaxNodeScrollBehavior {
1097 CursorTop,
1098 CenterSelection,
1099 CursorBottom,
1100}
1101
1102#[derive(Debug)]
1103pub(crate) struct NavigationData {
1104 cursor_anchor: Anchor,
1105 cursor_position: Point,
1106 scroll_anchor: ScrollAnchor,
1107 scroll_top_row: u32,
1108}
1109
1110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1111pub enum GotoDefinitionKind {
1112 Symbol,
1113 Declaration,
1114 Type,
1115 Implementation,
1116}
1117
1118#[derive(Debug, Clone)]
1119enum InlayHintRefreshReason {
1120 ModifiersChanged(bool),
1121 Toggle(bool),
1122 SettingsChange(InlayHintSettings),
1123 NewLinesShown,
1124 BufferEdited(HashSet<Arc<Language>>),
1125 RefreshRequested,
1126 ExcerptsRemoved(Vec<ExcerptId>),
1127}
1128
1129impl InlayHintRefreshReason {
1130 fn description(&self) -> &'static str {
1131 match self {
1132 Self::ModifiersChanged(_) => "modifiers changed",
1133 Self::Toggle(_) => "toggle",
1134 Self::SettingsChange(_) => "settings change",
1135 Self::NewLinesShown => "new lines shown",
1136 Self::BufferEdited(_) => "buffer edited",
1137 Self::RefreshRequested => "refresh requested",
1138 Self::ExcerptsRemoved(_) => "excerpts removed",
1139 }
1140 }
1141}
1142
1143pub enum FormatTarget {
1144 Buffers,
1145 Ranges(Vec<Range<MultiBufferPoint>>),
1146}
1147
1148pub(crate) struct FocusedBlock {
1149 id: BlockId,
1150 focus_handle: WeakFocusHandle,
1151}
1152
1153#[derive(Clone)]
1154enum JumpData {
1155 MultiBufferRow {
1156 row: MultiBufferRow,
1157 line_offset_from_top: u32,
1158 },
1159 MultiBufferPoint {
1160 excerpt_id: ExcerptId,
1161 position: Point,
1162 anchor: text::Anchor,
1163 line_offset_from_top: u32,
1164 },
1165}
1166
1167pub enum MultibufferSelectionMode {
1168 First,
1169 All,
1170}
1171
1172#[derive(Clone, Copy, Debug, Default)]
1173pub struct RewrapOptions {
1174 pub override_language_settings: bool,
1175 pub preserve_existing_whitespace: bool,
1176}
1177
1178impl Editor {
1179 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1180 let buffer = cx.new(|cx| Buffer::local("", cx));
1181 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1182 Self::new(
1183 EditorMode::SingleLine { auto_width: false },
1184 buffer,
1185 None,
1186 window,
1187 cx,
1188 )
1189 }
1190
1191 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1192 let buffer = cx.new(|cx| Buffer::local("", cx));
1193 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1194 Self::new(EditorMode::Full, buffer, None, window, cx)
1195 }
1196
1197 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1198 let buffer = cx.new(|cx| Buffer::local("", cx));
1199 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1200 Self::new(
1201 EditorMode::SingleLine { auto_width: true },
1202 buffer,
1203 None,
1204 window,
1205 cx,
1206 )
1207 }
1208
1209 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1210 let buffer = cx.new(|cx| Buffer::local("", cx));
1211 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1212 Self::new(
1213 EditorMode::AutoHeight { max_lines },
1214 buffer,
1215 None,
1216 window,
1217 cx,
1218 )
1219 }
1220
1221 pub fn for_buffer(
1222 buffer: Entity<Buffer>,
1223 project: Option<Entity<Project>>,
1224 window: &mut Window,
1225 cx: &mut Context<Self>,
1226 ) -> Self {
1227 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1228 Self::new(EditorMode::Full, buffer, project, window, cx)
1229 }
1230
1231 pub fn for_multibuffer(
1232 buffer: Entity<MultiBuffer>,
1233 project: Option<Entity<Project>>,
1234 window: &mut Window,
1235 cx: &mut Context<Self>,
1236 ) -> Self {
1237 Self::new(EditorMode::Full, buffer, project, window, cx)
1238 }
1239
1240 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1241 let mut clone = Self::new(
1242 self.mode,
1243 self.buffer.clone(),
1244 self.project.clone(),
1245 window,
1246 cx,
1247 );
1248 self.display_map.update(cx, |display_map, cx| {
1249 let snapshot = display_map.snapshot(cx);
1250 clone.display_map.update(cx, |display_map, cx| {
1251 display_map.set_state(&snapshot, cx);
1252 });
1253 });
1254 clone.folds_did_change(cx);
1255 clone.selections.clone_state(&self.selections);
1256 clone.scroll_manager.clone_state(&self.scroll_manager);
1257 clone.searchable = self.searchable;
1258 clone
1259 }
1260
1261 pub fn new(
1262 mode: EditorMode,
1263 buffer: Entity<MultiBuffer>,
1264 project: Option<Entity<Project>>,
1265 window: &mut Window,
1266 cx: &mut Context<Self>,
1267 ) -> Self {
1268 let style = window.text_style();
1269 let font_size = style.font_size.to_pixels(window.rem_size());
1270 let editor = cx.entity().downgrade();
1271 let fold_placeholder = FoldPlaceholder {
1272 constrain_width: true,
1273 render: Arc::new(move |fold_id, fold_range, cx| {
1274 let editor = editor.clone();
1275 div()
1276 .id(fold_id)
1277 .bg(cx.theme().colors().ghost_element_background)
1278 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1279 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1280 .rounded_xs()
1281 .size_full()
1282 .cursor_pointer()
1283 .child("⋯")
1284 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1285 .on_click(move |_, _window, cx| {
1286 editor
1287 .update(cx, |editor, cx| {
1288 editor.unfold_ranges(
1289 &[fold_range.start..fold_range.end],
1290 true,
1291 false,
1292 cx,
1293 );
1294 cx.stop_propagation();
1295 })
1296 .ok();
1297 })
1298 .into_any()
1299 }),
1300 merge_adjacent: true,
1301 ..Default::default()
1302 };
1303 let display_map = cx.new(|cx| {
1304 DisplayMap::new(
1305 buffer.clone(),
1306 style.font(),
1307 font_size,
1308 None,
1309 FILE_HEADER_HEIGHT,
1310 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1311 fold_placeholder,
1312 cx,
1313 )
1314 });
1315
1316 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1317
1318 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1319
1320 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1321 .then(|| language_settings::SoftWrap::None);
1322
1323 let mut project_subscriptions = Vec::new();
1324 if mode == EditorMode::Full {
1325 if let Some(project) = project.as_ref() {
1326 project_subscriptions.push(cx.subscribe_in(
1327 project,
1328 window,
1329 |editor, _, event, window, cx| match event {
1330 project::Event::RefreshCodeLens => {
1331 // we always query lens with actions, without storing them, always refreshing them
1332 }
1333 project::Event::RefreshInlayHints => {
1334 editor
1335 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1336 }
1337 project::Event::SnippetEdit(id, snippet_edits) => {
1338 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1339 let focus_handle = editor.focus_handle(cx);
1340 if focus_handle.is_focused(window) {
1341 let snapshot = buffer.read(cx).snapshot();
1342 for (range, snippet) in snippet_edits {
1343 let editor_range =
1344 language::range_from_lsp(*range).to_offset(&snapshot);
1345 editor
1346 .insert_snippet(
1347 &[editor_range],
1348 snippet.clone(),
1349 window,
1350 cx,
1351 )
1352 .ok();
1353 }
1354 }
1355 }
1356 }
1357 _ => {}
1358 },
1359 ));
1360 if let Some(task_inventory) = project
1361 .read(cx)
1362 .task_store()
1363 .read(cx)
1364 .task_inventory()
1365 .cloned()
1366 {
1367 project_subscriptions.push(cx.observe_in(
1368 &task_inventory,
1369 window,
1370 |editor, _, window, cx| {
1371 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1372 },
1373 ));
1374 };
1375
1376 project_subscriptions.push(cx.subscribe_in(
1377 &project.read(cx).breakpoint_store(),
1378 window,
1379 |editor, _, event, window, cx| match event {
1380 BreakpointStoreEvent::ActiveDebugLineChanged => {
1381 editor.go_to_active_debug_line(window, cx);
1382 }
1383 _ => {}
1384 },
1385 ));
1386 }
1387 }
1388
1389 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1390
1391 let inlay_hint_settings =
1392 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1393 let focus_handle = cx.focus_handle();
1394 cx.on_focus(&focus_handle, window, Self::handle_focus)
1395 .detach();
1396 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1397 .detach();
1398 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1399 .detach();
1400 cx.on_blur(&focus_handle, window, Self::handle_blur)
1401 .detach();
1402
1403 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1404 Some(false)
1405 } else {
1406 None
1407 };
1408
1409 let breakpoint_store = match (mode, project.as_ref()) {
1410 (EditorMode::Full, Some(project)) => Some(project.read(cx).breakpoint_store()),
1411 _ => None,
1412 };
1413
1414 let mut code_action_providers = Vec::new();
1415 let mut load_uncommitted_diff = None;
1416 if let Some(project) = project.clone() {
1417 load_uncommitted_diff = Some(
1418 get_uncommitted_diff_for_buffer(
1419 &project,
1420 buffer.read(cx).all_buffers(),
1421 buffer.clone(),
1422 cx,
1423 )
1424 .shared(),
1425 );
1426 code_action_providers.push(Rc::new(project) as Rc<_>);
1427 }
1428
1429 let mut this = Self {
1430 focus_handle,
1431 show_cursor_when_unfocused: false,
1432 last_focused_descendant: None,
1433 buffer: buffer.clone(),
1434 display_map: display_map.clone(),
1435 selections,
1436 scroll_manager: ScrollManager::new(cx),
1437 columnar_selection_tail: None,
1438 add_selections_state: None,
1439 select_next_state: None,
1440 select_prev_state: None,
1441 selection_history: Default::default(),
1442 autoclose_regions: Default::default(),
1443 snippet_stack: Default::default(),
1444 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1445 ime_transaction: Default::default(),
1446 active_diagnostics: None,
1447 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1448 inline_diagnostics_update: Task::ready(()),
1449 inline_diagnostics: Vec::new(),
1450 soft_wrap_mode_override,
1451 hard_wrap: None,
1452 completion_provider: project.clone().map(|project| Box::new(project) as _),
1453 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1454 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1455 project,
1456 blink_manager: blink_manager.clone(),
1457 show_local_selections: true,
1458 show_scrollbars: true,
1459 mode,
1460 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1461 show_gutter: mode == EditorMode::Full,
1462 show_line_numbers: None,
1463 use_relative_line_numbers: None,
1464 show_git_diff_gutter: None,
1465 show_code_actions: None,
1466 show_runnables: None,
1467 show_breakpoints: None,
1468 show_wrap_guides: None,
1469 show_indent_guides,
1470 placeholder_text: None,
1471 highlight_order: 0,
1472 highlighted_rows: HashMap::default(),
1473 background_highlights: Default::default(),
1474 gutter_highlights: TreeMap::default(),
1475 scrollbar_marker_state: ScrollbarMarkerState::default(),
1476 active_indent_guides_state: ActiveIndentGuidesState::default(),
1477 nav_history: None,
1478 context_menu: RefCell::new(None),
1479 context_menu_options: None,
1480 mouse_context_menu: None,
1481 completion_tasks: Default::default(),
1482 signature_help_state: SignatureHelpState::default(),
1483 auto_signature_help: None,
1484 find_all_references_task_sources: Vec::new(),
1485 next_completion_id: 0,
1486 next_inlay_id: 0,
1487 code_action_providers,
1488 available_code_actions: Default::default(),
1489 code_actions_task: Default::default(),
1490 selection_highlight_task: Default::default(),
1491 document_highlights_task: Default::default(),
1492 linked_editing_range_task: Default::default(),
1493 pending_rename: Default::default(),
1494 searchable: true,
1495 cursor_shape: EditorSettings::get_global(cx)
1496 .cursor_shape
1497 .unwrap_or_default(),
1498 current_line_highlight: None,
1499 autoindent_mode: Some(AutoindentMode::EachLine),
1500 collapse_matches: false,
1501 workspace: None,
1502 input_enabled: true,
1503 use_modal_editing: mode == EditorMode::Full,
1504 read_only: false,
1505 use_autoclose: true,
1506 use_auto_surround: true,
1507 auto_replace_emoji_shortcode: false,
1508 jsx_tag_auto_close_enabled_in_any_buffer: false,
1509 leader_peer_id: None,
1510 remote_id: None,
1511 hover_state: Default::default(),
1512 pending_mouse_down: None,
1513 hovered_link_state: Default::default(),
1514 edit_prediction_provider: None,
1515 active_inline_completion: None,
1516 stale_inline_completion_in_menu: None,
1517 edit_prediction_preview: EditPredictionPreview::Inactive {
1518 released_too_fast: false,
1519 },
1520 inline_diagnostics_enabled: mode == EditorMode::Full,
1521 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1522
1523 gutter_hovered: false,
1524 pixel_position_of_newest_cursor: None,
1525 last_bounds: None,
1526 last_position_map: None,
1527 expect_bounds_change: None,
1528 gutter_dimensions: GutterDimensions::default(),
1529 style: None,
1530 show_cursor_names: false,
1531 hovered_cursors: Default::default(),
1532 next_editor_action_id: EditorActionId::default(),
1533 editor_actions: Rc::default(),
1534 inline_completions_hidden_for_vim_mode: false,
1535 show_inline_completions_override: None,
1536 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1537 edit_prediction_settings: EditPredictionSettings::Disabled,
1538 edit_prediction_indent_conflict: false,
1539 edit_prediction_requires_modifier_in_indent_conflict: true,
1540 custom_context_menu: None,
1541 show_git_blame_gutter: false,
1542 show_git_blame_inline: false,
1543 show_selection_menu: None,
1544 show_git_blame_inline_delay_task: None,
1545 git_blame_inline_tooltip: None,
1546 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1547 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1548 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1549 .session
1550 .restore_unsaved_buffers,
1551 blame: None,
1552 blame_subscription: None,
1553 tasks: Default::default(),
1554
1555 breakpoint_store,
1556 gutter_breakpoint_indicator: (None, None),
1557 _subscriptions: vec![
1558 cx.observe(&buffer, Self::on_buffer_changed),
1559 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1560 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1561 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1562 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1563 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1564 cx.observe_window_activation(window, |editor, window, cx| {
1565 let active = window.is_window_active();
1566 editor.blink_manager.update(cx, |blink_manager, cx| {
1567 if active {
1568 blink_manager.enable(cx);
1569 } else {
1570 blink_manager.disable(cx);
1571 }
1572 });
1573 }),
1574 ],
1575 tasks_update_task: None,
1576 linked_edit_ranges: Default::default(),
1577 in_project_search: false,
1578 previous_search_ranges: None,
1579 breadcrumb_header: None,
1580 focused_block: None,
1581 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1582 addons: HashMap::default(),
1583 registered_buffers: HashMap::default(),
1584 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1585 selection_mark_mode: false,
1586 toggle_fold_multiple_buffers: Task::ready(()),
1587 serialize_selections: Task::ready(()),
1588 serialize_folds: Task::ready(()),
1589 text_style_refinement: None,
1590 load_diff_task: load_uncommitted_diff,
1591 mouse_cursor_hidden: false,
1592 hide_mouse_mode: EditorSettings::get_global(cx)
1593 .hide_mouse
1594 .unwrap_or_default(),
1595 };
1596 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1597 this._subscriptions
1598 .push(cx.observe(breakpoints, |_, _, cx| {
1599 cx.notify();
1600 }));
1601 }
1602 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1603 this._subscriptions.extend(project_subscriptions);
1604 this._subscriptions
1605 .push(cx.subscribe_self(|editor, e: &EditorEvent, cx| {
1606 if let EditorEvent::SelectionsChanged { local } = e {
1607 if *local {
1608 let new_anchor = editor.scroll_manager.anchor();
1609 editor.update_restoration_data(cx, move |data| {
1610 data.scroll_anchor = new_anchor;
1611 });
1612 }
1613 }
1614 }));
1615
1616 this.end_selection(window, cx);
1617 this.scroll_manager.show_scrollbars(window, cx);
1618 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1619
1620 if mode == EditorMode::Full {
1621 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1622 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1623
1624 if this.git_blame_inline_enabled {
1625 this.git_blame_inline_enabled = true;
1626 this.start_git_blame_inline(false, window, cx);
1627 }
1628
1629 this.go_to_active_debug_line(window, cx);
1630
1631 if let Some(buffer) = buffer.read(cx).as_singleton() {
1632 if let Some(project) = this.project.as_ref() {
1633 let handle = project.update(cx, |project, cx| {
1634 project.register_buffer_with_language_servers(&buffer, cx)
1635 });
1636 this.registered_buffers
1637 .insert(buffer.read(cx).remote_id(), handle);
1638 }
1639 }
1640 }
1641
1642 this.report_editor_event("Editor Opened", None, cx);
1643 this
1644 }
1645
1646 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1647 self.mouse_context_menu
1648 .as_ref()
1649 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1650 }
1651
1652 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1653 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1654 }
1655
1656 fn key_context_internal(
1657 &self,
1658 has_active_edit_prediction: bool,
1659 window: &Window,
1660 cx: &App,
1661 ) -> KeyContext {
1662 let mut key_context = KeyContext::new_with_defaults();
1663 key_context.add("Editor");
1664 let mode = match self.mode {
1665 EditorMode::SingleLine { .. } => "single_line",
1666 EditorMode::AutoHeight { .. } => "auto_height",
1667 EditorMode::Full => "full",
1668 };
1669
1670 if EditorSettings::jupyter_enabled(cx) {
1671 key_context.add("jupyter");
1672 }
1673
1674 key_context.set("mode", mode);
1675 if self.pending_rename.is_some() {
1676 key_context.add("renaming");
1677 }
1678
1679 match self.context_menu.borrow().as_ref() {
1680 Some(CodeContextMenu::Completions(_)) => {
1681 key_context.add("menu");
1682 key_context.add("showing_completions");
1683 }
1684 Some(CodeContextMenu::CodeActions(_)) => {
1685 key_context.add("menu");
1686 key_context.add("showing_code_actions")
1687 }
1688 None => {}
1689 }
1690
1691 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1692 if !self.focus_handle(cx).contains_focused(window, cx)
1693 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1694 {
1695 for addon in self.addons.values() {
1696 addon.extend_key_context(&mut key_context, cx)
1697 }
1698 }
1699
1700 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1701 if let Some(extension) = singleton_buffer
1702 .read(cx)
1703 .file()
1704 .and_then(|file| file.path().extension()?.to_str())
1705 {
1706 key_context.set("extension", extension.to_string());
1707 }
1708 } else {
1709 key_context.add("multibuffer");
1710 }
1711
1712 if has_active_edit_prediction {
1713 if self.edit_prediction_in_conflict() {
1714 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1715 } else {
1716 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1717 key_context.add("copilot_suggestion");
1718 }
1719 }
1720
1721 if self.selection_mark_mode {
1722 key_context.add("selection_mode");
1723 }
1724
1725 key_context
1726 }
1727
1728 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1729 self.mouse_cursor_hidden = match origin {
1730 HideMouseCursorOrigin::TypingAction => {
1731 matches!(
1732 self.hide_mouse_mode,
1733 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1734 )
1735 }
1736 HideMouseCursorOrigin::MovementAction => {
1737 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1738 }
1739 };
1740 }
1741
1742 pub fn edit_prediction_in_conflict(&self) -> bool {
1743 if !self.show_edit_predictions_in_menu() {
1744 return false;
1745 }
1746
1747 let showing_completions = self
1748 .context_menu
1749 .borrow()
1750 .as_ref()
1751 .map_or(false, |context| {
1752 matches!(context, CodeContextMenu::Completions(_))
1753 });
1754
1755 showing_completions
1756 || self.edit_prediction_requires_modifier()
1757 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1758 // bindings to insert tab characters.
1759 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1760 }
1761
1762 pub fn accept_edit_prediction_keybind(
1763 &self,
1764 window: &Window,
1765 cx: &App,
1766 ) -> AcceptEditPredictionBinding {
1767 let key_context = self.key_context_internal(true, window, cx);
1768 let in_conflict = self.edit_prediction_in_conflict();
1769
1770 AcceptEditPredictionBinding(
1771 window
1772 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1773 .into_iter()
1774 .filter(|binding| {
1775 !in_conflict
1776 || binding
1777 .keystrokes()
1778 .first()
1779 .map_or(false, |keystroke| keystroke.modifiers.modified())
1780 })
1781 .rev()
1782 .min_by_key(|binding| {
1783 binding
1784 .keystrokes()
1785 .first()
1786 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1787 }),
1788 )
1789 }
1790
1791 pub fn new_file(
1792 workspace: &mut Workspace,
1793 _: &workspace::NewFile,
1794 window: &mut Window,
1795 cx: &mut Context<Workspace>,
1796 ) {
1797 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1798 "Failed to create buffer",
1799 window,
1800 cx,
1801 |e, _, _| match e.error_code() {
1802 ErrorCode::RemoteUpgradeRequired => Some(format!(
1803 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1804 e.error_tag("required").unwrap_or("the latest version")
1805 )),
1806 _ => None,
1807 },
1808 );
1809 }
1810
1811 pub fn new_in_workspace(
1812 workspace: &mut Workspace,
1813 window: &mut Window,
1814 cx: &mut Context<Workspace>,
1815 ) -> Task<Result<Entity<Editor>>> {
1816 let project = workspace.project().clone();
1817 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1818
1819 cx.spawn_in(window, async move |workspace, cx| {
1820 let buffer = create.await?;
1821 workspace.update_in(cx, |workspace, window, cx| {
1822 let editor =
1823 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1824 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1825 editor
1826 })
1827 })
1828 }
1829
1830 fn new_file_vertical(
1831 workspace: &mut Workspace,
1832 _: &workspace::NewFileSplitVertical,
1833 window: &mut Window,
1834 cx: &mut Context<Workspace>,
1835 ) {
1836 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1837 }
1838
1839 fn new_file_horizontal(
1840 workspace: &mut Workspace,
1841 _: &workspace::NewFileSplitHorizontal,
1842 window: &mut Window,
1843 cx: &mut Context<Workspace>,
1844 ) {
1845 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1846 }
1847
1848 fn new_file_in_direction(
1849 workspace: &mut Workspace,
1850 direction: SplitDirection,
1851 window: &mut Window,
1852 cx: &mut Context<Workspace>,
1853 ) {
1854 let project = workspace.project().clone();
1855 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1856
1857 cx.spawn_in(window, async move |workspace, cx| {
1858 let buffer = create.await?;
1859 workspace.update_in(cx, move |workspace, window, cx| {
1860 workspace.split_item(
1861 direction,
1862 Box::new(
1863 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1864 ),
1865 window,
1866 cx,
1867 )
1868 })?;
1869 anyhow::Ok(())
1870 })
1871 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1872 match e.error_code() {
1873 ErrorCode::RemoteUpgradeRequired => Some(format!(
1874 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1875 e.error_tag("required").unwrap_or("the latest version")
1876 )),
1877 _ => None,
1878 }
1879 });
1880 }
1881
1882 pub fn leader_peer_id(&self) -> Option<PeerId> {
1883 self.leader_peer_id
1884 }
1885
1886 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1887 &self.buffer
1888 }
1889
1890 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1891 self.workspace.as_ref()?.0.upgrade()
1892 }
1893
1894 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1895 self.buffer().read(cx).title(cx)
1896 }
1897
1898 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1899 let git_blame_gutter_max_author_length = self
1900 .render_git_blame_gutter(cx)
1901 .then(|| {
1902 if let Some(blame) = self.blame.as_ref() {
1903 let max_author_length =
1904 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1905 Some(max_author_length)
1906 } else {
1907 None
1908 }
1909 })
1910 .flatten();
1911
1912 EditorSnapshot {
1913 mode: self.mode,
1914 show_gutter: self.show_gutter,
1915 show_line_numbers: self.show_line_numbers,
1916 show_git_diff_gutter: self.show_git_diff_gutter,
1917 show_code_actions: self.show_code_actions,
1918 show_runnables: self.show_runnables,
1919 show_breakpoints: self.show_breakpoints,
1920 git_blame_gutter_max_author_length,
1921 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
1922 scroll_anchor: self.scroll_manager.anchor(),
1923 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
1924 placeholder_text: self.placeholder_text.clone(),
1925 is_focused: self.focus_handle.is_focused(window),
1926 current_line_highlight: self
1927 .current_line_highlight
1928 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
1929 gutter_hovered: self.gutter_hovered,
1930 }
1931 }
1932
1933 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
1934 self.buffer.read(cx).language_at(point, cx)
1935 }
1936
1937 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
1938 self.buffer.read(cx).read(cx).file_at(point).cloned()
1939 }
1940
1941 pub fn active_excerpt(
1942 &self,
1943 cx: &App,
1944 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
1945 self.buffer
1946 .read(cx)
1947 .excerpt_containing(self.selections.newest_anchor().head(), cx)
1948 }
1949
1950 pub fn mode(&self) -> EditorMode {
1951 self.mode
1952 }
1953
1954 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
1955 self.collaboration_hub.as_deref()
1956 }
1957
1958 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
1959 self.collaboration_hub = Some(hub);
1960 }
1961
1962 pub fn set_in_project_search(&mut self, in_project_search: bool) {
1963 self.in_project_search = in_project_search;
1964 }
1965
1966 pub fn set_custom_context_menu(
1967 &mut self,
1968 f: impl 'static
1969 + Fn(
1970 &mut Self,
1971 DisplayPoint,
1972 &mut Window,
1973 &mut Context<Self>,
1974 ) -> Option<Entity<ui::ContextMenu>>,
1975 ) {
1976 self.custom_context_menu = Some(Box::new(f))
1977 }
1978
1979 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
1980 self.completion_provider = provider;
1981 }
1982
1983 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
1984 self.semantics_provider.clone()
1985 }
1986
1987 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
1988 self.semantics_provider = provider;
1989 }
1990
1991 pub fn set_edit_prediction_provider<T>(
1992 &mut self,
1993 provider: Option<Entity<T>>,
1994 window: &mut Window,
1995 cx: &mut Context<Self>,
1996 ) where
1997 T: EditPredictionProvider,
1998 {
1999 self.edit_prediction_provider =
2000 provider.map(|provider| RegisteredInlineCompletionProvider {
2001 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2002 if this.focus_handle.is_focused(window) {
2003 this.update_visible_inline_completion(window, cx);
2004 }
2005 }),
2006 provider: Arc::new(provider),
2007 });
2008 self.update_edit_prediction_settings(cx);
2009 self.refresh_inline_completion(false, false, window, cx);
2010 }
2011
2012 pub fn placeholder_text(&self) -> Option<&str> {
2013 self.placeholder_text.as_deref()
2014 }
2015
2016 pub fn set_placeholder_text(
2017 &mut self,
2018 placeholder_text: impl Into<Arc<str>>,
2019 cx: &mut Context<Self>,
2020 ) {
2021 let placeholder_text = Some(placeholder_text.into());
2022 if self.placeholder_text != placeholder_text {
2023 self.placeholder_text = placeholder_text;
2024 cx.notify();
2025 }
2026 }
2027
2028 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2029 self.cursor_shape = cursor_shape;
2030
2031 // Disrupt blink for immediate user feedback that the cursor shape has changed
2032 self.blink_manager.update(cx, BlinkManager::show_cursor);
2033
2034 cx.notify();
2035 }
2036
2037 pub fn set_current_line_highlight(
2038 &mut self,
2039 current_line_highlight: Option<CurrentLineHighlight>,
2040 ) {
2041 self.current_line_highlight = current_line_highlight;
2042 }
2043
2044 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2045 self.collapse_matches = collapse_matches;
2046 }
2047
2048 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2049 let buffers = self.buffer.read(cx).all_buffers();
2050 let Some(project) = self.project.as_ref() else {
2051 return;
2052 };
2053 project.update(cx, |project, cx| {
2054 for buffer in buffers {
2055 self.registered_buffers
2056 .entry(buffer.read(cx).remote_id())
2057 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2058 }
2059 })
2060 }
2061
2062 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2063 if self.collapse_matches {
2064 return range.start..range.start;
2065 }
2066 range.clone()
2067 }
2068
2069 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2070 if self.display_map.read(cx).clip_at_line_ends != clip {
2071 self.display_map
2072 .update(cx, |map, _| map.clip_at_line_ends = clip);
2073 }
2074 }
2075
2076 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2077 self.input_enabled = input_enabled;
2078 }
2079
2080 pub fn set_inline_completions_hidden_for_vim_mode(
2081 &mut self,
2082 hidden: bool,
2083 window: &mut Window,
2084 cx: &mut Context<Self>,
2085 ) {
2086 if hidden != self.inline_completions_hidden_for_vim_mode {
2087 self.inline_completions_hidden_for_vim_mode = hidden;
2088 if hidden {
2089 self.update_visible_inline_completion(window, cx);
2090 } else {
2091 self.refresh_inline_completion(true, false, window, cx);
2092 }
2093 }
2094 }
2095
2096 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2097 self.menu_inline_completions_policy = value;
2098 }
2099
2100 pub fn set_autoindent(&mut self, autoindent: bool) {
2101 if autoindent {
2102 self.autoindent_mode = Some(AutoindentMode::EachLine);
2103 } else {
2104 self.autoindent_mode = None;
2105 }
2106 }
2107
2108 pub fn read_only(&self, cx: &App) -> bool {
2109 self.read_only || self.buffer.read(cx).read_only()
2110 }
2111
2112 pub fn set_read_only(&mut self, read_only: bool) {
2113 self.read_only = read_only;
2114 }
2115
2116 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2117 self.use_autoclose = autoclose;
2118 }
2119
2120 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2121 self.use_auto_surround = auto_surround;
2122 }
2123
2124 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2125 self.auto_replace_emoji_shortcode = auto_replace;
2126 }
2127
2128 pub fn toggle_edit_predictions(
2129 &mut self,
2130 _: &ToggleEditPrediction,
2131 window: &mut Window,
2132 cx: &mut Context<Self>,
2133 ) {
2134 if self.show_inline_completions_override.is_some() {
2135 self.set_show_edit_predictions(None, window, cx);
2136 } else {
2137 let show_edit_predictions = !self.edit_predictions_enabled();
2138 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2139 }
2140 }
2141
2142 pub fn set_show_edit_predictions(
2143 &mut self,
2144 show_edit_predictions: Option<bool>,
2145 window: &mut Window,
2146 cx: &mut Context<Self>,
2147 ) {
2148 self.show_inline_completions_override = show_edit_predictions;
2149 self.update_edit_prediction_settings(cx);
2150
2151 if let Some(false) = show_edit_predictions {
2152 self.discard_inline_completion(false, cx);
2153 } else {
2154 self.refresh_inline_completion(false, true, window, cx);
2155 }
2156 }
2157
2158 fn inline_completions_disabled_in_scope(
2159 &self,
2160 buffer: &Entity<Buffer>,
2161 buffer_position: language::Anchor,
2162 cx: &App,
2163 ) -> bool {
2164 let snapshot = buffer.read(cx).snapshot();
2165 let settings = snapshot.settings_at(buffer_position, cx);
2166
2167 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2168 return false;
2169 };
2170
2171 scope.override_name().map_or(false, |scope_name| {
2172 settings
2173 .edit_predictions_disabled_in
2174 .iter()
2175 .any(|s| s == scope_name)
2176 })
2177 }
2178
2179 pub fn set_use_modal_editing(&mut self, to: bool) {
2180 self.use_modal_editing = to;
2181 }
2182
2183 pub fn use_modal_editing(&self) -> bool {
2184 self.use_modal_editing
2185 }
2186
2187 fn selections_did_change(
2188 &mut self,
2189 local: bool,
2190 old_cursor_position: &Anchor,
2191 show_completions: bool,
2192 window: &mut Window,
2193 cx: &mut Context<Self>,
2194 ) {
2195 window.invalidate_character_coordinates();
2196
2197 // Copy selections to primary selection buffer
2198 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2199 if local {
2200 let selections = self.selections.all::<usize>(cx);
2201 let buffer_handle = self.buffer.read(cx).read(cx);
2202
2203 let mut text = String::new();
2204 for (index, selection) in selections.iter().enumerate() {
2205 let text_for_selection = buffer_handle
2206 .text_for_range(selection.start..selection.end)
2207 .collect::<String>();
2208
2209 text.push_str(&text_for_selection);
2210 if index != selections.len() - 1 {
2211 text.push('\n');
2212 }
2213 }
2214
2215 if !text.is_empty() {
2216 cx.write_to_primary(ClipboardItem::new_string(text));
2217 }
2218 }
2219
2220 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2221 self.buffer.update(cx, |buffer, cx| {
2222 buffer.set_active_selections(
2223 &self.selections.disjoint_anchors(),
2224 self.selections.line_mode,
2225 self.cursor_shape,
2226 cx,
2227 )
2228 });
2229 }
2230 let display_map = self
2231 .display_map
2232 .update(cx, |display_map, cx| display_map.snapshot(cx));
2233 let buffer = &display_map.buffer_snapshot;
2234 self.add_selections_state = None;
2235 self.select_next_state = None;
2236 self.select_prev_state = None;
2237 self.select_syntax_node_history.try_clear();
2238 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2239 self.snippet_stack
2240 .invalidate(&self.selections.disjoint_anchors(), buffer);
2241 self.take_rename(false, window, cx);
2242
2243 let new_cursor_position = self.selections.newest_anchor().head();
2244
2245 self.push_to_nav_history(
2246 *old_cursor_position,
2247 Some(new_cursor_position.to_point(buffer)),
2248 false,
2249 cx,
2250 );
2251
2252 if local {
2253 let new_cursor_position = self.selections.newest_anchor().head();
2254 let mut context_menu = self.context_menu.borrow_mut();
2255 let completion_menu = match context_menu.as_ref() {
2256 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2257 _ => {
2258 *context_menu = None;
2259 None
2260 }
2261 };
2262 if let Some(buffer_id) = new_cursor_position.buffer_id {
2263 if !self.registered_buffers.contains_key(&buffer_id) {
2264 if let Some(project) = self.project.as_ref() {
2265 project.update(cx, |project, cx| {
2266 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2267 return;
2268 };
2269 self.registered_buffers.insert(
2270 buffer_id,
2271 project.register_buffer_with_language_servers(&buffer, cx),
2272 );
2273 })
2274 }
2275 }
2276 }
2277
2278 if let Some(completion_menu) = completion_menu {
2279 let cursor_position = new_cursor_position.to_offset(buffer);
2280 let (word_range, kind) =
2281 buffer.surrounding_word(completion_menu.initial_position, true);
2282 if kind == Some(CharKind::Word)
2283 && word_range.to_inclusive().contains(&cursor_position)
2284 {
2285 let mut completion_menu = completion_menu.clone();
2286 drop(context_menu);
2287
2288 let query = Self::completion_query(buffer, cursor_position);
2289 cx.spawn(async move |this, cx| {
2290 completion_menu
2291 .filter(query.as_deref(), cx.background_executor().clone())
2292 .await;
2293
2294 this.update(cx, |this, cx| {
2295 let mut context_menu = this.context_menu.borrow_mut();
2296 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2297 else {
2298 return;
2299 };
2300
2301 if menu.id > completion_menu.id {
2302 return;
2303 }
2304
2305 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2306 drop(context_menu);
2307 cx.notify();
2308 })
2309 })
2310 .detach();
2311
2312 if show_completions {
2313 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2314 }
2315 } else {
2316 drop(context_menu);
2317 self.hide_context_menu(window, cx);
2318 }
2319 } else {
2320 drop(context_menu);
2321 }
2322
2323 hide_hover(self, cx);
2324
2325 if old_cursor_position.to_display_point(&display_map).row()
2326 != new_cursor_position.to_display_point(&display_map).row()
2327 {
2328 self.available_code_actions.take();
2329 }
2330 self.refresh_code_actions(window, cx);
2331 self.refresh_document_highlights(cx);
2332 self.refresh_selected_text_highlights(window, cx);
2333 refresh_matching_bracket_highlights(self, window, cx);
2334 self.update_visible_inline_completion(window, cx);
2335 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2336 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2337 if self.git_blame_inline_enabled {
2338 self.start_inline_blame_timer(window, cx);
2339 }
2340 }
2341
2342 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2343 cx.emit(EditorEvent::SelectionsChanged { local });
2344
2345 let selections = &self.selections.disjoint;
2346 if selections.len() == 1 {
2347 cx.emit(SearchEvent::ActiveMatchChanged)
2348 }
2349 if local && self.is_singleton(cx) {
2350 let inmemory_selections = selections.iter().map(|s| s.range()).collect();
2351 self.update_restoration_data(cx, |data| {
2352 data.selections = inmemory_selections;
2353 });
2354
2355 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
2356 {
2357 if let Some(workspace_id) =
2358 self.workspace.as_ref().and_then(|workspace| workspace.1)
2359 {
2360 let snapshot = self.buffer().read(cx).snapshot(cx);
2361 let selections = selections.clone();
2362 let background_executor = cx.background_executor().clone();
2363 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2364 self.serialize_selections = cx.background_spawn(async move {
2365 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2366 let db_selections = selections
2367 .iter()
2368 .map(|selection| {
2369 (
2370 selection.start.to_offset(&snapshot),
2371 selection.end.to_offset(&snapshot),
2372 )
2373 })
2374 .collect();
2375
2376 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2377 .await
2378 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2379 .log_err();
2380 });
2381 }
2382 }
2383 }
2384
2385 cx.notify();
2386 }
2387
2388 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2389 if !self.is_singleton(cx)
2390 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2391 {
2392 return;
2393 }
2394
2395 let snapshot = self.buffer().read(cx).snapshot(cx);
2396 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2397 display_map
2398 .snapshot(cx)
2399 .folds_in_range(0..snapshot.len())
2400 .map(|fold| fold.range.deref().clone())
2401 .collect()
2402 });
2403 self.update_restoration_data(cx, |data| {
2404 data.folds = inmemory_folds;
2405 });
2406
2407 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2408 return;
2409 };
2410 let background_executor = cx.background_executor().clone();
2411 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2412 let db_folds = self.display_map.update(cx, |display_map, cx| {
2413 display_map
2414 .snapshot(cx)
2415 .folds_in_range(0..snapshot.len())
2416 .map(|fold| {
2417 (
2418 fold.range.start.to_offset(&snapshot),
2419 fold.range.end.to_offset(&snapshot),
2420 )
2421 })
2422 .collect()
2423 });
2424 self.serialize_folds = cx.background_spawn(async move {
2425 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2426 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2427 .await
2428 .with_context(|| {
2429 format!(
2430 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2431 )
2432 })
2433 .log_err();
2434 });
2435 }
2436
2437 pub fn sync_selections(
2438 &mut self,
2439 other: Entity<Editor>,
2440 cx: &mut Context<Self>,
2441 ) -> gpui::Subscription {
2442 let other_selections = other.read(cx).selections.disjoint.to_vec();
2443 self.selections.change_with(cx, |selections| {
2444 selections.select_anchors(other_selections);
2445 });
2446
2447 let other_subscription =
2448 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2449 EditorEvent::SelectionsChanged { local: true } => {
2450 let other_selections = other.read(cx).selections.disjoint.to_vec();
2451 if other_selections.is_empty() {
2452 return;
2453 }
2454 this.selections.change_with(cx, |selections| {
2455 selections.select_anchors(other_selections);
2456 });
2457 }
2458 _ => {}
2459 });
2460
2461 let this_subscription =
2462 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2463 EditorEvent::SelectionsChanged { local: true } => {
2464 let these_selections = this.selections.disjoint.to_vec();
2465 if these_selections.is_empty() {
2466 return;
2467 }
2468 other.update(cx, |other_editor, cx| {
2469 other_editor.selections.change_with(cx, |selections| {
2470 selections.select_anchors(these_selections);
2471 })
2472 });
2473 }
2474 _ => {}
2475 });
2476
2477 Subscription::join(other_subscription, this_subscription)
2478 }
2479
2480 pub fn change_selections<R>(
2481 &mut self,
2482 autoscroll: Option<Autoscroll>,
2483 window: &mut Window,
2484 cx: &mut Context<Self>,
2485 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2486 ) -> R {
2487 self.change_selections_inner(autoscroll, true, window, cx, change)
2488 }
2489
2490 fn change_selections_inner<R>(
2491 &mut self,
2492 autoscroll: Option<Autoscroll>,
2493 request_completions: bool,
2494 window: &mut Window,
2495 cx: &mut Context<Self>,
2496 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2497 ) -> R {
2498 let old_cursor_position = self.selections.newest_anchor().head();
2499 self.push_to_selection_history();
2500
2501 let (changed, result) = self.selections.change_with(cx, change);
2502
2503 if changed {
2504 if let Some(autoscroll) = autoscroll {
2505 self.request_autoscroll(autoscroll, cx);
2506 }
2507 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2508
2509 if self.should_open_signature_help_automatically(
2510 &old_cursor_position,
2511 self.signature_help_state.backspace_pressed(),
2512 cx,
2513 ) {
2514 self.show_signature_help(&ShowSignatureHelp, window, cx);
2515 }
2516 self.signature_help_state.set_backspace_pressed(false);
2517 }
2518
2519 result
2520 }
2521
2522 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2523 where
2524 I: IntoIterator<Item = (Range<S>, T)>,
2525 S: ToOffset,
2526 T: Into<Arc<str>>,
2527 {
2528 if self.read_only(cx) {
2529 return;
2530 }
2531
2532 self.buffer
2533 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2534 }
2535
2536 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2537 where
2538 I: IntoIterator<Item = (Range<S>, T)>,
2539 S: ToOffset,
2540 T: Into<Arc<str>>,
2541 {
2542 if self.read_only(cx) {
2543 return;
2544 }
2545
2546 self.buffer.update(cx, |buffer, cx| {
2547 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2548 });
2549 }
2550
2551 pub fn edit_with_block_indent<I, S, T>(
2552 &mut self,
2553 edits: I,
2554 original_indent_columns: Vec<Option<u32>>,
2555 cx: &mut Context<Self>,
2556 ) where
2557 I: IntoIterator<Item = (Range<S>, T)>,
2558 S: ToOffset,
2559 T: Into<Arc<str>>,
2560 {
2561 if self.read_only(cx) {
2562 return;
2563 }
2564
2565 self.buffer.update(cx, |buffer, cx| {
2566 buffer.edit(
2567 edits,
2568 Some(AutoindentMode::Block {
2569 original_indent_columns,
2570 }),
2571 cx,
2572 )
2573 });
2574 }
2575
2576 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2577 self.hide_context_menu(window, cx);
2578
2579 match phase {
2580 SelectPhase::Begin {
2581 position,
2582 add,
2583 click_count,
2584 } => self.begin_selection(position, add, click_count, window, cx),
2585 SelectPhase::BeginColumnar {
2586 position,
2587 goal_column,
2588 reset,
2589 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2590 SelectPhase::Extend {
2591 position,
2592 click_count,
2593 } => self.extend_selection(position, click_count, window, cx),
2594 SelectPhase::Update {
2595 position,
2596 goal_column,
2597 scroll_delta,
2598 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2599 SelectPhase::End => self.end_selection(window, cx),
2600 }
2601 }
2602
2603 fn extend_selection(
2604 &mut self,
2605 position: DisplayPoint,
2606 click_count: usize,
2607 window: &mut Window,
2608 cx: &mut Context<Self>,
2609 ) {
2610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2611 let tail = self.selections.newest::<usize>(cx).tail();
2612 self.begin_selection(position, false, click_count, window, cx);
2613
2614 let position = position.to_offset(&display_map, Bias::Left);
2615 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2616
2617 let mut pending_selection = self
2618 .selections
2619 .pending_anchor()
2620 .expect("extend_selection not called with pending selection");
2621 if position >= tail {
2622 pending_selection.start = tail_anchor;
2623 } else {
2624 pending_selection.end = tail_anchor;
2625 pending_selection.reversed = true;
2626 }
2627
2628 let mut pending_mode = self.selections.pending_mode().unwrap();
2629 match &mut pending_mode {
2630 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2631 _ => {}
2632 }
2633
2634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2635 s.set_pending(pending_selection, pending_mode)
2636 });
2637 }
2638
2639 fn begin_selection(
2640 &mut self,
2641 position: DisplayPoint,
2642 add: bool,
2643 click_count: usize,
2644 window: &mut Window,
2645 cx: &mut Context<Self>,
2646 ) {
2647 if !self.focus_handle.is_focused(window) {
2648 self.last_focused_descendant = None;
2649 window.focus(&self.focus_handle);
2650 }
2651
2652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2653 let buffer = &display_map.buffer_snapshot;
2654 let newest_selection = self.selections.newest_anchor().clone();
2655 let position = display_map.clip_point(position, Bias::Left);
2656
2657 let start;
2658 let end;
2659 let mode;
2660 let mut auto_scroll;
2661 match click_count {
2662 1 => {
2663 start = buffer.anchor_before(position.to_point(&display_map));
2664 end = start;
2665 mode = SelectMode::Character;
2666 auto_scroll = true;
2667 }
2668 2 => {
2669 let range = movement::surrounding_word(&display_map, position);
2670 start = buffer.anchor_before(range.start.to_point(&display_map));
2671 end = buffer.anchor_before(range.end.to_point(&display_map));
2672 mode = SelectMode::Word(start..end);
2673 auto_scroll = true;
2674 }
2675 3 => {
2676 let position = display_map
2677 .clip_point(position, Bias::Left)
2678 .to_point(&display_map);
2679 let line_start = display_map.prev_line_boundary(position).0;
2680 let next_line_start = buffer.clip_point(
2681 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2682 Bias::Left,
2683 );
2684 start = buffer.anchor_before(line_start);
2685 end = buffer.anchor_before(next_line_start);
2686 mode = SelectMode::Line(start..end);
2687 auto_scroll = true;
2688 }
2689 _ => {
2690 start = buffer.anchor_before(0);
2691 end = buffer.anchor_before(buffer.len());
2692 mode = SelectMode::All;
2693 auto_scroll = false;
2694 }
2695 }
2696 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2697
2698 let point_to_delete: Option<usize> = {
2699 let selected_points: Vec<Selection<Point>> =
2700 self.selections.disjoint_in_range(start..end, cx);
2701
2702 if !add || click_count > 1 {
2703 None
2704 } else if !selected_points.is_empty() {
2705 Some(selected_points[0].id)
2706 } else {
2707 let clicked_point_already_selected =
2708 self.selections.disjoint.iter().find(|selection| {
2709 selection.start.to_point(buffer) == start.to_point(buffer)
2710 || selection.end.to_point(buffer) == end.to_point(buffer)
2711 });
2712
2713 clicked_point_already_selected.map(|selection| selection.id)
2714 }
2715 };
2716
2717 let selections_count = self.selections.count();
2718
2719 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2720 if let Some(point_to_delete) = point_to_delete {
2721 s.delete(point_to_delete);
2722
2723 if selections_count == 1 {
2724 s.set_pending_anchor_range(start..end, mode);
2725 }
2726 } else {
2727 if !add {
2728 s.clear_disjoint();
2729 } else if click_count > 1 {
2730 s.delete(newest_selection.id)
2731 }
2732
2733 s.set_pending_anchor_range(start..end, mode);
2734 }
2735 });
2736 }
2737
2738 fn begin_columnar_selection(
2739 &mut self,
2740 position: DisplayPoint,
2741 goal_column: u32,
2742 reset: bool,
2743 window: &mut Window,
2744 cx: &mut Context<Self>,
2745 ) {
2746 if !self.focus_handle.is_focused(window) {
2747 self.last_focused_descendant = None;
2748 window.focus(&self.focus_handle);
2749 }
2750
2751 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2752
2753 if reset {
2754 let pointer_position = display_map
2755 .buffer_snapshot
2756 .anchor_before(position.to_point(&display_map));
2757
2758 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2759 s.clear_disjoint();
2760 s.set_pending_anchor_range(
2761 pointer_position..pointer_position,
2762 SelectMode::Character,
2763 );
2764 });
2765 }
2766
2767 let tail = self.selections.newest::<Point>(cx).tail();
2768 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2769
2770 if !reset {
2771 self.select_columns(
2772 tail.to_display_point(&display_map),
2773 position,
2774 goal_column,
2775 &display_map,
2776 window,
2777 cx,
2778 );
2779 }
2780 }
2781
2782 fn update_selection(
2783 &mut self,
2784 position: DisplayPoint,
2785 goal_column: u32,
2786 scroll_delta: gpui::Point<f32>,
2787 window: &mut Window,
2788 cx: &mut Context<Self>,
2789 ) {
2790 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2791
2792 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2793 let tail = tail.to_display_point(&display_map);
2794 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2795 } else if let Some(mut pending) = self.selections.pending_anchor() {
2796 let buffer = self.buffer.read(cx).snapshot(cx);
2797 let head;
2798 let tail;
2799 let mode = self.selections.pending_mode().unwrap();
2800 match &mode {
2801 SelectMode::Character => {
2802 head = position.to_point(&display_map);
2803 tail = pending.tail().to_point(&buffer);
2804 }
2805 SelectMode::Word(original_range) => {
2806 let original_display_range = original_range.start.to_display_point(&display_map)
2807 ..original_range.end.to_display_point(&display_map);
2808 let original_buffer_range = original_display_range.start.to_point(&display_map)
2809 ..original_display_range.end.to_point(&display_map);
2810 if movement::is_inside_word(&display_map, position)
2811 || original_display_range.contains(&position)
2812 {
2813 let word_range = movement::surrounding_word(&display_map, position);
2814 if word_range.start < original_display_range.start {
2815 head = word_range.start.to_point(&display_map);
2816 } else {
2817 head = word_range.end.to_point(&display_map);
2818 }
2819 } else {
2820 head = position.to_point(&display_map);
2821 }
2822
2823 if head <= original_buffer_range.start {
2824 tail = original_buffer_range.end;
2825 } else {
2826 tail = original_buffer_range.start;
2827 }
2828 }
2829 SelectMode::Line(original_range) => {
2830 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2831
2832 let position = display_map
2833 .clip_point(position, Bias::Left)
2834 .to_point(&display_map);
2835 let line_start = display_map.prev_line_boundary(position).0;
2836 let next_line_start = buffer.clip_point(
2837 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2838 Bias::Left,
2839 );
2840
2841 if line_start < original_range.start {
2842 head = line_start
2843 } else {
2844 head = next_line_start
2845 }
2846
2847 if head <= original_range.start {
2848 tail = original_range.end;
2849 } else {
2850 tail = original_range.start;
2851 }
2852 }
2853 SelectMode::All => {
2854 return;
2855 }
2856 };
2857
2858 if head < tail {
2859 pending.start = buffer.anchor_before(head);
2860 pending.end = buffer.anchor_before(tail);
2861 pending.reversed = true;
2862 } else {
2863 pending.start = buffer.anchor_before(tail);
2864 pending.end = buffer.anchor_before(head);
2865 pending.reversed = false;
2866 }
2867
2868 self.change_selections(None, window, cx, |s| {
2869 s.set_pending(pending, mode);
2870 });
2871 } else {
2872 log::error!("update_selection dispatched with no pending selection");
2873 return;
2874 }
2875
2876 self.apply_scroll_delta(scroll_delta, window, cx);
2877 cx.notify();
2878 }
2879
2880 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2881 self.columnar_selection_tail.take();
2882 if self.selections.pending_anchor().is_some() {
2883 let selections = self.selections.all::<usize>(cx);
2884 self.change_selections(None, window, cx, |s| {
2885 s.select(selections);
2886 s.clear_pending();
2887 });
2888 }
2889 }
2890
2891 fn select_columns(
2892 &mut self,
2893 tail: DisplayPoint,
2894 head: DisplayPoint,
2895 goal_column: u32,
2896 display_map: &DisplaySnapshot,
2897 window: &mut Window,
2898 cx: &mut Context<Self>,
2899 ) {
2900 let start_row = cmp::min(tail.row(), head.row());
2901 let end_row = cmp::max(tail.row(), head.row());
2902 let start_column = cmp::min(tail.column(), goal_column);
2903 let end_column = cmp::max(tail.column(), goal_column);
2904 let reversed = start_column < tail.column();
2905
2906 let selection_ranges = (start_row.0..=end_row.0)
2907 .map(DisplayRow)
2908 .filter_map(|row| {
2909 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
2910 let start = display_map
2911 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
2912 .to_point(display_map);
2913 let end = display_map
2914 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
2915 .to_point(display_map);
2916 if reversed {
2917 Some(end..start)
2918 } else {
2919 Some(start..end)
2920 }
2921 } else {
2922 None
2923 }
2924 })
2925 .collect::<Vec<_>>();
2926
2927 self.change_selections(None, window, cx, |s| {
2928 s.select_ranges(selection_ranges);
2929 });
2930 cx.notify();
2931 }
2932
2933 pub fn has_pending_nonempty_selection(&self) -> bool {
2934 let pending_nonempty_selection = match self.selections.pending_anchor() {
2935 Some(Selection { start, end, .. }) => start != end,
2936 None => false,
2937 };
2938
2939 pending_nonempty_selection
2940 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
2941 }
2942
2943 pub fn has_pending_selection(&self) -> bool {
2944 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
2945 }
2946
2947 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
2948 self.selection_mark_mode = false;
2949
2950 if self.clear_expanded_diff_hunks(cx) {
2951 cx.notify();
2952 return;
2953 }
2954 if self.dismiss_menus_and_popups(true, window, cx) {
2955 return;
2956 }
2957
2958 if self.mode == EditorMode::Full
2959 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
2960 {
2961 return;
2962 }
2963
2964 cx.propagate();
2965 }
2966
2967 pub fn dismiss_menus_and_popups(
2968 &mut self,
2969 is_user_requested: bool,
2970 window: &mut Window,
2971 cx: &mut Context<Self>,
2972 ) -> bool {
2973 if self.take_rename(false, window, cx).is_some() {
2974 return true;
2975 }
2976
2977 if hide_hover(self, cx) {
2978 return true;
2979 }
2980
2981 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
2982 return true;
2983 }
2984
2985 if self.hide_context_menu(window, cx).is_some() {
2986 return true;
2987 }
2988
2989 if self.mouse_context_menu.take().is_some() {
2990 return true;
2991 }
2992
2993 if is_user_requested && self.discard_inline_completion(true, cx) {
2994 return true;
2995 }
2996
2997 if self.snippet_stack.pop().is_some() {
2998 return true;
2999 }
3000
3001 if self.mode == EditorMode::Full && self.active_diagnostics.is_some() {
3002 self.dismiss_diagnostics(cx);
3003 return true;
3004 }
3005
3006 false
3007 }
3008
3009 fn linked_editing_ranges_for(
3010 &self,
3011 selection: Range<text::Anchor>,
3012 cx: &App,
3013 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3014 if self.linked_edit_ranges.is_empty() {
3015 return None;
3016 }
3017 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3018 selection.end.buffer_id.and_then(|end_buffer_id| {
3019 if selection.start.buffer_id != Some(end_buffer_id) {
3020 return None;
3021 }
3022 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3023 let snapshot = buffer.read(cx).snapshot();
3024 self.linked_edit_ranges
3025 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3026 .map(|ranges| (ranges, snapshot, buffer))
3027 })?;
3028 use text::ToOffset as TO;
3029 // find offset from the start of current range to current cursor position
3030 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3031
3032 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3033 let start_difference = start_offset - start_byte_offset;
3034 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3035 let end_difference = end_offset - start_byte_offset;
3036 // Current range has associated linked ranges.
3037 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3038 for range in linked_ranges.iter() {
3039 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3040 let end_offset = start_offset + end_difference;
3041 let start_offset = start_offset + start_difference;
3042 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3043 continue;
3044 }
3045 if self.selections.disjoint_anchor_ranges().any(|s| {
3046 if s.start.buffer_id != selection.start.buffer_id
3047 || s.end.buffer_id != selection.end.buffer_id
3048 {
3049 return false;
3050 }
3051 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3052 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3053 }) {
3054 continue;
3055 }
3056 let start = buffer_snapshot.anchor_after(start_offset);
3057 let end = buffer_snapshot.anchor_after(end_offset);
3058 linked_edits
3059 .entry(buffer.clone())
3060 .or_default()
3061 .push(start..end);
3062 }
3063 Some(linked_edits)
3064 }
3065
3066 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3067 let text: Arc<str> = text.into();
3068
3069 if self.read_only(cx) {
3070 return;
3071 }
3072
3073 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3074
3075 let selections = self.selections.all_adjusted(cx);
3076 let mut bracket_inserted = false;
3077 let mut edits = Vec::new();
3078 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3079 let mut new_selections = Vec::with_capacity(selections.len());
3080 let mut new_autoclose_regions = Vec::new();
3081 let snapshot = self.buffer.read(cx).read(cx);
3082
3083 for (selection, autoclose_region) in
3084 self.selections_with_autoclose_regions(selections, &snapshot)
3085 {
3086 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3087 // Determine if the inserted text matches the opening or closing
3088 // bracket of any of this language's bracket pairs.
3089 let mut bracket_pair = None;
3090 let mut is_bracket_pair_start = false;
3091 let mut is_bracket_pair_end = false;
3092 if !text.is_empty() {
3093 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3094 // and they are removing the character that triggered IME popup.
3095 for (pair, enabled) in scope.brackets() {
3096 if !pair.close && !pair.surround {
3097 continue;
3098 }
3099
3100 if enabled && pair.start.ends_with(text.as_ref()) {
3101 let prefix_len = pair.start.len() - text.len();
3102 let preceding_text_matches_prefix = prefix_len == 0
3103 || (selection.start.column >= (prefix_len as u32)
3104 && snapshot.contains_str_at(
3105 Point::new(
3106 selection.start.row,
3107 selection.start.column - (prefix_len as u32),
3108 ),
3109 &pair.start[..prefix_len],
3110 ));
3111 if preceding_text_matches_prefix {
3112 bracket_pair = Some(pair.clone());
3113 is_bracket_pair_start = true;
3114 break;
3115 }
3116 }
3117 if pair.end.as_str() == text.as_ref() {
3118 bracket_pair = Some(pair.clone());
3119 is_bracket_pair_end = true;
3120 break;
3121 }
3122 }
3123 }
3124
3125 if let Some(bracket_pair) = bracket_pair {
3126 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3127 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3128 let auto_surround =
3129 self.use_auto_surround && snapshot_settings.use_auto_surround;
3130 if selection.is_empty() {
3131 if is_bracket_pair_start {
3132 // If the inserted text is a suffix of an opening bracket and the
3133 // selection is preceded by the rest of the opening bracket, then
3134 // insert the closing bracket.
3135 let following_text_allows_autoclose = snapshot
3136 .chars_at(selection.start)
3137 .next()
3138 .map_or(true, |c| scope.should_autoclose_before(c));
3139
3140 let preceding_text_allows_autoclose = selection.start.column == 0
3141 || snapshot.reversed_chars_at(selection.start).next().map_or(
3142 true,
3143 |c| {
3144 bracket_pair.start != bracket_pair.end
3145 || !snapshot
3146 .char_classifier_at(selection.start)
3147 .is_word(c)
3148 },
3149 );
3150
3151 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3152 && bracket_pair.start.len() == 1
3153 {
3154 let target = bracket_pair.start.chars().next().unwrap();
3155 let current_line_count = snapshot
3156 .reversed_chars_at(selection.start)
3157 .take_while(|&c| c != '\n')
3158 .filter(|&c| c == target)
3159 .count();
3160 current_line_count % 2 == 1
3161 } else {
3162 false
3163 };
3164
3165 if autoclose
3166 && bracket_pair.close
3167 && following_text_allows_autoclose
3168 && preceding_text_allows_autoclose
3169 && !is_closing_quote
3170 {
3171 let anchor = snapshot.anchor_before(selection.end);
3172 new_selections.push((selection.map(|_| anchor), text.len()));
3173 new_autoclose_regions.push((
3174 anchor,
3175 text.len(),
3176 selection.id,
3177 bracket_pair.clone(),
3178 ));
3179 edits.push((
3180 selection.range(),
3181 format!("{}{}", text, bracket_pair.end).into(),
3182 ));
3183 bracket_inserted = true;
3184 continue;
3185 }
3186 }
3187
3188 if let Some(region) = autoclose_region {
3189 // If the selection is followed by an auto-inserted closing bracket,
3190 // then don't insert that closing bracket again; just move the selection
3191 // past the closing bracket.
3192 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3193 && text.as_ref() == region.pair.end.as_str();
3194 if should_skip {
3195 let anchor = snapshot.anchor_after(selection.end);
3196 new_selections
3197 .push((selection.map(|_| anchor), region.pair.end.len()));
3198 continue;
3199 }
3200 }
3201
3202 let always_treat_brackets_as_autoclosed = snapshot
3203 .language_settings_at(selection.start, cx)
3204 .always_treat_brackets_as_autoclosed;
3205 if always_treat_brackets_as_autoclosed
3206 && is_bracket_pair_end
3207 && snapshot.contains_str_at(selection.end, text.as_ref())
3208 {
3209 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3210 // and the inserted text is a closing bracket and the selection is followed
3211 // by the closing bracket then move the selection past the closing bracket.
3212 let anchor = snapshot.anchor_after(selection.end);
3213 new_selections.push((selection.map(|_| anchor), text.len()));
3214 continue;
3215 }
3216 }
3217 // If an opening bracket is 1 character long and is typed while
3218 // text is selected, then surround that text with the bracket pair.
3219 else if auto_surround
3220 && bracket_pair.surround
3221 && is_bracket_pair_start
3222 && bracket_pair.start.chars().count() == 1
3223 {
3224 edits.push((selection.start..selection.start, text.clone()));
3225 edits.push((
3226 selection.end..selection.end,
3227 bracket_pair.end.as_str().into(),
3228 ));
3229 bracket_inserted = true;
3230 new_selections.push((
3231 Selection {
3232 id: selection.id,
3233 start: snapshot.anchor_after(selection.start),
3234 end: snapshot.anchor_before(selection.end),
3235 reversed: selection.reversed,
3236 goal: selection.goal,
3237 },
3238 0,
3239 ));
3240 continue;
3241 }
3242 }
3243 }
3244
3245 if self.auto_replace_emoji_shortcode
3246 && selection.is_empty()
3247 && text.as_ref().ends_with(':')
3248 {
3249 if let Some(possible_emoji_short_code) =
3250 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3251 {
3252 if !possible_emoji_short_code.is_empty() {
3253 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3254 let emoji_shortcode_start = Point::new(
3255 selection.start.row,
3256 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3257 );
3258
3259 // Remove shortcode from buffer
3260 edits.push((
3261 emoji_shortcode_start..selection.start,
3262 "".to_string().into(),
3263 ));
3264 new_selections.push((
3265 Selection {
3266 id: selection.id,
3267 start: snapshot.anchor_after(emoji_shortcode_start),
3268 end: snapshot.anchor_before(selection.start),
3269 reversed: selection.reversed,
3270 goal: selection.goal,
3271 },
3272 0,
3273 ));
3274
3275 // Insert emoji
3276 let selection_start_anchor = snapshot.anchor_after(selection.start);
3277 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3278 edits.push((selection.start..selection.end, emoji.to_string().into()));
3279
3280 continue;
3281 }
3282 }
3283 }
3284 }
3285
3286 // If not handling any auto-close operation, then just replace the selected
3287 // text with the given input and move the selection to the end of the
3288 // newly inserted text.
3289 let anchor = snapshot.anchor_after(selection.end);
3290 if !self.linked_edit_ranges.is_empty() {
3291 let start_anchor = snapshot.anchor_before(selection.start);
3292
3293 let is_word_char = text.chars().next().map_or(true, |char| {
3294 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3295 classifier.is_word(char)
3296 });
3297
3298 if is_word_char {
3299 if let Some(ranges) = self
3300 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3301 {
3302 for (buffer, edits) in ranges {
3303 linked_edits
3304 .entry(buffer.clone())
3305 .or_default()
3306 .extend(edits.into_iter().map(|range| (range, text.clone())));
3307 }
3308 }
3309 }
3310 }
3311
3312 new_selections.push((selection.map(|_| anchor), 0));
3313 edits.push((selection.start..selection.end, text.clone()));
3314 }
3315
3316 drop(snapshot);
3317
3318 self.transact(window, cx, |this, window, cx| {
3319 let initial_buffer_versions =
3320 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3321
3322 this.buffer.update(cx, |buffer, cx| {
3323 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3324 });
3325 for (buffer, edits) in linked_edits {
3326 buffer.update(cx, |buffer, cx| {
3327 let snapshot = buffer.snapshot();
3328 let edits = edits
3329 .into_iter()
3330 .map(|(range, text)| {
3331 use text::ToPoint as TP;
3332 let end_point = TP::to_point(&range.end, &snapshot);
3333 let start_point = TP::to_point(&range.start, &snapshot);
3334 (start_point..end_point, text)
3335 })
3336 .sorted_by_key(|(range, _)| range.start);
3337 buffer.edit(edits, None, cx);
3338 })
3339 }
3340 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3341 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3342 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3343 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3344 .zip(new_selection_deltas)
3345 .map(|(selection, delta)| Selection {
3346 id: selection.id,
3347 start: selection.start + delta,
3348 end: selection.end + delta,
3349 reversed: selection.reversed,
3350 goal: SelectionGoal::None,
3351 })
3352 .collect::<Vec<_>>();
3353
3354 let mut i = 0;
3355 for (position, delta, selection_id, pair) in new_autoclose_regions {
3356 let position = position.to_offset(&map.buffer_snapshot) + delta;
3357 let start = map.buffer_snapshot.anchor_before(position);
3358 let end = map.buffer_snapshot.anchor_after(position);
3359 while let Some(existing_state) = this.autoclose_regions.get(i) {
3360 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3361 Ordering::Less => i += 1,
3362 Ordering::Greater => break,
3363 Ordering::Equal => {
3364 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3365 Ordering::Less => i += 1,
3366 Ordering::Equal => break,
3367 Ordering::Greater => break,
3368 }
3369 }
3370 }
3371 }
3372 this.autoclose_regions.insert(
3373 i,
3374 AutocloseRegion {
3375 selection_id,
3376 range: start..end,
3377 pair,
3378 },
3379 );
3380 }
3381
3382 let had_active_inline_completion = this.has_active_inline_completion();
3383 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3384 s.select(new_selections)
3385 });
3386
3387 if !bracket_inserted {
3388 if let Some(on_type_format_task) =
3389 this.trigger_on_type_formatting(text.to_string(), window, cx)
3390 {
3391 on_type_format_task.detach_and_log_err(cx);
3392 }
3393 }
3394
3395 let editor_settings = EditorSettings::get_global(cx);
3396 if bracket_inserted
3397 && (editor_settings.auto_signature_help
3398 || editor_settings.show_signature_help_after_edits)
3399 {
3400 this.show_signature_help(&ShowSignatureHelp, window, cx);
3401 }
3402
3403 let trigger_in_words =
3404 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3405 if this.hard_wrap.is_some() {
3406 let latest: Range<Point> = this.selections.newest(cx).range();
3407 if latest.is_empty()
3408 && this
3409 .buffer()
3410 .read(cx)
3411 .snapshot(cx)
3412 .line_len(MultiBufferRow(latest.start.row))
3413 == latest.start.column
3414 {
3415 this.rewrap_impl(
3416 RewrapOptions {
3417 override_language_settings: true,
3418 preserve_existing_whitespace: true,
3419 },
3420 cx,
3421 )
3422 }
3423 }
3424 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3425 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3426 this.refresh_inline_completion(true, false, window, cx);
3427 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3428 });
3429 }
3430
3431 fn find_possible_emoji_shortcode_at_position(
3432 snapshot: &MultiBufferSnapshot,
3433 position: Point,
3434 ) -> Option<String> {
3435 let mut chars = Vec::new();
3436 let mut found_colon = false;
3437 for char in snapshot.reversed_chars_at(position).take(100) {
3438 // Found a possible emoji shortcode in the middle of the buffer
3439 if found_colon {
3440 if char.is_whitespace() {
3441 chars.reverse();
3442 return Some(chars.iter().collect());
3443 }
3444 // If the previous character is not a whitespace, we are in the middle of a word
3445 // and we only want to complete the shortcode if the word is made up of other emojis
3446 let mut containing_word = String::new();
3447 for ch in snapshot
3448 .reversed_chars_at(position)
3449 .skip(chars.len() + 1)
3450 .take(100)
3451 {
3452 if ch.is_whitespace() {
3453 break;
3454 }
3455 containing_word.push(ch);
3456 }
3457 let containing_word = containing_word.chars().rev().collect::<String>();
3458 if util::word_consists_of_emojis(containing_word.as_str()) {
3459 chars.reverse();
3460 return Some(chars.iter().collect());
3461 }
3462 }
3463
3464 if char.is_whitespace() || !char.is_ascii() {
3465 return None;
3466 }
3467 if char == ':' {
3468 found_colon = true;
3469 } else {
3470 chars.push(char);
3471 }
3472 }
3473 // Found a possible emoji shortcode at the beginning of the buffer
3474 chars.reverse();
3475 Some(chars.iter().collect())
3476 }
3477
3478 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3479 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3480 self.transact(window, cx, |this, window, cx| {
3481 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3482 let selections = this.selections.all::<usize>(cx);
3483 let multi_buffer = this.buffer.read(cx);
3484 let buffer = multi_buffer.snapshot(cx);
3485 selections
3486 .iter()
3487 .map(|selection| {
3488 let start_point = selection.start.to_point(&buffer);
3489 let mut indent =
3490 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3491 indent.len = cmp::min(indent.len, start_point.column);
3492 let start = selection.start;
3493 let end = selection.end;
3494 let selection_is_empty = start == end;
3495 let language_scope = buffer.language_scope_at(start);
3496 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3497 &language_scope
3498 {
3499 let insert_extra_newline =
3500 insert_extra_newline_brackets(&buffer, start..end, language)
3501 || insert_extra_newline_tree_sitter(&buffer, start..end);
3502
3503 // Comment extension on newline is allowed only for cursor selections
3504 let comment_delimiter = maybe!({
3505 if !selection_is_empty {
3506 return None;
3507 }
3508
3509 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3510 return None;
3511 }
3512
3513 let delimiters = language.line_comment_prefixes();
3514 let max_len_of_delimiter =
3515 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3516 let (snapshot, range) =
3517 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3518
3519 let mut index_of_first_non_whitespace = 0;
3520 let comment_candidate = snapshot
3521 .chars_for_range(range)
3522 .skip_while(|c| {
3523 let should_skip = c.is_whitespace();
3524 if should_skip {
3525 index_of_first_non_whitespace += 1;
3526 }
3527 should_skip
3528 })
3529 .take(max_len_of_delimiter)
3530 .collect::<String>();
3531 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3532 comment_candidate.starts_with(comment_prefix.as_ref())
3533 })?;
3534 let cursor_is_placed_after_comment_marker =
3535 index_of_first_non_whitespace + comment_prefix.len()
3536 <= start_point.column as usize;
3537 if cursor_is_placed_after_comment_marker {
3538 Some(comment_prefix.clone())
3539 } else {
3540 None
3541 }
3542 });
3543 (comment_delimiter, insert_extra_newline)
3544 } else {
3545 (None, false)
3546 };
3547
3548 let capacity_for_delimiter = comment_delimiter
3549 .as_deref()
3550 .map(str::len)
3551 .unwrap_or_default();
3552 let mut new_text =
3553 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3554 new_text.push('\n');
3555 new_text.extend(indent.chars());
3556 if let Some(delimiter) = &comment_delimiter {
3557 new_text.push_str(delimiter);
3558 }
3559 if insert_extra_newline {
3560 new_text = new_text.repeat(2);
3561 }
3562
3563 let anchor = buffer.anchor_after(end);
3564 let new_selection = selection.map(|_| anchor);
3565 (
3566 (start..end, new_text),
3567 (insert_extra_newline, new_selection),
3568 )
3569 })
3570 .unzip()
3571 };
3572
3573 this.edit_with_autoindent(edits, cx);
3574 let buffer = this.buffer.read(cx).snapshot(cx);
3575 let new_selections = selection_fixup_info
3576 .into_iter()
3577 .map(|(extra_newline_inserted, new_selection)| {
3578 let mut cursor = new_selection.end.to_point(&buffer);
3579 if extra_newline_inserted {
3580 cursor.row -= 1;
3581 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3582 }
3583 new_selection.map(|_| cursor)
3584 })
3585 .collect();
3586
3587 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3588 s.select(new_selections)
3589 });
3590 this.refresh_inline_completion(true, false, window, cx);
3591 });
3592 }
3593
3594 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3595 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3596
3597 let buffer = self.buffer.read(cx);
3598 let snapshot = buffer.snapshot(cx);
3599
3600 let mut edits = Vec::new();
3601 let mut rows = Vec::new();
3602
3603 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3604 let cursor = selection.head();
3605 let row = cursor.row;
3606
3607 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3608
3609 let newline = "\n".to_string();
3610 edits.push((start_of_line..start_of_line, newline));
3611
3612 rows.push(row + rows_inserted as u32);
3613 }
3614
3615 self.transact(window, cx, |editor, window, cx| {
3616 editor.edit(edits, cx);
3617
3618 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3619 let mut index = 0;
3620 s.move_cursors_with(|map, _, _| {
3621 let row = rows[index];
3622 index += 1;
3623
3624 let point = Point::new(row, 0);
3625 let boundary = map.next_line_boundary(point).1;
3626 let clipped = map.clip_point(boundary, Bias::Left);
3627
3628 (clipped, SelectionGoal::None)
3629 });
3630 });
3631
3632 let mut indent_edits = Vec::new();
3633 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3634 for row in rows {
3635 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3636 for (row, indent) in indents {
3637 if indent.len == 0 {
3638 continue;
3639 }
3640
3641 let text = match indent.kind {
3642 IndentKind::Space => " ".repeat(indent.len as usize),
3643 IndentKind::Tab => "\t".repeat(indent.len as usize),
3644 };
3645 let point = Point::new(row.0, 0);
3646 indent_edits.push((point..point, text));
3647 }
3648 }
3649 editor.edit(indent_edits, cx);
3650 });
3651 }
3652
3653 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3655
3656 let buffer = self.buffer.read(cx);
3657 let snapshot = buffer.snapshot(cx);
3658
3659 let mut edits = Vec::new();
3660 let mut rows = Vec::new();
3661 let mut rows_inserted = 0;
3662
3663 for selection in self.selections.all_adjusted(cx) {
3664 let cursor = selection.head();
3665 let row = cursor.row;
3666
3667 let point = Point::new(row + 1, 0);
3668 let start_of_line = snapshot.clip_point(point, Bias::Left);
3669
3670 let newline = "\n".to_string();
3671 edits.push((start_of_line..start_of_line, newline));
3672
3673 rows_inserted += 1;
3674 rows.push(row + rows_inserted);
3675 }
3676
3677 self.transact(window, cx, |editor, window, cx| {
3678 editor.edit(edits, cx);
3679
3680 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3681 let mut index = 0;
3682 s.move_cursors_with(|map, _, _| {
3683 let row = rows[index];
3684 index += 1;
3685
3686 let point = Point::new(row, 0);
3687 let boundary = map.next_line_boundary(point).1;
3688 let clipped = map.clip_point(boundary, Bias::Left);
3689
3690 (clipped, SelectionGoal::None)
3691 });
3692 });
3693
3694 let mut indent_edits = Vec::new();
3695 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3696 for row in rows {
3697 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3698 for (row, indent) in indents {
3699 if indent.len == 0 {
3700 continue;
3701 }
3702
3703 let text = match indent.kind {
3704 IndentKind::Space => " ".repeat(indent.len as usize),
3705 IndentKind::Tab => "\t".repeat(indent.len as usize),
3706 };
3707 let point = Point::new(row.0, 0);
3708 indent_edits.push((point..point, text));
3709 }
3710 }
3711 editor.edit(indent_edits, cx);
3712 });
3713 }
3714
3715 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3716 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3717 original_indent_columns: Vec::new(),
3718 });
3719 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3720 }
3721
3722 fn insert_with_autoindent_mode(
3723 &mut self,
3724 text: &str,
3725 autoindent_mode: Option<AutoindentMode>,
3726 window: &mut Window,
3727 cx: &mut Context<Self>,
3728 ) {
3729 if self.read_only(cx) {
3730 return;
3731 }
3732
3733 let text: Arc<str> = text.into();
3734 self.transact(window, cx, |this, window, cx| {
3735 let old_selections = this.selections.all_adjusted(cx);
3736 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3737 let anchors = {
3738 let snapshot = buffer.read(cx);
3739 old_selections
3740 .iter()
3741 .map(|s| {
3742 let anchor = snapshot.anchor_after(s.head());
3743 s.map(|_| anchor)
3744 })
3745 .collect::<Vec<_>>()
3746 };
3747 buffer.edit(
3748 old_selections
3749 .iter()
3750 .map(|s| (s.start..s.end, text.clone())),
3751 autoindent_mode,
3752 cx,
3753 );
3754 anchors
3755 });
3756
3757 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3758 s.select_anchors(selection_anchors);
3759 });
3760
3761 cx.notify();
3762 });
3763 }
3764
3765 fn trigger_completion_on_input(
3766 &mut self,
3767 text: &str,
3768 trigger_in_words: bool,
3769 window: &mut Window,
3770 cx: &mut Context<Self>,
3771 ) {
3772 let ignore_completion_provider = self
3773 .context_menu
3774 .borrow()
3775 .as_ref()
3776 .map(|menu| match menu {
3777 CodeContextMenu::Completions(completions_menu) => {
3778 completions_menu.ignore_completion_provider
3779 }
3780 CodeContextMenu::CodeActions(_) => false,
3781 })
3782 .unwrap_or(false);
3783
3784 if ignore_completion_provider {
3785 self.show_word_completions(&ShowWordCompletions, window, cx);
3786 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3787 self.show_completions(
3788 &ShowCompletions {
3789 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3790 },
3791 window,
3792 cx,
3793 );
3794 } else {
3795 self.hide_context_menu(window, cx);
3796 }
3797 }
3798
3799 fn is_completion_trigger(
3800 &self,
3801 text: &str,
3802 trigger_in_words: bool,
3803 cx: &mut Context<Self>,
3804 ) -> bool {
3805 let position = self.selections.newest_anchor().head();
3806 let multibuffer = self.buffer.read(cx);
3807 let Some(buffer) = position
3808 .buffer_id
3809 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3810 else {
3811 return false;
3812 };
3813
3814 if let Some(completion_provider) = &self.completion_provider {
3815 completion_provider.is_completion_trigger(
3816 &buffer,
3817 position.text_anchor,
3818 text,
3819 trigger_in_words,
3820 cx,
3821 )
3822 } else {
3823 false
3824 }
3825 }
3826
3827 /// If any empty selections is touching the start of its innermost containing autoclose
3828 /// region, expand it to select the brackets.
3829 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3830 let selections = self.selections.all::<usize>(cx);
3831 let buffer = self.buffer.read(cx).read(cx);
3832 let new_selections = self
3833 .selections_with_autoclose_regions(selections, &buffer)
3834 .map(|(mut selection, region)| {
3835 if !selection.is_empty() {
3836 return selection;
3837 }
3838
3839 if let Some(region) = region {
3840 let mut range = region.range.to_offset(&buffer);
3841 if selection.start == range.start && range.start >= region.pair.start.len() {
3842 range.start -= region.pair.start.len();
3843 if buffer.contains_str_at(range.start, ®ion.pair.start)
3844 && buffer.contains_str_at(range.end, ®ion.pair.end)
3845 {
3846 range.end += region.pair.end.len();
3847 selection.start = range.start;
3848 selection.end = range.end;
3849
3850 return selection;
3851 }
3852 }
3853 }
3854
3855 let always_treat_brackets_as_autoclosed = buffer
3856 .language_settings_at(selection.start, cx)
3857 .always_treat_brackets_as_autoclosed;
3858
3859 if !always_treat_brackets_as_autoclosed {
3860 return selection;
3861 }
3862
3863 if let Some(scope) = buffer.language_scope_at(selection.start) {
3864 for (pair, enabled) in scope.brackets() {
3865 if !enabled || !pair.close {
3866 continue;
3867 }
3868
3869 if buffer.contains_str_at(selection.start, &pair.end) {
3870 let pair_start_len = pair.start.len();
3871 if buffer.contains_str_at(
3872 selection.start.saturating_sub(pair_start_len),
3873 &pair.start,
3874 ) {
3875 selection.start -= pair_start_len;
3876 selection.end += pair.end.len();
3877
3878 return selection;
3879 }
3880 }
3881 }
3882 }
3883
3884 selection
3885 })
3886 .collect();
3887
3888 drop(buffer);
3889 self.change_selections(None, window, cx, |selections| {
3890 selections.select(new_selections)
3891 });
3892 }
3893
3894 /// Iterate the given selections, and for each one, find the smallest surrounding
3895 /// autoclose region. This uses the ordering of the selections and the autoclose
3896 /// regions to avoid repeated comparisons.
3897 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
3898 &'a self,
3899 selections: impl IntoIterator<Item = Selection<D>>,
3900 buffer: &'a MultiBufferSnapshot,
3901 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
3902 let mut i = 0;
3903 let mut regions = self.autoclose_regions.as_slice();
3904 selections.into_iter().map(move |selection| {
3905 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
3906
3907 let mut enclosing = None;
3908 while let Some(pair_state) = regions.get(i) {
3909 if pair_state.range.end.to_offset(buffer) < range.start {
3910 regions = ®ions[i + 1..];
3911 i = 0;
3912 } else if pair_state.range.start.to_offset(buffer) > range.end {
3913 break;
3914 } else {
3915 if pair_state.selection_id == selection.id {
3916 enclosing = Some(pair_state);
3917 }
3918 i += 1;
3919 }
3920 }
3921
3922 (selection, enclosing)
3923 })
3924 }
3925
3926 /// Remove any autoclose regions that no longer contain their selection.
3927 fn invalidate_autoclose_regions(
3928 &mut self,
3929 mut selections: &[Selection<Anchor>],
3930 buffer: &MultiBufferSnapshot,
3931 ) {
3932 self.autoclose_regions.retain(|state| {
3933 let mut i = 0;
3934 while let Some(selection) = selections.get(i) {
3935 if selection.end.cmp(&state.range.start, buffer).is_lt() {
3936 selections = &selections[1..];
3937 continue;
3938 }
3939 if selection.start.cmp(&state.range.end, buffer).is_gt() {
3940 break;
3941 }
3942 if selection.id == state.selection_id {
3943 return true;
3944 } else {
3945 i += 1;
3946 }
3947 }
3948 false
3949 });
3950 }
3951
3952 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
3953 let offset = position.to_offset(buffer);
3954 let (word_range, kind) = buffer.surrounding_word(offset, true);
3955 if offset > word_range.start && kind == Some(CharKind::Word) {
3956 Some(
3957 buffer
3958 .text_for_range(word_range.start..offset)
3959 .collect::<String>(),
3960 )
3961 } else {
3962 None
3963 }
3964 }
3965
3966 pub fn toggle_inlay_hints(
3967 &mut self,
3968 _: &ToggleInlayHints,
3969 _: &mut Window,
3970 cx: &mut Context<Self>,
3971 ) {
3972 self.refresh_inlay_hints(
3973 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
3974 cx,
3975 );
3976 }
3977
3978 pub fn inlay_hints_enabled(&self) -> bool {
3979 self.inlay_hint_cache.enabled
3980 }
3981
3982 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
3983 if self.semantics_provider.is_none() || self.mode != EditorMode::Full {
3984 return;
3985 }
3986
3987 let reason_description = reason.description();
3988 let ignore_debounce = matches!(
3989 reason,
3990 InlayHintRefreshReason::SettingsChange(_)
3991 | InlayHintRefreshReason::Toggle(_)
3992 | InlayHintRefreshReason::ExcerptsRemoved(_)
3993 | InlayHintRefreshReason::ModifiersChanged(_)
3994 );
3995 let (invalidate_cache, required_languages) = match reason {
3996 InlayHintRefreshReason::ModifiersChanged(enabled) => {
3997 match self.inlay_hint_cache.modifiers_override(enabled) {
3998 Some(enabled) => {
3999 if enabled {
4000 (InvalidationStrategy::RefreshRequested, None)
4001 } else {
4002 self.splice_inlays(
4003 &self
4004 .visible_inlay_hints(cx)
4005 .iter()
4006 .map(|inlay| inlay.id)
4007 .collect::<Vec<InlayId>>(),
4008 Vec::new(),
4009 cx,
4010 );
4011 return;
4012 }
4013 }
4014 None => return,
4015 }
4016 }
4017 InlayHintRefreshReason::Toggle(enabled) => {
4018 if self.inlay_hint_cache.toggle(enabled) {
4019 if enabled {
4020 (InvalidationStrategy::RefreshRequested, None)
4021 } else {
4022 self.splice_inlays(
4023 &self
4024 .visible_inlay_hints(cx)
4025 .iter()
4026 .map(|inlay| inlay.id)
4027 .collect::<Vec<InlayId>>(),
4028 Vec::new(),
4029 cx,
4030 );
4031 return;
4032 }
4033 } else {
4034 return;
4035 }
4036 }
4037 InlayHintRefreshReason::SettingsChange(new_settings) => {
4038 match self.inlay_hint_cache.update_settings(
4039 &self.buffer,
4040 new_settings,
4041 self.visible_inlay_hints(cx),
4042 cx,
4043 ) {
4044 ControlFlow::Break(Some(InlaySplice {
4045 to_remove,
4046 to_insert,
4047 })) => {
4048 self.splice_inlays(&to_remove, to_insert, cx);
4049 return;
4050 }
4051 ControlFlow::Break(None) => return,
4052 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4053 }
4054 }
4055 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4056 if let Some(InlaySplice {
4057 to_remove,
4058 to_insert,
4059 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4060 {
4061 self.splice_inlays(&to_remove, to_insert, cx);
4062 }
4063 return;
4064 }
4065 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4066 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4067 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4068 }
4069 InlayHintRefreshReason::RefreshRequested => {
4070 (InvalidationStrategy::RefreshRequested, None)
4071 }
4072 };
4073
4074 if let Some(InlaySplice {
4075 to_remove,
4076 to_insert,
4077 }) = self.inlay_hint_cache.spawn_hint_refresh(
4078 reason_description,
4079 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4080 invalidate_cache,
4081 ignore_debounce,
4082 cx,
4083 ) {
4084 self.splice_inlays(&to_remove, to_insert, cx);
4085 }
4086 }
4087
4088 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4089 self.display_map
4090 .read(cx)
4091 .current_inlays()
4092 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4093 .cloned()
4094 .collect()
4095 }
4096
4097 pub fn excerpts_for_inlay_hints_query(
4098 &self,
4099 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4100 cx: &mut Context<Editor>,
4101 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4102 let Some(project) = self.project.as_ref() else {
4103 return HashMap::default();
4104 };
4105 let project = project.read(cx);
4106 let multi_buffer = self.buffer().read(cx);
4107 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4108 let multi_buffer_visible_start = self
4109 .scroll_manager
4110 .anchor()
4111 .anchor
4112 .to_point(&multi_buffer_snapshot);
4113 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4114 multi_buffer_visible_start
4115 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4116 Bias::Left,
4117 );
4118 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4119 multi_buffer_snapshot
4120 .range_to_buffer_ranges(multi_buffer_visible_range)
4121 .into_iter()
4122 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4123 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4124 let buffer_file = project::File::from_dyn(buffer.file())?;
4125 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4126 let worktree_entry = buffer_worktree
4127 .read(cx)
4128 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4129 if worktree_entry.is_ignored {
4130 return None;
4131 }
4132
4133 let language = buffer.language()?;
4134 if let Some(restrict_to_languages) = restrict_to_languages {
4135 if !restrict_to_languages.contains(language) {
4136 return None;
4137 }
4138 }
4139 Some((
4140 excerpt_id,
4141 (
4142 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4143 buffer.version().clone(),
4144 excerpt_visible_range,
4145 ),
4146 ))
4147 })
4148 .collect()
4149 }
4150
4151 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4152 TextLayoutDetails {
4153 text_system: window.text_system().clone(),
4154 editor_style: self.style.clone().unwrap(),
4155 rem_size: window.rem_size(),
4156 scroll_anchor: self.scroll_manager.anchor(),
4157 visible_rows: self.visible_line_count(),
4158 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4159 }
4160 }
4161
4162 pub fn splice_inlays(
4163 &self,
4164 to_remove: &[InlayId],
4165 to_insert: Vec<Inlay>,
4166 cx: &mut Context<Self>,
4167 ) {
4168 self.display_map.update(cx, |display_map, cx| {
4169 display_map.splice_inlays(to_remove, to_insert, cx)
4170 });
4171 cx.notify();
4172 }
4173
4174 fn trigger_on_type_formatting(
4175 &self,
4176 input: String,
4177 window: &mut Window,
4178 cx: &mut Context<Self>,
4179 ) -> Option<Task<Result<()>>> {
4180 if input.len() != 1 {
4181 return None;
4182 }
4183
4184 let project = self.project.as_ref()?;
4185 let position = self.selections.newest_anchor().head();
4186 let (buffer, buffer_position) = self
4187 .buffer
4188 .read(cx)
4189 .text_anchor_for_position(position, cx)?;
4190
4191 let settings = language_settings::language_settings(
4192 buffer
4193 .read(cx)
4194 .language_at(buffer_position)
4195 .map(|l| l.name()),
4196 buffer.read(cx).file(),
4197 cx,
4198 );
4199 if !settings.use_on_type_format {
4200 return None;
4201 }
4202
4203 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4204 // hence we do LSP request & edit on host side only — add formats to host's history.
4205 let push_to_lsp_host_history = true;
4206 // If this is not the host, append its history with new edits.
4207 let push_to_client_history = project.read(cx).is_via_collab();
4208
4209 let on_type_formatting = project.update(cx, |project, cx| {
4210 project.on_type_format(
4211 buffer.clone(),
4212 buffer_position,
4213 input,
4214 push_to_lsp_host_history,
4215 cx,
4216 )
4217 });
4218 Some(cx.spawn_in(window, async move |editor, cx| {
4219 if let Some(transaction) = on_type_formatting.await? {
4220 if push_to_client_history {
4221 buffer
4222 .update(cx, |buffer, _| {
4223 buffer.push_transaction(transaction, Instant::now());
4224 })
4225 .ok();
4226 }
4227 editor.update(cx, |editor, cx| {
4228 editor.refresh_document_highlights(cx);
4229 })?;
4230 }
4231 Ok(())
4232 }))
4233 }
4234
4235 pub fn show_word_completions(
4236 &mut self,
4237 _: &ShowWordCompletions,
4238 window: &mut Window,
4239 cx: &mut Context<Self>,
4240 ) {
4241 self.open_completions_menu(true, None, window, cx);
4242 }
4243
4244 pub fn show_completions(
4245 &mut self,
4246 options: &ShowCompletions,
4247 window: &mut Window,
4248 cx: &mut Context<Self>,
4249 ) {
4250 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4251 }
4252
4253 fn open_completions_menu(
4254 &mut self,
4255 ignore_completion_provider: bool,
4256 trigger: Option<&str>,
4257 window: &mut Window,
4258 cx: &mut Context<Self>,
4259 ) {
4260 if self.pending_rename.is_some() {
4261 return;
4262 }
4263 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4264 return;
4265 }
4266
4267 let position = self.selections.newest_anchor().head();
4268 if position.diff_base_anchor.is_some() {
4269 return;
4270 }
4271 let (buffer, buffer_position) =
4272 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4273 output
4274 } else {
4275 return;
4276 };
4277 let buffer_snapshot = buffer.read(cx).snapshot();
4278 let show_completion_documentation = buffer_snapshot
4279 .settings_at(buffer_position, cx)
4280 .show_completion_documentation;
4281
4282 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4283
4284 let trigger_kind = match trigger {
4285 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4286 CompletionTriggerKind::TRIGGER_CHARACTER
4287 }
4288 _ => CompletionTriggerKind::INVOKED,
4289 };
4290 let completion_context = CompletionContext {
4291 trigger_character: trigger.and_then(|trigger| {
4292 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4293 Some(String::from(trigger))
4294 } else {
4295 None
4296 }
4297 }),
4298 trigger_kind,
4299 };
4300
4301 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4302 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4303 let word_to_exclude = buffer_snapshot
4304 .text_for_range(old_range.clone())
4305 .collect::<String>();
4306 (
4307 buffer_snapshot.anchor_before(old_range.start)
4308 ..buffer_snapshot.anchor_after(old_range.end),
4309 Some(word_to_exclude),
4310 )
4311 } else {
4312 (buffer_position..buffer_position, None)
4313 };
4314
4315 let completion_settings = language_settings(
4316 buffer_snapshot
4317 .language_at(buffer_position)
4318 .map(|language| language.name()),
4319 buffer_snapshot.file(),
4320 cx,
4321 )
4322 .completions;
4323
4324 // The document can be large, so stay in reasonable bounds when searching for words,
4325 // otherwise completion pop-up might be slow to appear.
4326 const WORD_LOOKUP_ROWS: u32 = 5_000;
4327 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4328 let min_word_search = buffer_snapshot.clip_point(
4329 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4330 Bias::Left,
4331 );
4332 let max_word_search = buffer_snapshot.clip_point(
4333 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4334 Bias::Right,
4335 );
4336 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4337 ..buffer_snapshot.point_to_offset(max_word_search);
4338
4339 let provider = self
4340 .completion_provider
4341 .as_ref()
4342 .filter(|_| !ignore_completion_provider);
4343 let skip_digits = query
4344 .as_ref()
4345 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4346
4347 let (mut words, provided_completions) = match provider {
4348 Some(provider) => {
4349 let completions = provider.completions(
4350 position.excerpt_id,
4351 &buffer,
4352 buffer_position,
4353 completion_context,
4354 window,
4355 cx,
4356 );
4357
4358 let words = match completion_settings.words {
4359 WordsCompletionMode::Disabled => Task::ready(HashMap::default()),
4360 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4361 .background_spawn(async move {
4362 buffer_snapshot.words_in_range(WordsQuery {
4363 fuzzy_contents: None,
4364 range: word_search_range,
4365 skip_digits,
4366 })
4367 }),
4368 };
4369
4370 (words, completions)
4371 }
4372 None => (
4373 cx.background_spawn(async move {
4374 buffer_snapshot.words_in_range(WordsQuery {
4375 fuzzy_contents: None,
4376 range: word_search_range,
4377 skip_digits,
4378 })
4379 }),
4380 Task::ready(Ok(None)),
4381 ),
4382 };
4383
4384 let sort_completions = provider
4385 .as_ref()
4386 .map_or(true, |provider| provider.sort_completions());
4387
4388 let filter_completions = provider
4389 .as_ref()
4390 .map_or(true, |provider| provider.filter_completions());
4391
4392 let id = post_inc(&mut self.next_completion_id);
4393 let task = cx.spawn_in(window, async move |editor, cx| {
4394 async move {
4395 editor.update(cx, |this, _| {
4396 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4397 })?;
4398
4399 let mut completions = Vec::new();
4400 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4401 completions.extend(provided_completions);
4402 if completion_settings.words == WordsCompletionMode::Fallback {
4403 words = Task::ready(HashMap::default());
4404 }
4405 }
4406
4407 let mut words = words.await;
4408 if let Some(word_to_exclude) = &word_to_exclude {
4409 words.remove(word_to_exclude);
4410 }
4411 for lsp_completion in &completions {
4412 words.remove(&lsp_completion.new_text);
4413 }
4414 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4415 old_range: old_range.clone(),
4416 new_text: word.clone(),
4417 label: CodeLabel::plain(word, None),
4418 icon_path: None,
4419 documentation: None,
4420 source: CompletionSource::BufferWord {
4421 word_range,
4422 resolved: false,
4423 },
4424 confirm: None,
4425 }));
4426
4427 let menu = if completions.is_empty() {
4428 None
4429 } else {
4430 let mut menu = CompletionsMenu::new(
4431 id,
4432 sort_completions,
4433 show_completion_documentation,
4434 ignore_completion_provider,
4435 position,
4436 buffer.clone(),
4437 completions.into(),
4438 );
4439
4440 menu.filter(
4441 if filter_completions {
4442 query.as_deref()
4443 } else {
4444 None
4445 },
4446 cx.background_executor().clone(),
4447 )
4448 .await;
4449
4450 menu.visible().then_some(menu)
4451 };
4452
4453 editor.update_in(cx, |editor, window, cx| {
4454 match editor.context_menu.borrow().as_ref() {
4455 None => {}
4456 Some(CodeContextMenu::Completions(prev_menu)) => {
4457 if prev_menu.id > id {
4458 return;
4459 }
4460 }
4461 _ => return,
4462 }
4463
4464 if editor.focus_handle.is_focused(window) && menu.is_some() {
4465 let mut menu = menu.unwrap();
4466 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4467
4468 *editor.context_menu.borrow_mut() =
4469 Some(CodeContextMenu::Completions(menu));
4470
4471 if editor.show_edit_predictions_in_menu() {
4472 editor.update_visible_inline_completion(window, cx);
4473 } else {
4474 editor.discard_inline_completion(false, cx);
4475 }
4476
4477 cx.notify();
4478 } else if editor.completion_tasks.len() <= 1 {
4479 // If there are no more completion tasks and the last menu was
4480 // empty, we should hide it.
4481 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4482 // If it was already hidden and we don't show inline
4483 // completions in the menu, we should also show the
4484 // inline-completion when available.
4485 if was_hidden && editor.show_edit_predictions_in_menu() {
4486 editor.update_visible_inline_completion(window, cx);
4487 }
4488 }
4489 })?;
4490
4491 anyhow::Ok(())
4492 }
4493 .log_err()
4494 .await
4495 });
4496
4497 self.completion_tasks.push((id, task));
4498 }
4499
4500 #[cfg(feature = "test-support")]
4501 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4502 let menu = self.context_menu.borrow();
4503 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4504 let completions = menu.completions.borrow();
4505 Some(completions.to_vec())
4506 } else {
4507 None
4508 }
4509 }
4510
4511 pub fn confirm_completion(
4512 &mut self,
4513 action: &ConfirmCompletion,
4514 window: &mut Window,
4515 cx: &mut Context<Self>,
4516 ) -> Option<Task<Result<()>>> {
4517 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4518 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4519 }
4520
4521 pub fn compose_completion(
4522 &mut self,
4523 action: &ComposeCompletion,
4524 window: &mut Window,
4525 cx: &mut Context<Self>,
4526 ) -> Option<Task<Result<()>>> {
4527 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4528 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4529 }
4530
4531 fn do_completion(
4532 &mut self,
4533 item_ix: Option<usize>,
4534 intent: CompletionIntent,
4535 window: &mut Window,
4536 cx: &mut Context<Editor>,
4537 ) -> Option<Task<Result<()>>> {
4538 use language::ToOffset as _;
4539
4540 let completions_menu =
4541 if let CodeContextMenu::Completions(menu) = self.hide_context_menu(window, cx)? {
4542 menu
4543 } else {
4544 return None;
4545 };
4546
4547 let candidate_id = {
4548 let entries = completions_menu.entries.borrow();
4549 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4550 if self.show_edit_predictions_in_menu() {
4551 self.discard_inline_completion(true, cx);
4552 }
4553 mat.candidate_id
4554 };
4555
4556 let buffer_handle = completions_menu.buffer;
4557 let completion = completions_menu
4558 .completions
4559 .borrow()
4560 .get(candidate_id)?
4561 .clone();
4562 cx.stop_propagation();
4563
4564 let snippet;
4565 let new_text;
4566 if completion.is_snippet() {
4567 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4568 new_text = snippet.as_ref().unwrap().text.clone();
4569 } else {
4570 snippet = None;
4571 new_text = completion.new_text.clone();
4572 };
4573 let selections = self.selections.all::<usize>(cx);
4574 let buffer = buffer_handle.read(cx);
4575 let old_range = completion.old_range.to_offset(buffer);
4576 let old_text = buffer.text_for_range(old_range.clone()).collect::<String>();
4577
4578 let newest_selection = self.selections.newest_anchor();
4579 if newest_selection.start.buffer_id != Some(buffer_handle.read(cx).remote_id()) {
4580 return None;
4581 }
4582
4583 let lookbehind = newest_selection
4584 .start
4585 .text_anchor
4586 .to_offset(buffer)
4587 .saturating_sub(old_range.start);
4588 let lookahead = old_range
4589 .end
4590 .saturating_sub(newest_selection.end.text_anchor.to_offset(buffer));
4591 let mut common_prefix_len = old_text
4592 .bytes()
4593 .zip(new_text.bytes())
4594 .take_while(|(a, b)| a == b)
4595 .count();
4596
4597 let snapshot = self.buffer.read(cx).snapshot(cx);
4598 let mut range_to_replace: Option<Range<isize>> = None;
4599 let mut ranges = Vec::new();
4600 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4601 for selection in &selections {
4602 if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
4603 let start = selection.start.saturating_sub(lookbehind);
4604 let end = selection.end + lookahead;
4605 if selection.id == newest_selection.id {
4606 range_to_replace = Some(
4607 ((start + common_prefix_len) as isize - selection.start as isize)
4608 ..(end as isize - selection.start as isize),
4609 );
4610 }
4611 ranges.push(start + common_prefix_len..end);
4612 } else {
4613 common_prefix_len = 0;
4614 ranges.clear();
4615 ranges.extend(selections.iter().map(|s| {
4616 if s.id == newest_selection.id {
4617 range_to_replace = Some(
4618 old_range.start.to_offset_utf16(&snapshot).0 as isize
4619 - selection.start as isize
4620 ..old_range.end.to_offset_utf16(&snapshot).0 as isize
4621 - selection.start as isize,
4622 );
4623 old_range.clone()
4624 } else {
4625 s.start..s.end
4626 }
4627 }));
4628 break;
4629 }
4630 if !self.linked_edit_ranges.is_empty() {
4631 let start_anchor = snapshot.anchor_before(selection.head());
4632 let end_anchor = snapshot.anchor_after(selection.tail());
4633 if let Some(ranges) = self
4634 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4635 {
4636 for (buffer, edits) in ranges {
4637 linked_edits.entry(buffer.clone()).or_default().extend(
4638 edits
4639 .into_iter()
4640 .map(|range| (range, new_text[common_prefix_len..].to_owned())),
4641 );
4642 }
4643 }
4644 }
4645 }
4646 let text = &new_text[common_prefix_len..];
4647
4648 cx.emit(EditorEvent::InputHandled {
4649 utf16_range_to_replace: range_to_replace,
4650 text: text.into(),
4651 });
4652
4653 self.transact(window, cx, |this, window, cx| {
4654 if let Some(mut snippet) = snippet {
4655 snippet.text = text.to_string();
4656 for tabstop in snippet
4657 .tabstops
4658 .iter_mut()
4659 .flat_map(|tabstop| tabstop.ranges.iter_mut())
4660 {
4661 tabstop.start -= common_prefix_len as isize;
4662 tabstop.end -= common_prefix_len as isize;
4663 }
4664
4665 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4666 } else {
4667 this.buffer.update(cx, |buffer, cx| {
4668 let edits = ranges.iter().map(|range| (range.clone(), text));
4669 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4670 });
4671 }
4672 for (buffer, edits) in linked_edits {
4673 buffer.update(cx, |buffer, cx| {
4674 let snapshot = buffer.snapshot();
4675 let edits = edits
4676 .into_iter()
4677 .map(|(range, text)| {
4678 use text::ToPoint as TP;
4679 let end_point = TP::to_point(&range.end, &snapshot);
4680 let start_point = TP::to_point(&range.start, &snapshot);
4681 (start_point..end_point, text)
4682 })
4683 .sorted_by_key(|(range, _)| range.start);
4684 buffer.edit(edits, None, cx);
4685 })
4686 }
4687
4688 this.refresh_inline_completion(true, false, window, cx);
4689 });
4690
4691 let show_new_completions_on_confirm = completion
4692 .confirm
4693 .as_ref()
4694 .map_or(false, |confirm| confirm(intent, window, cx));
4695 if show_new_completions_on_confirm {
4696 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4697 }
4698
4699 let provider = self.completion_provider.as_ref()?;
4700 drop(completion);
4701 let apply_edits = provider.apply_additional_edits_for_completion(
4702 buffer_handle,
4703 completions_menu.completions.clone(),
4704 candidate_id,
4705 true,
4706 cx,
4707 );
4708
4709 let editor_settings = EditorSettings::get_global(cx);
4710 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4711 // After the code completion is finished, users often want to know what signatures are needed.
4712 // so we should automatically call signature_help
4713 self.show_signature_help(&ShowSignatureHelp, window, cx);
4714 }
4715
4716 Some(cx.foreground_executor().spawn(async move {
4717 apply_edits.await?;
4718 Ok(())
4719 }))
4720 }
4721
4722 pub fn toggle_code_actions(
4723 &mut self,
4724 action: &ToggleCodeActions,
4725 window: &mut Window,
4726 cx: &mut Context<Self>,
4727 ) {
4728 let mut context_menu = self.context_menu.borrow_mut();
4729 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4730 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4731 // Toggle if we're selecting the same one
4732 *context_menu = None;
4733 cx.notify();
4734 return;
4735 } else {
4736 // Otherwise, clear it and start a new one
4737 *context_menu = None;
4738 cx.notify();
4739 }
4740 }
4741 drop(context_menu);
4742 let snapshot = self.snapshot(window, cx);
4743 let deployed_from_indicator = action.deployed_from_indicator;
4744 let mut task = self.code_actions_task.take();
4745 let action = action.clone();
4746 cx.spawn_in(window, async move |editor, cx| {
4747 while let Some(prev_task) = task {
4748 prev_task.await.log_err();
4749 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4750 }
4751
4752 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
4753 if editor.focus_handle.is_focused(window) {
4754 let multibuffer_point = action
4755 .deployed_from_indicator
4756 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4757 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
4758 let (buffer, buffer_row) = snapshot
4759 .buffer_snapshot
4760 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4761 .and_then(|(buffer_snapshot, range)| {
4762 editor
4763 .buffer
4764 .read(cx)
4765 .buffer(buffer_snapshot.remote_id())
4766 .map(|buffer| (buffer, range.start.row))
4767 })?;
4768 let (_, code_actions) = editor
4769 .available_code_actions
4770 .clone()
4771 .and_then(|(location, code_actions)| {
4772 let snapshot = location.buffer.read(cx).snapshot();
4773 let point_range = location.range.to_point(&snapshot);
4774 let point_range = point_range.start.row..=point_range.end.row;
4775 if point_range.contains(&buffer_row) {
4776 Some((location, code_actions))
4777 } else {
4778 None
4779 }
4780 })
4781 .unzip();
4782 let buffer_id = buffer.read(cx).remote_id();
4783 let tasks = editor
4784 .tasks
4785 .get(&(buffer_id, buffer_row))
4786 .map(|t| Arc::new(t.to_owned()));
4787 if tasks.is_none() && code_actions.is_none() {
4788 return None;
4789 }
4790
4791 editor.completion_tasks.clear();
4792 editor.discard_inline_completion(false, cx);
4793 let task_context =
4794 tasks
4795 .as_ref()
4796 .zip(editor.project.clone())
4797 .map(|(tasks, project)| {
4798 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4799 });
4800
4801 let debugger_flag = cx.has_flag::<Debugger>();
4802
4803 Some(cx.spawn_in(window, async move |editor, cx| {
4804 let task_context = match task_context {
4805 Some(task_context) => task_context.await,
4806 None => None,
4807 };
4808 let resolved_tasks =
4809 tasks.zip(task_context).map(|(tasks, task_context)| {
4810 Rc::new(ResolvedTasks {
4811 templates: tasks.resolve(&task_context).collect(),
4812 position: snapshot.buffer_snapshot.anchor_before(Point::new(
4813 multibuffer_point.row,
4814 tasks.column,
4815 )),
4816 })
4817 });
4818 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
4819 tasks
4820 .templates
4821 .iter()
4822 .filter(|task| {
4823 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4824 debugger_flag
4825 } else {
4826 true
4827 }
4828 })
4829 .count()
4830 == 1
4831 }) && code_actions
4832 .as_ref()
4833 .map_or(true, |actions| actions.is_empty());
4834 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
4835 *editor.context_menu.borrow_mut() =
4836 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
4837 buffer,
4838 actions: CodeActionContents {
4839 tasks: resolved_tasks,
4840 actions: code_actions,
4841 },
4842 selected_item: Default::default(),
4843 scroll_handle: UniformListScrollHandle::default(),
4844 deployed_from_indicator,
4845 }));
4846 if spawn_straight_away {
4847 if let Some(task) = editor.confirm_code_action(
4848 &ConfirmCodeAction { item_ix: Some(0) },
4849 window,
4850 cx,
4851 ) {
4852 cx.notify();
4853 return task;
4854 }
4855 }
4856 cx.notify();
4857 Task::ready(Ok(()))
4858 }) {
4859 task.await
4860 } else {
4861 Ok(())
4862 }
4863 }))
4864 } else {
4865 Some(Task::ready(Ok(())))
4866 }
4867 })?;
4868 if let Some(task) = spawned_test_task {
4869 task.await?;
4870 }
4871
4872 Ok::<_, anyhow::Error>(())
4873 })
4874 .detach_and_log_err(cx);
4875 }
4876
4877 pub fn confirm_code_action(
4878 &mut self,
4879 action: &ConfirmCodeAction,
4880 window: &mut Window,
4881 cx: &mut Context<Self>,
4882 ) -> Option<Task<Result<()>>> {
4883 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4884
4885 let actions_menu =
4886 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
4887 menu
4888 } else {
4889 return None;
4890 };
4891
4892 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
4893 let action = actions_menu.actions.get(action_ix)?;
4894 let title = action.label();
4895 let buffer = actions_menu.buffer;
4896 let workspace = self.workspace()?;
4897
4898 match action {
4899 CodeActionsItem::Task(task_source_kind, resolved_task) => {
4900 match resolved_task.task_type() {
4901 task::TaskType::Script => 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 Some(Task::ready(Ok(())))
4911 }),
4912 task::TaskType::Debug(debug_args) => {
4913 if debug_args.locator.is_some() {
4914 workspace.update(cx, |workspace, cx| {
4915 workspace::tasks::schedule_resolved_task(
4916 workspace,
4917 task_source_kind,
4918 resolved_task,
4919 false,
4920 cx,
4921 );
4922 });
4923
4924 return Some(Task::ready(Ok(())));
4925 }
4926
4927 if let Some(project) = self.project.as_ref() {
4928 project
4929 .update(cx, |project, cx| {
4930 project.start_debug_session(
4931 resolved_task.resolved_debug_adapter_config().unwrap(),
4932 cx,
4933 )
4934 })
4935 .detach_and_log_err(cx);
4936 Some(Task::ready(Ok(())))
4937 } else {
4938 Some(Task::ready(Ok(())))
4939 }
4940 }
4941 }
4942 }
4943 CodeActionsItem::CodeAction {
4944 excerpt_id,
4945 action,
4946 provider,
4947 } => {
4948 let apply_code_action =
4949 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
4950 let workspace = workspace.downgrade();
4951 Some(cx.spawn_in(window, async move |editor, cx| {
4952 let project_transaction = apply_code_action.await?;
4953 Self::open_project_transaction(
4954 &editor,
4955 workspace,
4956 project_transaction,
4957 title,
4958 cx,
4959 )
4960 .await
4961 }))
4962 }
4963 }
4964 }
4965
4966 pub async fn open_project_transaction(
4967 this: &WeakEntity<Editor>,
4968 workspace: WeakEntity<Workspace>,
4969 transaction: ProjectTransaction,
4970 title: String,
4971 cx: &mut AsyncWindowContext,
4972 ) -> Result<()> {
4973 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
4974 cx.update(|_, cx| {
4975 entries.sort_unstable_by_key(|(buffer, _)| {
4976 buffer.read(cx).file().map(|f| f.path().clone())
4977 });
4978 })?;
4979
4980 // If the project transaction's edits are all contained within this editor, then
4981 // avoid opening a new editor to display them.
4982
4983 if let Some((buffer, transaction)) = entries.first() {
4984 if entries.len() == 1 {
4985 let excerpt = this.update(cx, |editor, cx| {
4986 editor
4987 .buffer()
4988 .read(cx)
4989 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
4990 })?;
4991 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
4992 if excerpted_buffer == *buffer {
4993 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
4994 let excerpt_range = excerpt_range.to_offset(buffer);
4995 buffer
4996 .edited_ranges_for_transaction::<usize>(transaction)
4997 .all(|range| {
4998 excerpt_range.start <= range.start
4999 && excerpt_range.end >= range.end
5000 })
5001 })?;
5002
5003 if all_edits_within_excerpt {
5004 return Ok(());
5005 }
5006 }
5007 }
5008 }
5009 } else {
5010 return Ok(());
5011 }
5012
5013 let mut ranges_to_highlight = Vec::new();
5014 let excerpt_buffer = cx.new(|cx| {
5015 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5016 for (buffer_handle, transaction) in &entries {
5017 let buffer = buffer_handle.read(cx);
5018 ranges_to_highlight.extend(
5019 multibuffer.push_excerpts_with_context_lines(
5020 buffer_handle.clone(),
5021 buffer
5022 .edited_ranges_for_transaction::<usize>(transaction)
5023 .collect(),
5024 DEFAULT_MULTIBUFFER_CONTEXT,
5025 cx,
5026 ),
5027 );
5028 }
5029 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5030 multibuffer
5031 })?;
5032
5033 workspace.update_in(cx, |workspace, window, cx| {
5034 let project = workspace.project().clone();
5035 let editor =
5036 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5037 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5038 editor.update(cx, |editor, cx| {
5039 editor.highlight_background::<Self>(
5040 &ranges_to_highlight,
5041 |theme| theme.editor_highlighted_line_background,
5042 cx,
5043 );
5044 });
5045 })?;
5046
5047 Ok(())
5048 }
5049
5050 pub fn clear_code_action_providers(&mut self) {
5051 self.code_action_providers.clear();
5052 self.available_code_actions.take();
5053 }
5054
5055 pub fn add_code_action_provider(
5056 &mut self,
5057 provider: Rc<dyn CodeActionProvider>,
5058 window: &mut Window,
5059 cx: &mut Context<Self>,
5060 ) {
5061 if self
5062 .code_action_providers
5063 .iter()
5064 .any(|existing_provider| existing_provider.id() == provider.id())
5065 {
5066 return;
5067 }
5068
5069 self.code_action_providers.push(provider);
5070 self.refresh_code_actions(window, cx);
5071 }
5072
5073 pub fn remove_code_action_provider(
5074 &mut self,
5075 id: Arc<str>,
5076 window: &mut Window,
5077 cx: &mut Context<Self>,
5078 ) {
5079 self.code_action_providers
5080 .retain(|provider| provider.id() != id);
5081 self.refresh_code_actions(window, cx);
5082 }
5083
5084 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5085 let buffer = self.buffer.read(cx);
5086 let newest_selection = self.selections.newest_anchor().clone();
5087 if newest_selection.head().diff_base_anchor.is_some() {
5088 return None;
5089 }
5090 let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
5091 let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
5092 if start_buffer != end_buffer {
5093 return None;
5094 }
5095
5096 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5097 cx.background_executor()
5098 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5099 .await;
5100
5101 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5102 let providers = this.code_action_providers.clone();
5103 let tasks = this
5104 .code_action_providers
5105 .iter()
5106 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5107 .collect::<Vec<_>>();
5108 (providers, tasks)
5109 })?;
5110
5111 let mut actions = Vec::new();
5112 for (provider, provider_actions) in
5113 providers.into_iter().zip(future::join_all(tasks).await)
5114 {
5115 if let Some(provider_actions) = provider_actions.log_err() {
5116 actions.extend(provider_actions.into_iter().map(|action| {
5117 AvailableCodeAction {
5118 excerpt_id: newest_selection.start.excerpt_id,
5119 action,
5120 provider: provider.clone(),
5121 }
5122 }));
5123 }
5124 }
5125
5126 this.update(cx, |this, cx| {
5127 this.available_code_actions = if actions.is_empty() {
5128 None
5129 } else {
5130 Some((
5131 Location {
5132 buffer: start_buffer,
5133 range: start..end,
5134 },
5135 actions.into(),
5136 ))
5137 };
5138 cx.notify();
5139 })
5140 }));
5141 None
5142 }
5143
5144 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5145 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5146 self.show_git_blame_inline = false;
5147
5148 self.show_git_blame_inline_delay_task =
5149 Some(cx.spawn_in(window, async move |this, cx| {
5150 cx.background_executor().timer(delay).await;
5151
5152 this.update(cx, |this, cx| {
5153 this.show_git_blame_inline = true;
5154 cx.notify();
5155 })
5156 .log_err();
5157 }));
5158 }
5159 }
5160
5161 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5162 if self.pending_rename.is_some() {
5163 return None;
5164 }
5165
5166 let provider = self.semantics_provider.clone()?;
5167 let buffer = self.buffer.read(cx);
5168 let newest_selection = self.selections.newest_anchor().clone();
5169 let cursor_position = newest_selection.head();
5170 let (cursor_buffer, cursor_buffer_position) =
5171 buffer.text_anchor_for_position(cursor_position, cx)?;
5172 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5173 if cursor_buffer != tail_buffer {
5174 return None;
5175 }
5176 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5177 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5178 cx.background_executor()
5179 .timer(Duration::from_millis(debounce))
5180 .await;
5181
5182 let highlights = if let Some(highlights) = cx
5183 .update(|cx| {
5184 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5185 })
5186 .ok()
5187 .flatten()
5188 {
5189 highlights.await.log_err()
5190 } else {
5191 None
5192 };
5193
5194 if let Some(highlights) = highlights {
5195 this.update(cx, |this, cx| {
5196 if this.pending_rename.is_some() {
5197 return;
5198 }
5199
5200 let buffer_id = cursor_position.buffer_id;
5201 let buffer = this.buffer.read(cx);
5202 if !buffer
5203 .text_anchor_for_position(cursor_position, cx)
5204 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5205 {
5206 return;
5207 }
5208
5209 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5210 let mut write_ranges = Vec::new();
5211 let mut read_ranges = Vec::new();
5212 for highlight in highlights {
5213 for (excerpt_id, excerpt_range) in
5214 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5215 {
5216 let start = highlight
5217 .range
5218 .start
5219 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5220 let end = highlight
5221 .range
5222 .end
5223 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5224 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5225 continue;
5226 }
5227
5228 let range = Anchor {
5229 buffer_id,
5230 excerpt_id,
5231 text_anchor: start,
5232 diff_base_anchor: None,
5233 }..Anchor {
5234 buffer_id,
5235 excerpt_id,
5236 text_anchor: end,
5237 diff_base_anchor: None,
5238 };
5239 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5240 write_ranges.push(range);
5241 } else {
5242 read_ranges.push(range);
5243 }
5244 }
5245 }
5246
5247 this.highlight_background::<DocumentHighlightRead>(
5248 &read_ranges,
5249 |theme| theme.editor_document_highlight_read_background,
5250 cx,
5251 );
5252 this.highlight_background::<DocumentHighlightWrite>(
5253 &write_ranges,
5254 |theme| theme.editor_document_highlight_write_background,
5255 cx,
5256 );
5257 cx.notify();
5258 })
5259 .log_err();
5260 }
5261 }));
5262 None
5263 }
5264
5265 pub fn refresh_selected_text_highlights(
5266 &mut self,
5267 window: &mut Window,
5268 cx: &mut Context<Editor>,
5269 ) {
5270 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5271 return;
5272 }
5273 self.selection_highlight_task.take();
5274 if !EditorSettings::get_global(cx).selection_highlight {
5275 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5276 return;
5277 }
5278 if self.selections.count() != 1 || self.selections.line_mode {
5279 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5280 return;
5281 }
5282 let selection = self.selections.newest::<Point>(cx);
5283 if selection.is_empty() || selection.start.row != selection.end.row {
5284 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5285 return;
5286 }
5287 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5288 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5289 cx.background_executor()
5290 .timer(Duration::from_millis(debounce))
5291 .await;
5292 let Some(Some(matches_task)) = editor
5293 .update_in(cx, |editor, _, cx| {
5294 if editor.selections.count() != 1 || editor.selections.line_mode {
5295 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5296 return None;
5297 }
5298 let selection = editor.selections.newest::<Point>(cx);
5299 if selection.is_empty() || selection.start.row != selection.end.row {
5300 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5301 return None;
5302 }
5303 let buffer = editor.buffer().read(cx).snapshot(cx);
5304 let query = buffer.text_for_range(selection.range()).collect::<String>();
5305 if query.trim().is_empty() {
5306 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5307 return None;
5308 }
5309 Some(cx.background_spawn(async move {
5310 let mut ranges = Vec::new();
5311 let selection_anchors = selection.range().to_anchors(&buffer);
5312 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5313 for (search_buffer, search_range, excerpt_id) in
5314 buffer.range_to_buffer_ranges(range)
5315 {
5316 ranges.extend(
5317 project::search::SearchQuery::text(
5318 query.clone(),
5319 false,
5320 false,
5321 false,
5322 Default::default(),
5323 Default::default(),
5324 None,
5325 )
5326 .unwrap()
5327 .search(search_buffer, Some(search_range.clone()))
5328 .await
5329 .into_iter()
5330 .filter_map(
5331 |match_range| {
5332 let start = search_buffer.anchor_after(
5333 search_range.start + match_range.start,
5334 );
5335 let end = search_buffer.anchor_before(
5336 search_range.start + match_range.end,
5337 );
5338 let range = Anchor::range_in_buffer(
5339 excerpt_id,
5340 search_buffer.remote_id(),
5341 start..end,
5342 );
5343 (range != selection_anchors).then_some(range)
5344 },
5345 ),
5346 );
5347 }
5348 }
5349 ranges
5350 }))
5351 })
5352 .log_err()
5353 else {
5354 return;
5355 };
5356 let matches = matches_task.await;
5357 editor
5358 .update_in(cx, |editor, _, cx| {
5359 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5360 if !matches.is_empty() {
5361 editor.highlight_background::<SelectedTextHighlight>(
5362 &matches,
5363 |theme| theme.editor_document_highlight_bracket_background,
5364 cx,
5365 )
5366 }
5367 })
5368 .log_err();
5369 }));
5370 }
5371
5372 pub fn refresh_inline_completion(
5373 &mut self,
5374 debounce: bool,
5375 user_requested: bool,
5376 window: &mut Window,
5377 cx: &mut Context<Self>,
5378 ) -> Option<()> {
5379 let provider = self.edit_prediction_provider()?;
5380 let cursor = self.selections.newest_anchor().head();
5381 let (buffer, cursor_buffer_position) =
5382 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5383
5384 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5385 self.discard_inline_completion(false, cx);
5386 return None;
5387 }
5388
5389 if !user_requested
5390 && (!self.should_show_edit_predictions()
5391 || !self.is_focused(window)
5392 || buffer.read(cx).is_empty())
5393 {
5394 self.discard_inline_completion(false, cx);
5395 return None;
5396 }
5397
5398 self.update_visible_inline_completion(window, cx);
5399 provider.refresh(
5400 self.project.clone(),
5401 buffer,
5402 cursor_buffer_position,
5403 debounce,
5404 cx,
5405 );
5406 Some(())
5407 }
5408
5409 fn show_edit_predictions_in_menu(&self) -> bool {
5410 match self.edit_prediction_settings {
5411 EditPredictionSettings::Disabled => false,
5412 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5413 }
5414 }
5415
5416 pub fn edit_predictions_enabled(&self) -> bool {
5417 match self.edit_prediction_settings {
5418 EditPredictionSettings::Disabled => false,
5419 EditPredictionSettings::Enabled { .. } => true,
5420 }
5421 }
5422
5423 fn edit_prediction_requires_modifier(&self) -> bool {
5424 match self.edit_prediction_settings {
5425 EditPredictionSettings::Disabled => false,
5426 EditPredictionSettings::Enabled {
5427 preview_requires_modifier,
5428 ..
5429 } => preview_requires_modifier,
5430 }
5431 }
5432
5433 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5434 if self.edit_prediction_provider.is_none() {
5435 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5436 } else {
5437 let selection = self.selections.newest_anchor();
5438 let cursor = selection.head();
5439
5440 if let Some((buffer, cursor_buffer_position)) =
5441 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5442 {
5443 self.edit_prediction_settings =
5444 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5445 }
5446 }
5447 }
5448
5449 fn edit_prediction_settings_at_position(
5450 &self,
5451 buffer: &Entity<Buffer>,
5452 buffer_position: language::Anchor,
5453 cx: &App,
5454 ) -> EditPredictionSettings {
5455 if self.mode != EditorMode::Full
5456 || !self.show_inline_completions_override.unwrap_or(true)
5457 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5458 {
5459 return EditPredictionSettings::Disabled;
5460 }
5461
5462 let buffer = buffer.read(cx);
5463
5464 let file = buffer.file();
5465
5466 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5467 return EditPredictionSettings::Disabled;
5468 };
5469
5470 let by_provider = matches!(
5471 self.menu_inline_completions_policy,
5472 MenuInlineCompletionsPolicy::ByProvider
5473 );
5474
5475 let show_in_menu = by_provider
5476 && self
5477 .edit_prediction_provider
5478 .as_ref()
5479 .map_or(false, |provider| {
5480 provider.provider.show_completions_in_menu()
5481 });
5482
5483 let preview_requires_modifier =
5484 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5485
5486 EditPredictionSettings::Enabled {
5487 show_in_menu,
5488 preview_requires_modifier,
5489 }
5490 }
5491
5492 fn should_show_edit_predictions(&self) -> bool {
5493 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5494 }
5495
5496 pub fn edit_prediction_preview_is_active(&self) -> bool {
5497 matches!(
5498 self.edit_prediction_preview,
5499 EditPredictionPreview::Active { .. }
5500 )
5501 }
5502
5503 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5504 let cursor = self.selections.newest_anchor().head();
5505 if let Some((buffer, cursor_position)) =
5506 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5507 {
5508 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5509 } else {
5510 false
5511 }
5512 }
5513
5514 fn edit_predictions_enabled_in_buffer(
5515 &self,
5516 buffer: &Entity<Buffer>,
5517 buffer_position: language::Anchor,
5518 cx: &App,
5519 ) -> bool {
5520 maybe!({
5521 if self.read_only(cx) {
5522 return Some(false);
5523 }
5524 let provider = self.edit_prediction_provider()?;
5525 if !provider.is_enabled(&buffer, buffer_position, cx) {
5526 return Some(false);
5527 }
5528 let buffer = buffer.read(cx);
5529 let Some(file) = buffer.file() else {
5530 return Some(true);
5531 };
5532 let settings = all_language_settings(Some(file), cx);
5533 Some(settings.edit_predictions_enabled_for_file(file, cx))
5534 })
5535 .unwrap_or(false)
5536 }
5537
5538 fn cycle_inline_completion(
5539 &mut self,
5540 direction: Direction,
5541 window: &mut Window,
5542 cx: &mut Context<Self>,
5543 ) -> Option<()> {
5544 let provider = self.edit_prediction_provider()?;
5545 let cursor = self.selections.newest_anchor().head();
5546 let (buffer, cursor_buffer_position) =
5547 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5548 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5549 return None;
5550 }
5551
5552 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5553 self.update_visible_inline_completion(window, cx);
5554
5555 Some(())
5556 }
5557
5558 pub fn show_inline_completion(
5559 &mut self,
5560 _: &ShowEditPrediction,
5561 window: &mut Window,
5562 cx: &mut Context<Self>,
5563 ) {
5564 if !self.has_active_inline_completion() {
5565 self.refresh_inline_completion(false, true, window, cx);
5566 return;
5567 }
5568
5569 self.update_visible_inline_completion(window, cx);
5570 }
5571
5572 pub fn display_cursor_names(
5573 &mut self,
5574 _: &DisplayCursorNames,
5575 window: &mut Window,
5576 cx: &mut Context<Self>,
5577 ) {
5578 self.show_cursor_names(window, cx);
5579 }
5580
5581 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5582 self.show_cursor_names = true;
5583 cx.notify();
5584 cx.spawn_in(window, async move |this, cx| {
5585 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5586 this.update(cx, |this, cx| {
5587 this.show_cursor_names = false;
5588 cx.notify()
5589 })
5590 .ok()
5591 })
5592 .detach();
5593 }
5594
5595 pub fn next_edit_prediction(
5596 &mut self,
5597 _: &NextEditPrediction,
5598 window: &mut Window,
5599 cx: &mut Context<Self>,
5600 ) {
5601 if self.has_active_inline_completion() {
5602 self.cycle_inline_completion(Direction::Next, window, cx);
5603 } else {
5604 let is_copilot_disabled = self
5605 .refresh_inline_completion(false, true, window, cx)
5606 .is_none();
5607 if is_copilot_disabled {
5608 cx.propagate();
5609 }
5610 }
5611 }
5612
5613 pub fn previous_edit_prediction(
5614 &mut self,
5615 _: &PreviousEditPrediction,
5616 window: &mut Window,
5617 cx: &mut Context<Self>,
5618 ) {
5619 if self.has_active_inline_completion() {
5620 self.cycle_inline_completion(Direction::Prev, window, cx);
5621 } else {
5622 let is_copilot_disabled = self
5623 .refresh_inline_completion(false, true, window, cx)
5624 .is_none();
5625 if is_copilot_disabled {
5626 cx.propagate();
5627 }
5628 }
5629 }
5630
5631 pub fn accept_edit_prediction(
5632 &mut self,
5633 _: &AcceptEditPrediction,
5634 window: &mut Window,
5635 cx: &mut Context<Self>,
5636 ) {
5637 if self.show_edit_predictions_in_menu() {
5638 self.hide_context_menu(window, cx);
5639 }
5640
5641 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5642 return;
5643 };
5644
5645 self.report_inline_completion_event(
5646 active_inline_completion.completion_id.clone(),
5647 true,
5648 cx,
5649 );
5650
5651 match &active_inline_completion.completion {
5652 InlineCompletion::Move { target, .. } => {
5653 let target = *target;
5654
5655 if let Some(position_map) = &self.last_position_map {
5656 if position_map
5657 .visible_row_range
5658 .contains(&target.to_display_point(&position_map.snapshot).row())
5659 || !self.edit_prediction_requires_modifier()
5660 {
5661 self.unfold_ranges(&[target..target], true, false, cx);
5662 // Note that this is also done in vim's handler of the Tab action.
5663 self.change_selections(
5664 Some(Autoscroll::newest()),
5665 window,
5666 cx,
5667 |selections| {
5668 selections.select_anchor_ranges([target..target]);
5669 },
5670 );
5671 self.clear_row_highlights::<EditPredictionPreview>();
5672
5673 self.edit_prediction_preview
5674 .set_previous_scroll_position(None);
5675 } else {
5676 self.edit_prediction_preview
5677 .set_previous_scroll_position(Some(
5678 position_map.snapshot.scroll_anchor,
5679 ));
5680
5681 self.highlight_rows::<EditPredictionPreview>(
5682 target..target,
5683 cx.theme().colors().editor_highlighted_line_background,
5684 true,
5685 cx,
5686 );
5687 self.request_autoscroll(Autoscroll::fit(), cx);
5688 }
5689 }
5690 }
5691 InlineCompletion::Edit { edits, .. } => {
5692 if let Some(provider) = self.edit_prediction_provider() {
5693 provider.accept(cx);
5694 }
5695
5696 let snapshot = self.buffer.read(cx).snapshot(cx);
5697 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5698
5699 self.buffer.update(cx, |buffer, cx| {
5700 buffer.edit(edits.iter().cloned(), None, cx)
5701 });
5702
5703 self.change_selections(None, window, cx, |s| {
5704 s.select_anchor_ranges([last_edit_end..last_edit_end])
5705 });
5706
5707 self.update_visible_inline_completion(window, cx);
5708 if self.active_inline_completion.is_none() {
5709 self.refresh_inline_completion(true, true, window, cx);
5710 }
5711
5712 cx.notify();
5713 }
5714 }
5715
5716 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5717 }
5718
5719 pub fn accept_partial_inline_completion(
5720 &mut self,
5721 _: &AcceptPartialEditPrediction,
5722 window: &mut Window,
5723 cx: &mut Context<Self>,
5724 ) {
5725 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5726 return;
5727 };
5728 if self.selections.count() != 1 {
5729 return;
5730 }
5731
5732 self.report_inline_completion_event(
5733 active_inline_completion.completion_id.clone(),
5734 true,
5735 cx,
5736 );
5737
5738 match &active_inline_completion.completion {
5739 InlineCompletion::Move { target, .. } => {
5740 let target = *target;
5741 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5742 selections.select_anchor_ranges([target..target]);
5743 });
5744 }
5745 InlineCompletion::Edit { edits, .. } => {
5746 // Find an insertion that starts at the cursor position.
5747 let snapshot = self.buffer.read(cx).snapshot(cx);
5748 let cursor_offset = self.selections.newest::<usize>(cx).head();
5749 let insertion = edits.iter().find_map(|(range, text)| {
5750 let range = range.to_offset(&snapshot);
5751 if range.is_empty() && range.start == cursor_offset {
5752 Some(text)
5753 } else {
5754 None
5755 }
5756 });
5757
5758 if let Some(text) = insertion {
5759 let mut partial_completion = text
5760 .chars()
5761 .by_ref()
5762 .take_while(|c| c.is_alphabetic())
5763 .collect::<String>();
5764 if partial_completion.is_empty() {
5765 partial_completion = text
5766 .chars()
5767 .by_ref()
5768 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5769 .collect::<String>();
5770 }
5771
5772 cx.emit(EditorEvent::InputHandled {
5773 utf16_range_to_replace: None,
5774 text: partial_completion.clone().into(),
5775 });
5776
5777 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5778
5779 self.refresh_inline_completion(true, true, window, cx);
5780 cx.notify();
5781 } else {
5782 self.accept_edit_prediction(&Default::default(), window, cx);
5783 }
5784 }
5785 }
5786 }
5787
5788 fn discard_inline_completion(
5789 &mut self,
5790 should_report_inline_completion_event: bool,
5791 cx: &mut Context<Self>,
5792 ) -> bool {
5793 if should_report_inline_completion_event {
5794 let completion_id = self
5795 .active_inline_completion
5796 .as_ref()
5797 .and_then(|active_completion| active_completion.completion_id.clone());
5798
5799 self.report_inline_completion_event(completion_id, false, cx);
5800 }
5801
5802 if let Some(provider) = self.edit_prediction_provider() {
5803 provider.discard(cx);
5804 }
5805
5806 self.take_active_inline_completion(cx)
5807 }
5808
5809 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5810 let Some(provider) = self.edit_prediction_provider() else {
5811 return;
5812 };
5813
5814 let Some((_, buffer, _)) = self
5815 .buffer
5816 .read(cx)
5817 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5818 else {
5819 return;
5820 };
5821
5822 let extension = buffer
5823 .read(cx)
5824 .file()
5825 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
5826
5827 let event_type = match accepted {
5828 true => "Edit Prediction Accepted",
5829 false => "Edit Prediction Discarded",
5830 };
5831 telemetry::event!(
5832 event_type,
5833 provider = provider.name(),
5834 prediction_id = id,
5835 suggestion_accepted = accepted,
5836 file_extension = extension,
5837 );
5838 }
5839
5840 pub fn has_active_inline_completion(&self) -> bool {
5841 self.active_inline_completion.is_some()
5842 }
5843
5844 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
5845 let Some(active_inline_completion) = self.active_inline_completion.take() else {
5846 return false;
5847 };
5848
5849 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
5850 self.clear_highlights::<InlineCompletionHighlight>(cx);
5851 self.stale_inline_completion_in_menu = Some(active_inline_completion);
5852 true
5853 }
5854
5855 /// Returns true when we're displaying the edit prediction popover below the cursor
5856 /// like we are not previewing and the LSP autocomplete menu is visible
5857 /// or we are in `when_holding_modifier` mode.
5858 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
5859 if self.edit_prediction_preview_is_active()
5860 || !self.show_edit_predictions_in_menu()
5861 || !self.edit_predictions_enabled()
5862 {
5863 return false;
5864 }
5865
5866 if self.has_visible_completions_menu() {
5867 return true;
5868 }
5869
5870 has_completion && self.edit_prediction_requires_modifier()
5871 }
5872
5873 fn handle_modifiers_changed(
5874 &mut self,
5875 modifiers: Modifiers,
5876 position_map: &PositionMap,
5877 window: &mut Window,
5878 cx: &mut Context<Self>,
5879 ) {
5880 if self.show_edit_predictions_in_menu() {
5881 self.update_edit_prediction_preview(&modifiers, window, cx);
5882 }
5883
5884 self.update_selection_mode(&modifiers, position_map, window, cx);
5885
5886 let mouse_position = window.mouse_position();
5887 if !position_map.text_hitbox.is_hovered(window) {
5888 return;
5889 }
5890
5891 self.update_hovered_link(
5892 position_map.point_for_position(mouse_position),
5893 &position_map.snapshot,
5894 modifiers,
5895 window,
5896 cx,
5897 )
5898 }
5899
5900 fn update_selection_mode(
5901 &mut self,
5902 modifiers: &Modifiers,
5903 position_map: &PositionMap,
5904 window: &mut Window,
5905 cx: &mut Context<Self>,
5906 ) {
5907 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
5908 return;
5909 }
5910
5911 let mouse_position = window.mouse_position();
5912 let point_for_position = position_map.point_for_position(mouse_position);
5913 let position = point_for_position.previous_valid;
5914
5915 self.select(
5916 SelectPhase::BeginColumnar {
5917 position,
5918 reset: false,
5919 goal_column: point_for_position.exact_unclipped.column(),
5920 },
5921 window,
5922 cx,
5923 );
5924 }
5925
5926 fn update_edit_prediction_preview(
5927 &mut self,
5928 modifiers: &Modifiers,
5929 window: &mut Window,
5930 cx: &mut Context<Self>,
5931 ) {
5932 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
5933 let Some(accept_keystroke) = accept_keybind.keystroke() else {
5934 return;
5935 };
5936
5937 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
5938 if matches!(
5939 self.edit_prediction_preview,
5940 EditPredictionPreview::Inactive { .. }
5941 ) {
5942 self.edit_prediction_preview = EditPredictionPreview::Active {
5943 previous_scroll_position: None,
5944 since: Instant::now(),
5945 };
5946
5947 self.update_visible_inline_completion(window, cx);
5948 cx.notify();
5949 }
5950 } else if let EditPredictionPreview::Active {
5951 previous_scroll_position,
5952 since,
5953 } = self.edit_prediction_preview
5954 {
5955 if let (Some(previous_scroll_position), Some(position_map)) =
5956 (previous_scroll_position, self.last_position_map.as_ref())
5957 {
5958 self.set_scroll_position(
5959 previous_scroll_position
5960 .scroll_position(&position_map.snapshot.display_snapshot),
5961 window,
5962 cx,
5963 );
5964 }
5965
5966 self.edit_prediction_preview = EditPredictionPreview::Inactive {
5967 released_too_fast: since.elapsed() < Duration::from_millis(200),
5968 };
5969 self.clear_row_highlights::<EditPredictionPreview>();
5970 self.update_visible_inline_completion(window, cx);
5971 cx.notify();
5972 }
5973 }
5974
5975 fn update_visible_inline_completion(
5976 &mut self,
5977 _window: &mut Window,
5978 cx: &mut Context<Self>,
5979 ) -> Option<()> {
5980 let selection = self.selections.newest_anchor();
5981 let cursor = selection.head();
5982 let multibuffer = self.buffer.read(cx).snapshot(cx);
5983 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
5984 let excerpt_id = cursor.excerpt_id;
5985
5986 let show_in_menu = self.show_edit_predictions_in_menu();
5987 let completions_menu_has_precedence = !show_in_menu
5988 && (self.context_menu.borrow().is_some()
5989 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
5990
5991 if completions_menu_has_precedence
5992 || !offset_selection.is_empty()
5993 || self
5994 .active_inline_completion
5995 .as_ref()
5996 .map_or(false, |completion| {
5997 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
5998 let invalidation_range = invalidation_range.start..=invalidation_range.end;
5999 !invalidation_range.contains(&offset_selection.head())
6000 })
6001 {
6002 self.discard_inline_completion(false, cx);
6003 return None;
6004 }
6005
6006 self.take_active_inline_completion(cx);
6007 let Some(provider) = self.edit_prediction_provider() else {
6008 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6009 return None;
6010 };
6011
6012 let (buffer, cursor_buffer_position) =
6013 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6014
6015 self.edit_prediction_settings =
6016 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6017
6018 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6019
6020 if self.edit_prediction_indent_conflict {
6021 let cursor_point = cursor.to_point(&multibuffer);
6022
6023 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6024
6025 if let Some((_, indent)) = indents.iter().next() {
6026 if indent.len == cursor_point.column {
6027 self.edit_prediction_indent_conflict = false;
6028 }
6029 }
6030 }
6031
6032 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6033 let edits = inline_completion
6034 .edits
6035 .into_iter()
6036 .flat_map(|(range, new_text)| {
6037 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6038 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6039 Some((start..end, new_text))
6040 })
6041 .collect::<Vec<_>>();
6042 if edits.is_empty() {
6043 return None;
6044 }
6045
6046 let first_edit_start = edits.first().unwrap().0.start;
6047 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6048 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6049
6050 let last_edit_end = edits.last().unwrap().0.end;
6051 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6052 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6053
6054 let cursor_row = cursor.to_point(&multibuffer).row;
6055
6056 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6057
6058 let mut inlay_ids = Vec::new();
6059 let invalidation_row_range;
6060 let move_invalidation_row_range = if cursor_row < edit_start_row {
6061 Some(cursor_row..edit_end_row)
6062 } else if cursor_row > edit_end_row {
6063 Some(edit_start_row..cursor_row)
6064 } else {
6065 None
6066 };
6067 let is_move =
6068 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6069 let completion = if is_move {
6070 invalidation_row_range =
6071 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6072 let target = first_edit_start;
6073 InlineCompletion::Move { target, snapshot }
6074 } else {
6075 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6076 && !self.inline_completions_hidden_for_vim_mode;
6077
6078 if show_completions_in_buffer {
6079 if edits
6080 .iter()
6081 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6082 {
6083 let mut inlays = Vec::new();
6084 for (range, new_text) in &edits {
6085 let inlay = Inlay::inline_completion(
6086 post_inc(&mut self.next_inlay_id),
6087 range.start,
6088 new_text.as_str(),
6089 );
6090 inlay_ids.push(inlay.id);
6091 inlays.push(inlay);
6092 }
6093
6094 self.splice_inlays(&[], inlays, cx);
6095 } else {
6096 let background_color = cx.theme().status().deleted_background;
6097 self.highlight_text::<InlineCompletionHighlight>(
6098 edits.iter().map(|(range, _)| range.clone()).collect(),
6099 HighlightStyle {
6100 background_color: Some(background_color),
6101 ..Default::default()
6102 },
6103 cx,
6104 );
6105 }
6106 }
6107
6108 invalidation_row_range = edit_start_row..edit_end_row;
6109
6110 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6111 if provider.show_tab_accept_marker() {
6112 EditDisplayMode::TabAccept
6113 } else {
6114 EditDisplayMode::Inline
6115 }
6116 } else {
6117 EditDisplayMode::DiffPopover
6118 };
6119
6120 InlineCompletion::Edit {
6121 edits,
6122 edit_preview: inline_completion.edit_preview,
6123 display_mode,
6124 snapshot,
6125 }
6126 };
6127
6128 let invalidation_range = multibuffer
6129 .anchor_before(Point::new(invalidation_row_range.start, 0))
6130 ..multibuffer.anchor_after(Point::new(
6131 invalidation_row_range.end,
6132 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6133 ));
6134
6135 self.stale_inline_completion_in_menu = None;
6136 self.active_inline_completion = Some(InlineCompletionState {
6137 inlay_ids,
6138 completion,
6139 completion_id: inline_completion.id,
6140 invalidation_range,
6141 });
6142
6143 cx.notify();
6144
6145 Some(())
6146 }
6147
6148 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6149 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6150 }
6151
6152 fn render_code_actions_indicator(
6153 &self,
6154 _style: &EditorStyle,
6155 row: DisplayRow,
6156 is_active: bool,
6157 breakpoint: Option<&(Anchor, Breakpoint)>,
6158 cx: &mut Context<Self>,
6159 ) -> Option<IconButton> {
6160 let color = Color::Muted;
6161 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6162
6163 if self.available_code_actions.is_some() {
6164 Some(
6165 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6166 .shape(ui::IconButtonShape::Square)
6167 .icon_size(IconSize::XSmall)
6168 .icon_color(color)
6169 .toggle_state(is_active)
6170 .tooltip({
6171 let focus_handle = self.focus_handle.clone();
6172 move |window, cx| {
6173 Tooltip::for_action_in(
6174 "Toggle Code Actions",
6175 &ToggleCodeActions {
6176 deployed_from_indicator: None,
6177 },
6178 &focus_handle,
6179 window,
6180 cx,
6181 )
6182 }
6183 })
6184 .on_click(cx.listener(move |editor, _e, window, cx| {
6185 window.focus(&editor.focus_handle(cx));
6186 editor.toggle_code_actions(
6187 &ToggleCodeActions {
6188 deployed_from_indicator: Some(row),
6189 },
6190 window,
6191 cx,
6192 );
6193 }))
6194 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6195 editor.set_breakpoint_context_menu(
6196 row,
6197 position,
6198 event.down.position,
6199 window,
6200 cx,
6201 );
6202 })),
6203 )
6204 } else {
6205 None
6206 }
6207 }
6208
6209 fn clear_tasks(&mut self) {
6210 self.tasks.clear()
6211 }
6212
6213 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6214 if self.tasks.insert(key, value).is_some() {
6215 // This case should hopefully be rare, but just in case...
6216 log::error!(
6217 "multiple different run targets found on a single line, only the last target will be rendered"
6218 )
6219 }
6220 }
6221
6222 /// Get all display points of breakpoints that will be rendered within editor
6223 ///
6224 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6225 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6226 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6227 fn active_breakpoints(
6228 &mut self,
6229 range: Range<DisplayRow>,
6230 window: &mut Window,
6231 cx: &mut Context<Self>,
6232 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6233 let mut breakpoint_display_points = HashMap::default();
6234
6235 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6236 return breakpoint_display_points;
6237 };
6238
6239 let snapshot = self.snapshot(window, cx);
6240
6241 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6242 let Some(project) = self.project.as_ref() else {
6243 return breakpoint_display_points;
6244 };
6245
6246 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6247 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6248
6249 for (buffer_snapshot, range, excerpt_id) in
6250 multi_buffer_snapshot.range_to_buffer_ranges(range)
6251 {
6252 let Some(buffer) = project.read_with(cx, |this, cx| {
6253 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6254 }) else {
6255 continue;
6256 };
6257 let breakpoints = breakpoint_store.read(cx).breakpoints(
6258 &buffer,
6259 Some(
6260 buffer_snapshot.anchor_before(range.start)
6261 ..buffer_snapshot.anchor_after(range.end),
6262 ),
6263 buffer_snapshot,
6264 cx,
6265 );
6266 for (anchor, breakpoint) in breakpoints {
6267 let multi_buffer_anchor =
6268 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6269 let position = multi_buffer_anchor
6270 .to_point(&multi_buffer_snapshot)
6271 .to_display_point(&snapshot);
6272
6273 breakpoint_display_points
6274 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6275 }
6276 }
6277
6278 breakpoint_display_points
6279 }
6280
6281 fn breakpoint_context_menu(
6282 &self,
6283 anchor: Anchor,
6284 window: &mut Window,
6285 cx: &mut Context<Self>,
6286 ) -> Entity<ui::ContextMenu> {
6287 let weak_editor = cx.weak_entity();
6288 let focus_handle = self.focus_handle(cx);
6289
6290 let row = self
6291 .buffer
6292 .read(cx)
6293 .snapshot(cx)
6294 .summary_for_anchor::<Point>(&anchor)
6295 .row;
6296
6297 let breakpoint = self
6298 .breakpoint_at_row(row, window, cx)
6299 .map(|(_, bp)| Arc::from(bp));
6300
6301 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.message.is_some()) {
6302 "Edit Log Breakpoint"
6303 } else {
6304 "Set Log Breakpoint"
6305 };
6306
6307 let condition_breakpoint_msg =
6308 if breakpoint.as_ref().is_some_and(|bp| bp.condition.is_some()) {
6309 "Edit Condition Breakpoint"
6310 } else {
6311 "Set Condition Breakpoint"
6312 };
6313
6314 let hit_condition_breakpoint_msg = if breakpoint
6315 .as_ref()
6316 .is_some_and(|bp| bp.hit_condition.is_some())
6317 {
6318 "Edit Hit Condition Breakpoint"
6319 } else {
6320 "Set Hit Condition Breakpoint"
6321 };
6322
6323 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6324 "Unset Breakpoint"
6325 } else {
6326 "Set Breakpoint"
6327 };
6328
6329 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.state {
6330 BreakpointState::Enabled => Some("Disable"),
6331 BreakpointState::Disabled => Some("Enable"),
6332 });
6333
6334 let breakpoint = breakpoint.unwrap_or_else(|| Arc::new(Breakpoint::new_standard()));
6335
6336 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6337 menu.on_blur_subscription(Subscription::new(|| {}))
6338 .context(focus_handle)
6339 .when_some(toggle_state_msg, |this, msg| {
6340 this.entry(msg, None, {
6341 let weak_editor = weak_editor.clone();
6342 let breakpoint = breakpoint.clone();
6343 move |_window, cx| {
6344 weak_editor
6345 .update(cx, |this, cx| {
6346 this.edit_breakpoint_at_anchor(
6347 anchor,
6348 breakpoint.as_ref().clone(),
6349 BreakpointEditAction::InvertState,
6350 cx,
6351 );
6352 })
6353 .log_err();
6354 }
6355 })
6356 })
6357 .entry(set_breakpoint_msg, None, {
6358 let weak_editor = weak_editor.clone();
6359 let breakpoint = breakpoint.clone();
6360 move |_window, cx| {
6361 weak_editor
6362 .update(cx, |this, cx| {
6363 this.edit_breakpoint_at_anchor(
6364 anchor,
6365 breakpoint.as_ref().clone(),
6366 BreakpointEditAction::Toggle,
6367 cx,
6368 );
6369 })
6370 .log_err();
6371 }
6372 })
6373 .entry(log_breakpoint_msg, None, {
6374 let breakpoint = breakpoint.clone();
6375 let weak_editor = weak_editor.clone();
6376 move |window, cx| {
6377 weak_editor
6378 .update(cx, |this, cx| {
6379 this.add_edit_breakpoint_block(
6380 anchor,
6381 breakpoint.as_ref(),
6382 BreakpointPromptEditAction::Log,
6383 window,
6384 cx,
6385 );
6386 })
6387 .log_err();
6388 }
6389 })
6390 .entry(condition_breakpoint_msg, None, {
6391 let breakpoint = breakpoint.clone();
6392 let weak_editor = weak_editor.clone();
6393 move |window, cx| {
6394 weak_editor
6395 .update(cx, |this, cx| {
6396 this.add_edit_breakpoint_block(
6397 anchor,
6398 breakpoint.as_ref(),
6399 BreakpointPromptEditAction::Condition,
6400 window,
6401 cx,
6402 );
6403 })
6404 .log_err();
6405 }
6406 })
6407 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6408 weak_editor
6409 .update(cx, |this, cx| {
6410 this.add_edit_breakpoint_block(
6411 anchor,
6412 breakpoint.as_ref(),
6413 BreakpointPromptEditAction::HitCondition,
6414 window,
6415 cx,
6416 );
6417 })
6418 .log_err();
6419 })
6420 })
6421 }
6422
6423 fn render_breakpoint(
6424 &self,
6425 position: Anchor,
6426 row: DisplayRow,
6427 breakpoint: &Breakpoint,
6428 cx: &mut Context<Self>,
6429 ) -> IconButton {
6430 let (color, icon) = {
6431 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6432 (false, false) => ui::IconName::DebugBreakpoint,
6433 (true, false) => ui::IconName::DebugLogBreakpoint,
6434 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6435 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6436 };
6437
6438 let color = if self
6439 .gutter_breakpoint_indicator
6440 .0
6441 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6442 {
6443 Color::Hint
6444 } else {
6445 Color::Debugger
6446 };
6447
6448 (color, icon)
6449 };
6450
6451 let breakpoint = Arc::from(breakpoint.clone());
6452
6453 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6454 .icon_size(IconSize::XSmall)
6455 .size(ui::ButtonSize::None)
6456 .icon_color(color)
6457 .style(ButtonStyle::Transparent)
6458 .on_click(cx.listener({
6459 let breakpoint = breakpoint.clone();
6460
6461 move |editor, event: &ClickEvent, window, cx| {
6462 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6463 BreakpointEditAction::InvertState
6464 } else {
6465 BreakpointEditAction::Toggle
6466 };
6467
6468 window.focus(&editor.focus_handle(cx));
6469 editor.edit_breakpoint_at_anchor(
6470 position,
6471 breakpoint.as_ref().clone(),
6472 edit_action,
6473 cx,
6474 );
6475 }
6476 }))
6477 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6478 editor.set_breakpoint_context_menu(
6479 row,
6480 Some(position),
6481 event.down.position,
6482 window,
6483 cx,
6484 );
6485 }))
6486 }
6487
6488 fn build_tasks_context(
6489 project: &Entity<Project>,
6490 buffer: &Entity<Buffer>,
6491 buffer_row: u32,
6492 tasks: &Arc<RunnableTasks>,
6493 cx: &mut Context<Self>,
6494 ) -> Task<Option<task::TaskContext>> {
6495 let position = Point::new(buffer_row, tasks.column);
6496 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6497 let location = Location {
6498 buffer: buffer.clone(),
6499 range: range_start..range_start,
6500 };
6501 // Fill in the environmental variables from the tree-sitter captures
6502 let mut captured_task_variables = TaskVariables::default();
6503 for (capture_name, value) in tasks.extra_variables.clone() {
6504 captured_task_variables.insert(
6505 task::VariableName::Custom(capture_name.into()),
6506 value.clone(),
6507 );
6508 }
6509 project.update(cx, |project, cx| {
6510 project.task_store().update(cx, |task_store, cx| {
6511 task_store.task_context_for_location(captured_task_variables, location, cx)
6512 })
6513 })
6514 }
6515
6516 pub fn spawn_nearest_task(
6517 &mut self,
6518 action: &SpawnNearestTask,
6519 window: &mut Window,
6520 cx: &mut Context<Self>,
6521 ) {
6522 let Some((workspace, _)) = self.workspace.clone() else {
6523 return;
6524 };
6525 let Some(project) = self.project.clone() else {
6526 return;
6527 };
6528
6529 // Try to find a closest, enclosing node using tree-sitter that has a
6530 // task
6531 let Some((buffer, buffer_row, tasks)) = self
6532 .find_enclosing_node_task(cx)
6533 // Or find the task that's closest in row-distance.
6534 .or_else(|| self.find_closest_task(cx))
6535 else {
6536 return;
6537 };
6538
6539 let reveal_strategy = action.reveal;
6540 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6541 cx.spawn_in(window, async move |_, cx| {
6542 let context = task_context.await?;
6543 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6544
6545 let resolved = resolved_task.resolved.as_mut()?;
6546 resolved.reveal = reveal_strategy;
6547
6548 workspace
6549 .update(cx, |workspace, cx| {
6550 workspace::tasks::schedule_resolved_task(
6551 workspace,
6552 task_source_kind,
6553 resolved_task,
6554 false,
6555 cx,
6556 );
6557 })
6558 .ok()
6559 })
6560 .detach();
6561 }
6562
6563 fn find_closest_task(
6564 &mut self,
6565 cx: &mut Context<Self>,
6566 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6567 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6568
6569 let ((buffer_id, row), tasks) = self
6570 .tasks
6571 .iter()
6572 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6573
6574 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6575 let tasks = Arc::new(tasks.to_owned());
6576 Some((buffer, *row, tasks))
6577 }
6578
6579 fn find_enclosing_node_task(
6580 &mut self,
6581 cx: &mut Context<Self>,
6582 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6583 let snapshot = self.buffer.read(cx).snapshot(cx);
6584 let offset = self.selections.newest::<usize>(cx).head();
6585 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6586 let buffer_id = excerpt.buffer().remote_id();
6587
6588 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6589 let mut cursor = layer.node().walk();
6590
6591 while cursor.goto_first_child_for_byte(offset).is_some() {
6592 if cursor.node().end_byte() == offset {
6593 cursor.goto_next_sibling();
6594 }
6595 }
6596
6597 // Ascend to the smallest ancestor that contains the range and has a task.
6598 loop {
6599 let node = cursor.node();
6600 let node_range = node.byte_range();
6601 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6602
6603 // Check if this node contains our offset
6604 if node_range.start <= offset && node_range.end >= offset {
6605 // If it contains offset, check for task
6606 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6607 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6608 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6609 }
6610 }
6611
6612 if !cursor.goto_parent() {
6613 break;
6614 }
6615 }
6616 None
6617 }
6618
6619 fn render_run_indicator(
6620 &self,
6621 _style: &EditorStyle,
6622 is_active: bool,
6623 row: DisplayRow,
6624 breakpoint: Option<(Anchor, Breakpoint)>,
6625 cx: &mut Context<Self>,
6626 ) -> IconButton {
6627 let color = Color::Muted;
6628 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6629
6630 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6631 .shape(ui::IconButtonShape::Square)
6632 .icon_size(IconSize::XSmall)
6633 .icon_color(color)
6634 .toggle_state(is_active)
6635 .on_click(cx.listener(move |editor, _e, window, cx| {
6636 window.focus(&editor.focus_handle(cx));
6637 editor.toggle_code_actions(
6638 &ToggleCodeActions {
6639 deployed_from_indicator: Some(row),
6640 },
6641 window,
6642 cx,
6643 );
6644 }))
6645 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6646 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6647 }))
6648 }
6649
6650 pub fn context_menu_visible(&self) -> bool {
6651 !self.edit_prediction_preview_is_active()
6652 && self
6653 .context_menu
6654 .borrow()
6655 .as_ref()
6656 .map_or(false, |menu| menu.visible())
6657 }
6658
6659 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6660 self.context_menu
6661 .borrow()
6662 .as_ref()
6663 .map(|menu| menu.origin())
6664 }
6665
6666 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6667 self.context_menu_options = Some(options);
6668 }
6669
6670 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6671 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6672
6673 fn render_edit_prediction_popover(
6674 &mut self,
6675 text_bounds: &Bounds<Pixels>,
6676 content_origin: gpui::Point<Pixels>,
6677 editor_snapshot: &EditorSnapshot,
6678 visible_row_range: Range<DisplayRow>,
6679 scroll_top: f32,
6680 scroll_bottom: f32,
6681 line_layouts: &[LineWithInvisibles],
6682 line_height: Pixels,
6683 scroll_pixel_position: gpui::Point<Pixels>,
6684 newest_selection_head: Option<DisplayPoint>,
6685 editor_width: Pixels,
6686 style: &EditorStyle,
6687 window: &mut Window,
6688 cx: &mut App,
6689 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6690 let active_inline_completion = self.active_inline_completion.as_ref()?;
6691
6692 if self.edit_prediction_visible_in_cursor_popover(true) {
6693 return None;
6694 }
6695
6696 match &active_inline_completion.completion {
6697 InlineCompletion::Move { target, .. } => {
6698 let target_display_point = target.to_display_point(editor_snapshot);
6699
6700 if self.edit_prediction_requires_modifier() {
6701 if !self.edit_prediction_preview_is_active() {
6702 return None;
6703 }
6704
6705 self.render_edit_prediction_modifier_jump_popover(
6706 text_bounds,
6707 content_origin,
6708 visible_row_range,
6709 line_layouts,
6710 line_height,
6711 scroll_pixel_position,
6712 newest_selection_head,
6713 target_display_point,
6714 window,
6715 cx,
6716 )
6717 } else {
6718 self.render_edit_prediction_eager_jump_popover(
6719 text_bounds,
6720 content_origin,
6721 editor_snapshot,
6722 visible_row_range,
6723 scroll_top,
6724 scroll_bottom,
6725 line_height,
6726 scroll_pixel_position,
6727 target_display_point,
6728 editor_width,
6729 window,
6730 cx,
6731 )
6732 }
6733 }
6734 InlineCompletion::Edit {
6735 display_mode: EditDisplayMode::Inline,
6736 ..
6737 } => None,
6738 InlineCompletion::Edit {
6739 display_mode: EditDisplayMode::TabAccept,
6740 edits,
6741 ..
6742 } => {
6743 let range = &edits.first()?.0;
6744 let target_display_point = range.end.to_display_point(editor_snapshot);
6745
6746 self.render_edit_prediction_end_of_line_popover(
6747 "Accept",
6748 editor_snapshot,
6749 visible_row_range,
6750 target_display_point,
6751 line_height,
6752 scroll_pixel_position,
6753 content_origin,
6754 editor_width,
6755 window,
6756 cx,
6757 )
6758 }
6759 InlineCompletion::Edit {
6760 edits,
6761 edit_preview,
6762 display_mode: EditDisplayMode::DiffPopover,
6763 snapshot,
6764 } => self.render_edit_prediction_diff_popover(
6765 text_bounds,
6766 content_origin,
6767 editor_snapshot,
6768 visible_row_range,
6769 line_layouts,
6770 line_height,
6771 scroll_pixel_position,
6772 newest_selection_head,
6773 editor_width,
6774 style,
6775 edits,
6776 edit_preview,
6777 snapshot,
6778 window,
6779 cx,
6780 ),
6781 }
6782 }
6783
6784 fn render_edit_prediction_modifier_jump_popover(
6785 &mut self,
6786 text_bounds: &Bounds<Pixels>,
6787 content_origin: gpui::Point<Pixels>,
6788 visible_row_range: Range<DisplayRow>,
6789 line_layouts: &[LineWithInvisibles],
6790 line_height: Pixels,
6791 scroll_pixel_position: gpui::Point<Pixels>,
6792 newest_selection_head: Option<DisplayPoint>,
6793 target_display_point: DisplayPoint,
6794 window: &mut Window,
6795 cx: &mut App,
6796 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6797 let scrolled_content_origin =
6798 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
6799
6800 const SCROLL_PADDING_Y: Pixels = px(12.);
6801
6802 if target_display_point.row() < visible_row_range.start {
6803 return self.render_edit_prediction_scroll_popover(
6804 |_| SCROLL_PADDING_Y,
6805 IconName::ArrowUp,
6806 visible_row_range,
6807 line_layouts,
6808 newest_selection_head,
6809 scrolled_content_origin,
6810 window,
6811 cx,
6812 );
6813 } else if target_display_point.row() >= visible_row_range.end {
6814 return self.render_edit_prediction_scroll_popover(
6815 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
6816 IconName::ArrowDown,
6817 visible_row_range,
6818 line_layouts,
6819 newest_selection_head,
6820 scrolled_content_origin,
6821 window,
6822 cx,
6823 );
6824 }
6825
6826 const POLE_WIDTH: Pixels = px(2.);
6827
6828 let line_layout =
6829 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
6830 let target_column = target_display_point.column() as usize;
6831
6832 let target_x = line_layout.x_for_index(target_column);
6833 let target_y =
6834 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
6835
6836 let flag_on_right = target_x < text_bounds.size.width / 2.;
6837
6838 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
6839 border_color.l += 0.001;
6840
6841 let mut element = v_flex()
6842 .items_end()
6843 .when(flag_on_right, |el| el.items_start())
6844 .child(if flag_on_right {
6845 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6846 .rounded_bl(px(0.))
6847 .rounded_tl(px(0.))
6848 .border_l_2()
6849 .border_color(border_color)
6850 } else {
6851 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
6852 .rounded_br(px(0.))
6853 .rounded_tr(px(0.))
6854 .border_r_2()
6855 .border_color(border_color)
6856 })
6857 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
6858 .into_any();
6859
6860 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6861
6862 let mut origin = scrolled_content_origin + point(target_x, target_y)
6863 - point(
6864 if flag_on_right {
6865 POLE_WIDTH
6866 } else {
6867 size.width - POLE_WIDTH
6868 },
6869 size.height - line_height,
6870 );
6871
6872 origin.x = origin.x.max(content_origin.x);
6873
6874 element.prepaint_at(origin, window, cx);
6875
6876 Some((element, origin))
6877 }
6878
6879 fn render_edit_prediction_scroll_popover(
6880 &mut self,
6881 to_y: impl Fn(Size<Pixels>) -> Pixels,
6882 scroll_icon: IconName,
6883 visible_row_range: Range<DisplayRow>,
6884 line_layouts: &[LineWithInvisibles],
6885 newest_selection_head: Option<DisplayPoint>,
6886 scrolled_content_origin: gpui::Point<Pixels>,
6887 window: &mut Window,
6888 cx: &mut App,
6889 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6890 let mut element = self
6891 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
6892 .into_any();
6893
6894 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6895
6896 let cursor = newest_selection_head?;
6897 let cursor_row_layout =
6898 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
6899 let cursor_column = cursor.column() as usize;
6900
6901 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
6902
6903 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
6904
6905 element.prepaint_at(origin, window, cx);
6906 Some((element, origin))
6907 }
6908
6909 fn render_edit_prediction_eager_jump_popover(
6910 &mut self,
6911 text_bounds: &Bounds<Pixels>,
6912 content_origin: gpui::Point<Pixels>,
6913 editor_snapshot: &EditorSnapshot,
6914 visible_row_range: Range<DisplayRow>,
6915 scroll_top: f32,
6916 scroll_bottom: f32,
6917 line_height: Pixels,
6918 scroll_pixel_position: gpui::Point<Pixels>,
6919 target_display_point: DisplayPoint,
6920 editor_width: Pixels,
6921 window: &mut Window,
6922 cx: &mut App,
6923 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6924 if target_display_point.row().as_f32() < scroll_top {
6925 let mut element = self
6926 .render_edit_prediction_line_popover(
6927 "Jump to Edit",
6928 Some(IconName::ArrowUp),
6929 window,
6930 cx,
6931 )?
6932 .into_any();
6933
6934 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6935 let offset = point(
6936 (text_bounds.size.width - size.width) / 2.,
6937 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6938 );
6939
6940 let origin = text_bounds.origin + offset;
6941 element.prepaint_at(origin, window, cx);
6942 Some((element, origin))
6943 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
6944 let mut element = self
6945 .render_edit_prediction_line_popover(
6946 "Jump to Edit",
6947 Some(IconName::ArrowDown),
6948 window,
6949 cx,
6950 )?
6951 .into_any();
6952
6953 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
6954 let offset = point(
6955 (text_bounds.size.width - size.width) / 2.,
6956 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
6957 );
6958
6959 let origin = text_bounds.origin + offset;
6960 element.prepaint_at(origin, window, cx);
6961 Some((element, origin))
6962 } else {
6963 self.render_edit_prediction_end_of_line_popover(
6964 "Jump to Edit",
6965 editor_snapshot,
6966 visible_row_range,
6967 target_display_point,
6968 line_height,
6969 scroll_pixel_position,
6970 content_origin,
6971 editor_width,
6972 window,
6973 cx,
6974 )
6975 }
6976 }
6977
6978 fn render_edit_prediction_end_of_line_popover(
6979 self: &mut Editor,
6980 label: &'static str,
6981 editor_snapshot: &EditorSnapshot,
6982 visible_row_range: Range<DisplayRow>,
6983 target_display_point: DisplayPoint,
6984 line_height: Pixels,
6985 scroll_pixel_position: gpui::Point<Pixels>,
6986 content_origin: gpui::Point<Pixels>,
6987 editor_width: Pixels,
6988 window: &mut Window,
6989 cx: &mut App,
6990 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6991 let target_line_end = DisplayPoint::new(
6992 target_display_point.row(),
6993 editor_snapshot.line_len(target_display_point.row()),
6994 );
6995
6996 let mut element = self
6997 .render_edit_prediction_line_popover(label, None, window, cx)?
6998 .into_any();
6999
7000 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7001
7002 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7003
7004 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7005 let mut origin = start_point
7006 + line_origin
7007 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7008 origin.x = origin.x.max(content_origin.x);
7009
7010 let max_x = content_origin.x + editor_width - size.width;
7011
7012 if origin.x > max_x {
7013 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7014
7015 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7016 origin.y += offset;
7017 IconName::ArrowUp
7018 } else {
7019 origin.y -= offset;
7020 IconName::ArrowDown
7021 };
7022
7023 element = self
7024 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7025 .into_any();
7026
7027 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7028
7029 origin.x = content_origin.x + editor_width - size.width - px(2.);
7030 }
7031
7032 element.prepaint_at(origin, window, cx);
7033 Some((element, origin))
7034 }
7035
7036 fn render_edit_prediction_diff_popover(
7037 self: &Editor,
7038 text_bounds: &Bounds<Pixels>,
7039 content_origin: gpui::Point<Pixels>,
7040 editor_snapshot: &EditorSnapshot,
7041 visible_row_range: Range<DisplayRow>,
7042 line_layouts: &[LineWithInvisibles],
7043 line_height: Pixels,
7044 scroll_pixel_position: gpui::Point<Pixels>,
7045 newest_selection_head: Option<DisplayPoint>,
7046 editor_width: Pixels,
7047 style: &EditorStyle,
7048 edits: &Vec<(Range<Anchor>, String)>,
7049 edit_preview: &Option<language::EditPreview>,
7050 snapshot: &language::BufferSnapshot,
7051 window: &mut Window,
7052 cx: &mut App,
7053 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7054 let edit_start = edits
7055 .first()
7056 .unwrap()
7057 .0
7058 .start
7059 .to_display_point(editor_snapshot);
7060 let edit_end = edits
7061 .last()
7062 .unwrap()
7063 .0
7064 .end
7065 .to_display_point(editor_snapshot);
7066
7067 let is_visible = visible_row_range.contains(&edit_start.row())
7068 || visible_row_range.contains(&edit_end.row());
7069 if !is_visible {
7070 return None;
7071 }
7072
7073 let highlighted_edits =
7074 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7075
7076 let styled_text = highlighted_edits.to_styled_text(&style.text);
7077 let line_count = highlighted_edits.text.lines().count();
7078
7079 const BORDER_WIDTH: Pixels = px(1.);
7080
7081 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7082 let has_keybind = keybind.is_some();
7083
7084 let mut element = h_flex()
7085 .items_start()
7086 .child(
7087 h_flex()
7088 .bg(cx.theme().colors().editor_background)
7089 .border(BORDER_WIDTH)
7090 .shadow_sm()
7091 .border_color(cx.theme().colors().border)
7092 .rounded_l_lg()
7093 .when(line_count > 1, |el| el.rounded_br_lg())
7094 .pr_1()
7095 .child(styled_text),
7096 )
7097 .child(
7098 h_flex()
7099 .h(line_height + BORDER_WIDTH * 2.)
7100 .px_1p5()
7101 .gap_1()
7102 // Workaround: For some reason, there's a gap if we don't do this
7103 .ml(-BORDER_WIDTH)
7104 .shadow(smallvec![gpui::BoxShadow {
7105 color: gpui::black().opacity(0.05),
7106 offset: point(px(1.), px(1.)),
7107 blur_radius: px(2.),
7108 spread_radius: px(0.),
7109 }])
7110 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7111 .border(BORDER_WIDTH)
7112 .border_color(cx.theme().colors().border)
7113 .rounded_r_lg()
7114 .id("edit_prediction_diff_popover_keybind")
7115 .when(!has_keybind, |el| {
7116 let status_colors = cx.theme().status();
7117
7118 el.bg(status_colors.error_background)
7119 .border_color(status_colors.error.opacity(0.6))
7120 .child(Icon::new(IconName::Info).color(Color::Error))
7121 .cursor_default()
7122 .hoverable_tooltip(move |_window, cx| {
7123 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7124 })
7125 })
7126 .children(keybind),
7127 )
7128 .into_any();
7129
7130 let longest_row =
7131 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7132 let longest_line_width = if visible_row_range.contains(&longest_row) {
7133 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7134 } else {
7135 layout_line(
7136 longest_row,
7137 editor_snapshot,
7138 style,
7139 editor_width,
7140 |_| false,
7141 window,
7142 cx,
7143 )
7144 .width
7145 };
7146
7147 let viewport_bounds =
7148 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7149 right: -EditorElement::SCROLLBAR_WIDTH,
7150 ..Default::default()
7151 });
7152
7153 let x_after_longest =
7154 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7155 - scroll_pixel_position.x;
7156
7157 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7158
7159 // Fully visible if it can be displayed within the window (allow overlapping other
7160 // panes). However, this is only allowed if the popover starts within text_bounds.
7161 let can_position_to_the_right = x_after_longest < text_bounds.right()
7162 && x_after_longest + element_bounds.width < viewport_bounds.right();
7163
7164 let mut origin = if can_position_to_the_right {
7165 point(
7166 x_after_longest,
7167 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7168 - scroll_pixel_position.y,
7169 )
7170 } else {
7171 let cursor_row = newest_selection_head.map(|head| head.row());
7172 let above_edit = edit_start
7173 .row()
7174 .0
7175 .checked_sub(line_count as u32)
7176 .map(DisplayRow);
7177 let below_edit = Some(edit_end.row() + 1);
7178 let above_cursor =
7179 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7180 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7181
7182 // Place the edit popover adjacent to the edit if there is a location
7183 // available that is onscreen and does not obscure the cursor. Otherwise,
7184 // place it adjacent to the cursor.
7185 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7186 .into_iter()
7187 .flatten()
7188 .find(|&start_row| {
7189 let end_row = start_row + line_count as u32;
7190 visible_row_range.contains(&start_row)
7191 && visible_row_range.contains(&end_row)
7192 && cursor_row.map_or(true, |cursor_row| {
7193 !((start_row..end_row).contains(&cursor_row))
7194 })
7195 })?;
7196
7197 content_origin
7198 + point(
7199 -scroll_pixel_position.x,
7200 row_target.as_f32() * line_height - scroll_pixel_position.y,
7201 )
7202 };
7203
7204 origin.x -= BORDER_WIDTH;
7205
7206 window.defer_draw(element, origin, 1);
7207
7208 // Do not return an element, since it will already be drawn due to defer_draw.
7209 None
7210 }
7211
7212 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7213 px(30.)
7214 }
7215
7216 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7217 if self.read_only(cx) {
7218 cx.theme().players().read_only()
7219 } else {
7220 self.style.as_ref().unwrap().local_player
7221 }
7222 }
7223
7224 fn render_edit_prediction_accept_keybind(
7225 &self,
7226 window: &mut Window,
7227 cx: &App,
7228 ) -> Option<AnyElement> {
7229 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7230 let accept_keystroke = accept_binding.keystroke()?;
7231
7232 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7233
7234 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7235 Color::Accent
7236 } else {
7237 Color::Muted
7238 };
7239
7240 h_flex()
7241 .px_0p5()
7242 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7243 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7244 .text_size(TextSize::XSmall.rems(cx))
7245 .child(h_flex().children(ui::render_modifiers(
7246 &accept_keystroke.modifiers,
7247 PlatformStyle::platform(),
7248 Some(modifiers_color),
7249 Some(IconSize::XSmall.rems().into()),
7250 true,
7251 )))
7252 .when(is_platform_style_mac, |parent| {
7253 parent.child(accept_keystroke.key.clone())
7254 })
7255 .when(!is_platform_style_mac, |parent| {
7256 parent.child(
7257 Key::new(
7258 util::capitalize(&accept_keystroke.key),
7259 Some(Color::Default),
7260 )
7261 .size(Some(IconSize::XSmall.rems().into())),
7262 )
7263 })
7264 .into_any()
7265 .into()
7266 }
7267
7268 fn render_edit_prediction_line_popover(
7269 &self,
7270 label: impl Into<SharedString>,
7271 icon: Option<IconName>,
7272 window: &mut Window,
7273 cx: &App,
7274 ) -> Option<Stateful<Div>> {
7275 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7276
7277 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7278 let has_keybind = keybind.is_some();
7279
7280 let result = h_flex()
7281 .id("ep-line-popover")
7282 .py_0p5()
7283 .pl_1()
7284 .pr(padding_right)
7285 .gap_1()
7286 .rounded_md()
7287 .border_1()
7288 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7289 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7290 .shadow_sm()
7291 .when(!has_keybind, |el| {
7292 let status_colors = cx.theme().status();
7293
7294 el.bg(status_colors.error_background)
7295 .border_color(status_colors.error.opacity(0.6))
7296 .pl_2()
7297 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7298 .cursor_default()
7299 .hoverable_tooltip(move |_window, cx| {
7300 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7301 })
7302 })
7303 .children(keybind)
7304 .child(
7305 Label::new(label)
7306 .size(LabelSize::Small)
7307 .when(!has_keybind, |el| {
7308 el.color(cx.theme().status().error.into()).strikethrough()
7309 }),
7310 )
7311 .when(!has_keybind, |el| {
7312 el.child(
7313 h_flex().ml_1().child(
7314 Icon::new(IconName::Info)
7315 .size(IconSize::Small)
7316 .color(cx.theme().status().error.into()),
7317 ),
7318 )
7319 })
7320 .when_some(icon, |element, icon| {
7321 element.child(
7322 div()
7323 .mt(px(1.5))
7324 .child(Icon::new(icon).size(IconSize::Small)),
7325 )
7326 });
7327
7328 Some(result)
7329 }
7330
7331 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7332 let accent_color = cx.theme().colors().text_accent;
7333 let editor_bg_color = cx.theme().colors().editor_background;
7334 editor_bg_color.blend(accent_color.opacity(0.1))
7335 }
7336
7337 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7338 let accent_color = cx.theme().colors().text_accent;
7339 let editor_bg_color = cx.theme().colors().editor_background;
7340 editor_bg_color.blend(accent_color.opacity(0.6))
7341 }
7342
7343 fn render_edit_prediction_cursor_popover(
7344 &self,
7345 min_width: Pixels,
7346 max_width: Pixels,
7347 cursor_point: Point,
7348 style: &EditorStyle,
7349 accept_keystroke: Option<&gpui::Keystroke>,
7350 _window: &Window,
7351 cx: &mut Context<Editor>,
7352 ) -> Option<AnyElement> {
7353 let provider = self.edit_prediction_provider.as_ref()?;
7354
7355 if provider.provider.needs_terms_acceptance(cx) {
7356 return Some(
7357 h_flex()
7358 .min_w(min_width)
7359 .flex_1()
7360 .px_2()
7361 .py_1()
7362 .gap_3()
7363 .elevation_2(cx)
7364 .hover(|style| style.bg(cx.theme().colors().element_hover))
7365 .id("accept-terms")
7366 .cursor_pointer()
7367 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7368 .on_click(cx.listener(|this, _event, window, cx| {
7369 cx.stop_propagation();
7370 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7371 window.dispatch_action(
7372 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7373 cx,
7374 );
7375 }))
7376 .child(
7377 h_flex()
7378 .flex_1()
7379 .gap_2()
7380 .child(Icon::new(IconName::ZedPredict))
7381 .child(Label::new("Accept Terms of Service"))
7382 .child(div().w_full())
7383 .child(
7384 Icon::new(IconName::ArrowUpRight)
7385 .color(Color::Muted)
7386 .size(IconSize::Small),
7387 )
7388 .into_any_element(),
7389 )
7390 .into_any(),
7391 );
7392 }
7393
7394 let is_refreshing = provider.provider.is_refreshing(cx);
7395
7396 fn pending_completion_container() -> Div {
7397 h_flex()
7398 .h_full()
7399 .flex_1()
7400 .gap_2()
7401 .child(Icon::new(IconName::ZedPredict))
7402 }
7403
7404 let completion = match &self.active_inline_completion {
7405 Some(prediction) => {
7406 if !self.has_visible_completions_menu() {
7407 const RADIUS: Pixels = px(6.);
7408 const BORDER_WIDTH: Pixels = px(1.);
7409
7410 return Some(
7411 h_flex()
7412 .elevation_2(cx)
7413 .border(BORDER_WIDTH)
7414 .border_color(cx.theme().colors().border)
7415 .when(accept_keystroke.is_none(), |el| {
7416 el.border_color(cx.theme().status().error)
7417 })
7418 .rounded(RADIUS)
7419 .rounded_tl(px(0.))
7420 .overflow_hidden()
7421 .child(div().px_1p5().child(match &prediction.completion {
7422 InlineCompletion::Move { target, snapshot } => {
7423 use text::ToPoint as _;
7424 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7425 {
7426 Icon::new(IconName::ZedPredictDown)
7427 } else {
7428 Icon::new(IconName::ZedPredictUp)
7429 }
7430 }
7431 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7432 }))
7433 .child(
7434 h_flex()
7435 .gap_1()
7436 .py_1()
7437 .px_2()
7438 .rounded_r(RADIUS - BORDER_WIDTH)
7439 .border_l_1()
7440 .border_color(cx.theme().colors().border)
7441 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7442 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7443 el.child(
7444 Label::new("Hold")
7445 .size(LabelSize::Small)
7446 .when(accept_keystroke.is_none(), |el| {
7447 el.strikethrough()
7448 })
7449 .line_height_style(LineHeightStyle::UiLabel),
7450 )
7451 })
7452 .id("edit_prediction_cursor_popover_keybind")
7453 .when(accept_keystroke.is_none(), |el| {
7454 let status_colors = cx.theme().status();
7455
7456 el.bg(status_colors.error_background)
7457 .border_color(status_colors.error.opacity(0.6))
7458 .child(Icon::new(IconName::Info).color(Color::Error))
7459 .cursor_default()
7460 .hoverable_tooltip(move |_window, cx| {
7461 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7462 .into()
7463 })
7464 })
7465 .when_some(
7466 accept_keystroke.as_ref(),
7467 |el, accept_keystroke| {
7468 el.child(h_flex().children(ui::render_modifiers(
7469 &accept_keystroke.modifiers,
7470 PlatformStyle::platform(),
7471 Some(Color::Default),
7472 Some(IconSize::XSmall.rems().into()),
7473 false,
7474 )))
7475 },
7476 ),
7477 )
7478 .into_any(),
7479 );
7480 }
7481
7482 self.render_edit_prediction_cursor_popover_preview(
7483 prediction,
7484 cursor_point,
7485 style,
7486 cx,
7487 )?
7488 }
7489
7490 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7491 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7492 stale_completion,
7493 cursor_point,
7494 style,
7495 cx,
7496 )?,
7497
7498 None => {
7499 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7500 }
7501 },
7502
7503 None => pending_completion_container().child(Label::new("No Prediction")),
7504 };
7505
7506 let completion = if is_refreshing {
7507 completion
7508 .with_animation(
7509 "loading-completion",
7510 Animation::new(Duration::from_secs(2))
7511 .repeat()
7512 .with_easing(pulsating_between(0.4, 0.8)),
7513 |label, delta| label.opacity(delta),
7514 )
7515 .into_any_element()
7516 } else {
7517 completion.into_any_element()
7518 };
7519
7520 let has_completion = self.active_inline_completion.is_some();
7521
7522 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7523 Some(
7524 h_flex()
7525 .min_w(min_width)
7526 .max_w(max_width)
7527 .flex_1()
7528 .elevation_2(cx)
7529 .border_color(cx.theme().colors().border)
7530 .child(
7531 div()
7532 .flex_1()
7533 .py_1()
7534 .px_2()
7535 .overflow_hidden()
7536 .child(completion),
7537 )
7538 .when_some(accept_keystroke, |el, accept_keystroke| {
7539 if !accept_keystroke.modifiers.modified() {
7540 return el;
7541 }
7542
7543 el.child(
7544 h_flex()
7545 .h_full()
7546 .border_l_1()
7547 .rounded_r_lg()
7548 .border_color(cx.theme().colors().border)
7549 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7550 .gap_1()
7551 .py_1()
7552 .px_2()
7553 .child(
7554 h_flex()
7555 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7556 .when(is_platform_style_mac, |parent| parent.gap_1())
7557 .child(h_flex().children(ui::render_modifiers(
7558 &accept_keystroke.modifiers,
7559 PlatformStyle::platform(),
7560 Some(if !has_completion {
7561 Color::Muted
7562 } else {
7563 Color::Default
7564 }),
7565 None,
7566 false,
7567 ))),
7568 )
7569 .child(Label::new("Preview").into_any_element())
7570 .opacity(if has_completion { 1.0 } else { 0.4 }),
7571 )
7572 })
7573 .into_any(),
7574 )
7575 }
7576
7577 fn render_edit_prediction_cursor_popover_preview(
7578 &self,
7579 completion: &InlineCompletionState,
7580 cursor_point: Point,
7581 style: &EditorStyle,
7582 cx: &mut Context<Editor>,
7583 ) -> Option<Div> {
7584 use text::ToPoint as _;
7585
7586 fn render_relative_row_jump(
7587 prefix: impl Into<String>,
7588 current_row: u32,
7589 target_row: u32,
7590 ) -> Div {
7591 let (row_diff, arrow) = if target_row < current_row {
7592 (current_row - target_row, IconName::ArrowUp)
7593 } else {
7594 (target_row - current_row, IconName::ArrowDown)
7595 };
7596
7597 h_flex()
7598 .child(
7599 Label::new(format!("{}{}", prefix.into(), row_diff))
7600 .color(Color::Muted)
7601 .size(LabelSize::Small),
7602 )
7603 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7604 }
7605
7606 match &completion.completion {
7607 InlineCompletion::Move {
7608 target, snapshot, ..
7609 } => Some(
7610 h_flex()
7611 .px_2()
7612 .gap_2()
7613 .flex_1()
7614 .child(
7615 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7616 Icon::new(IconName::ZedPredictDown)
7617 } else {
7618 Icon::new(IconName::ZedPredictUp)
7619 },
7620 )
7621 .child(Label::new("Jump to Edit")),
7622 ),
7623
7624 InlineCompletion::Edit {
7625 edits,
7626 edit_preview,
7627 snapshot,
7628 display_mode: _,
7629 } => {
7630 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7631
7632 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7633 &snapshot,
7634 &edits,
7635 edit_preview.as_ref()?,
7636 true,
7637 cx,
7638 )
7639 .first_line_preview();
7640
7641 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7642 .with_default_highlights(&style.text, highlighted_edits.highlights);
7643
7644 let preview = h_flex()
7645 .gap_1()
7646 .min_w_16()
7647 .child(styled_text)
7648 .when(has_more_lines, |parent| parent.child("…"));
7649
7650 let left = if first_edit_row != cursor_point.row {
7651 render_relative_row_jump("", cursor_point.row, first_edit_row)
7652 .into_any_element()
7653 } else {
7654 Icon::new(IconName::ZedPredict).into_any_element()
7655 };
7656
7657 Some(
7658 h_flex()
7659 .h_full()
7660 .flex_1()
7661 .gap_2()
7662 .pr_1()
7663 .overflow_x_hidden()
7664 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7665 .child(left)
7666 .child(preview),
7667 )
7668 }
7669 }
7670 }
7671
7672 fn render_context_menu(
7673 &self,
7674 style: &EditorStyle,
7675 max_height_in_lines: u32,
7676 y_flipped: bool,
7677 window: &mut Window,
7678 cx: &mut Context<Editor>,
7679 ) -> Option<AnyElement> {
7680 let menu = self.context_menu.borrow();
7681 let menu = menu.as_ref()?;
7682 if !menu.visible() {
7683 return None;
7684 };
7685 Some(menu.render(style, max_height_in_lines, y_flipped, window, cx))
7686 }
7687
7688 fn render_context_menu_aside(
7689 &mut self,
7690 max_size: Size<Pixels>,
7691 window: &mut Window,
7692 cx: &mut Context<Editor>,
7693 ) -> Option<AnyElement> {
7694 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7695 if menu.visible() {
7696 menu.render_aside(self, max_size, window, cx)
7697 } else {
7698 None
7699 }
7700 })
7701 }
7702
7703 fn hide_context_menu(
7704 &mut self,
7705 window: &mut Window,
7706 cx: &mut Context<Self>,
7707 ) -> Option<CodeContextMenu> {
7708 cx.notify();
7709 self.completion_tasks.clear();
7710 let context_menu = self.context_menu.borrow_mut().take();
7711 self.stale_inline_completion_in_menu.take();
7712 self.update_visible_inline_completion(window, cx);
7713 context_menu
7714 }
7715
7716 fn show_snippet_choices(
7717 &mut self,
7718 choices: &Vec<String>,
7719 selection: Range<Anchor>,
7720 cx: &mut Context<Self>,
7721 ) {
7722 if selection.start.buffer_id.is_none() {
7723 return;
7724 }
7725 let buffer_id = selection.start.buffer_id.unwrap();
7726 let buffer = self.buffer().read(cx).buffer(buffer_id);
7727 let id = post_inc(&mut self.next_completion_id);
7728
7729 if let Some(buffer) = buffer {
7730 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7731 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7732 ));
7733 }
7734 }
7735
7736 pub fn insert_snippet(
7737 &mut self,
7738 insertion_ranges: &[Range<usize>],
7739 snippet: Snippet,
7740 window: &mut Window,
7741 cx: &mut Context<Self>,
7742 ) -> Result<()> {
7743 struct Tabstop<T> {
7744 is_end_tabstop: bool,
7745 ranges: Vec<Range<T>>,
7746 choices: Option<Vec<String>>,
7747 }
7748
7749 let tabstops = self.buffer.update(cx, |buffer, cx| {
7750 let snippet_text: Arc<str> = snippet.text.clone().into();
7751 let edits = insertion_ranges
7752 .iter()
7753 .cloned()
7754 .map(|range| (range, snippet_text.clone()));
7755 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7756
7757 let snapshot = &*buffer.read(cx);
7758 let snippet = &snippet;
7759 snippet
7760 .tabstops
7761 .iter()
7762 .map(|tabstop| {
7763 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7764 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7765 });
7766 let mut tabstop_ranges = tabstop
7767 .ranges
7768 .iter()
7769 .flat_map(|tabstop_range| {
7770 let mut delta = 0_isize;
7771 insertion_ranges.iter().map(move |insertion_range| {
7772 let insertion_start = insertion_range.start as isize + delta;
7773 delta +=
7774 snippet.text.len() as isize - insertion_range.len() as isize;
7775
7776 let start = ((insertion_start + tabstop_range.start) as usize)
7777 .min(snapshot.len());
7778 let end = ((insertion_start + tabstop_range.end) as usize)
7779 .min(snapshot.len());
7780 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7781 })
7782 })
7783 .collect::<Vec<_>>();
7784 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7785
7786 Tabstop {
7787 is_end_tabstop,
7788 ranges: tabstop_ranges,
7789 choices: tabstop.choices.clone(),
7790 }
7791 })
7792 .collect::<Vec<_>>()
7793 });
7794 if let Some(tabstop) = tabstops.first() {
7795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7796 s.select_ranges(tabstop.ranges.iter().cloned());
7797 });
7798
7799 if let Some(choices) = &tabstop.choices {
7800 if let Some(selection) = tabstop.ranges.first() {
7801 self.show_snippet_choices(choices, selection.clone(), cx)
7802 }
7803 }
7804
7805 // If we're already at the last tabstop and it's at the end of the snippet,
7806 // we're done, we don't need to keep the state around.
7807 if !tabstop.is_end_tabstop {
7808 let choices = tabstops
7809 .iter()
7810 .map(|tabstop| tabstop.choices.clone())
7811 .collect();
7812
7813 let ranges = tabstops
7814 .into_iter()
7815 .map(|tabstop| tabstop.ranges)
7816 .collect::<Vec<_>>();
7817
7818 self.snippet_stack.push(SnippetState {
7819 active_index: 0,
7820 ranges,
7821 choices,
7822 });
7823 }
7824
7825 // Check whether the just-entered snippet ends with an auto-closable bracket.
7826 if self.autoclose_regions.is_empty() {
7827 let snapshot = self.buffer.read(cx).snapshot(cx);
7828 for selection in &mut self.selections.all::<Point>(cx) {
7829 let selection_head = selection.head();
7830 let Some(scope) = snapshot.language_scope_at(selection_head) else {
7831 continue;
7832 };
7833
7834 let mut bracket_pair = None;
7835 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
7836 let prev_chars = snapshot
7837 .reversed_chars_at(selection_head)
7838 .collect::<String>();
7839 for (pair, enabled) in scope.brackets() {
7840 if enabled
7841 && pair.close
7842 && prev_chars.starts_with(pair.start.as_str())
7843 && next_chars.starts_with(pair.end.as_str())
7844 {
7845 bracket_pair = Some(pair.clone());
7846 break;
7847 }
7848 }
7849 if let Some(pair) = bracket_pair {
7850 let start = snapshot.anchor_after(selection_head);
7851 let end = snapshot.anchor_after(selection_head);
7852 self.autoclose_regions.push(AutocloseRegion {
7853 selection_id: selection.id,
7854 range: start..end,
7855 pair,
7856 });
7857 }
7858 }
7859 }
7860 }
7861 Ok(())
7862 }
7863
7864 pub fn move_to_next_snippet_tabstop(
7865 &mut self,
7866 window: &mut Window,
7867 cx: &mut Context<Self>,
7868 ) -> bool {
7869 self.move_to_snippet_tabstop(Bias::Right, window, cx)
7870 }
7871
7872 pub fn move_to_prev_snippet_tabstop(
7873 &mut self,
7874 window: &mut Window,
7875 cx: &mut Context<Self>,
7876 ) -> bool {
7877 self.move_to_snippet_tabstop(Bias::Left, window, cx)
7878 }
7879
7880 pub fn move_to_snippet_tabstop(
7881 &mut self,
7882 bias: Bias,
7883 window: &mut Window,
7884 cx: &mut Context<Self>,
7885 ) -> bool {
7886 if let Some(mut snippet) = self.snippet_stack.pop() {
7887 match bias {
7888 Bias::Left => {
7889 if snippet.active_index > 0 {
7890 snippet.active_index -= 1;
7891 } else {
7892 self.snippet_stack.push(snippet);
7893 return false;
7894 }
7895 }
7896 Bias::Right => {
7897 if snippet.active_index + 1 < snippet.ranges.len() {
7898 snippet.active_index += 1;
7899 } else {
7900 self.snippet_stack.push(snippet);
7901 return false;
7902 }
7903 }
7904 }
7905 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
7906 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7907 s.select_anchor_ranges(current_ranges.iter().cloned())
7908 });
7909
7910 if let Some(choices) = &snippet.choices[snippet.active_index] {
7911 if let Some(selection) = current_ranges.first() {
7912 self.show_snippet_choices(&choices, selection.clone(), cx);
7913 }
7914 }
7915
7916 // If snippet state is not at the last tabstop, push it back on the stack
7917 if snippet.active_index + 1 < snippet.ranges.len() {
7918 self.snippet_stack.push(snippet);
7919 }
7920 return true;
7921 }
7922 }
7923
7924 false
7925 }
7926
7927 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7928 self.transact(window, cx, |this, window, cx| {
7929 this.select_all(&SelectAll, window, cx);
7930 this.insert("", window, cx);
7931 });
7932 }
7933
7934 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
7935 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
7936 self.transact(window, cx, |this, window, cx| {
7937 this.select_autoclose_pair(window, cx);
7938 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
7939 if !this.linked_edit_ranges.is_empty() {
7940 let selections = this.selections.all::<MultiBufferPoint>(cx);
7941 let snapshot = this.buffer.read(cx).snapshot(cx);
7942
7943 for selection in selections.iter() {
7944 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
7945 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
7946 if selection_start.buffer_id != selection_end.buffer_id {
7947 continue;
7948 }
7949 if let Some(ranges) =
7950 this.linked_editing_ranges_for(selection_start..selection_end, cx)
7951 {
7952 for (buffer, entries) in ranges {
7953 linked_ranges.entry(buffer).or_default().extend(entries);
7954 }
7955 }
7956 }
7957 }
7958
7959 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
7960 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
7961 for selection in &mut selections {
7962 if selection.is_empty() {
7963 let old_head = selection.head();
7964 let mut new_head =
7965 movement::left(&display_map, old_head.to_display_point(&display_map))
7966 .to_point(&display_map);
7967 if let Some((buffer, line_buffer_range)) = display_map
7968 .buffer_snapshot
7969 .buffer_line_for_row(MultiBufferRow(old_head.row))
7970 {
7971 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
7972 let indent_len = match indent_size.kind {
7973 IndentKind::Space => {
7974 buffer.settings_at(line_buffer_range.start, cx).tab_size
7975 }
7976 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
7977 };
7978 if old_head.column <= indent_size.len && old_head.column > 0 {
7979 let indent_len = indent_len.get();
7980 new_head = cmp::min(
7981 new_head,
7982 MultiBufferPoint::new(
7983 old_head.row,
7984 ((old_head.column - 1) / indent_len) * indent_len,
7985 ),
7986 );
7987 }
7988 }
7989
7990 selection.set_head(new_head, SelectionGoal::None);
7991 }
7992 }
7993
7994 this.signature_help_state.set_backspace_pressed(true);
7995 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
7996 s.select(selections)
7997 });
7998 this.insert("", window, cx);
7999 let empty_str: Arc<str> = Arc::from("");
8000 for (buffer, edits) in linked_ranges {
8001 let snapshot = buffer.read(cx).snapshot();
8002 use text::ToPoint as TP;
8003
8004 let edits = edits
8005 .into_iter()
8006 .map(|range| {
8007 let end_point = TP::to_point(&range.end, &snapshot);
8008 let mut start_point = TP::to_point(&range.start, &snapshot);
8009
8010 if end_point == start_point {
8011 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8012 .saturating_sub(1);
8013 start_point =
8014 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8015 };
8016
8017 (start_point..end_point, empty_str.clone())
8018 })
8019 .sorted_by_key(|(range, _)| range.start)
8020 .collect::<Vec<_>>();
8021 buffer.update(cx, |this, cx| {
8022 this.edit(edits, None, cx);
8023 })
8024 }
8025 this.refresh_inline_completion(true, false, window, cx);
8026 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8027 });
8028 }
8029
8030 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8031 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8032 self.transact(window, cx, |this, window, cx| {
8033 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8034 s.move_with(|map, selection| {
8035 if selection.is_empty() {
8036 let cursor = movement::right(map, selection.head());
8037 selection.end = cursor;
8038 selection.reversed = true;
8039 selection.goal = SelectionGoal::None;
8040 }
8041 })
8042 });
8043 this.insert("", window, cx);
8044 this.refresh_inline_completion(true, false, window, cx);
8045 });
8046 }
8047
8048 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8049 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8050 if self.move_to_prev_snippet_tabstop(window, cx) {
8051 return;
8052 }
8053 self.outdent(&Outdent, window, cx);
8054 }
8055
8056 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8057 if self.move_to_next_snippet_tabstop(window, cx) {
8058 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8059 return;
8060 }
8061 if self.read_only(cx) {
8062 return;
8063 }
8064 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8065 let mut selections = self.selections.all_adjusted(cx);
8066 let buffer = self.buffer.read(cx);
8067 let snapshot = buffer.snapshot(cx);
8068 let rows_iter = selections.iter().map(|s| s.head().row);
8069 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8070
8071 let mut edits = Vec::new();
8072 let mut prev_edited_row = 0;
8073 let mut row_delta = 0;
8074 for selection in &mut selections {
8075 if selection.start.row != prev_edited_row {
8076 row_delta = 0;
8077 }
8078 prev_edited_row = selection.end.row;
8079
8080 // If the selection is non-empty, then increase the indentation of the selected lines.
8081 if !selection.is_empty() {
8082 row_delta =
8083 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8084 continue;
8085 }
8086
8087 // If the selection is empty and the cursor is in the leading whitespace before the
8088 // suggested indentation, then auto-indent the line.
8089 let cursor = selection.head();
8090 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8091 if let Some(suggested_indent) =
8092 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8093 {
8094 if cursor.column < suggested_indent.len
8095 && cursor.column <= current_indent.len
8096 && current_indent.len <= suggested_indent.len
8097 {
8098 selection.start = Point::new(cursor.row, suggested_indent.len);
8099 selection.end = selection.start;
8100 if row_delta == 0 {
8101 edits.extend(Buffer::edit_for_indent_size_adjustment(
8102 cursor.row,
8103 current_indent,
8104 suggested_indent,
8105 ));
8106 row_delta = suggested_indent.len - current_indent.len;
8107 }
8108 continue;
8109 }
8110 }
8111
8112 // Otherwise, insert a hard or soft tab.
8113 let settings = buffer.language_settings_at(cursor, cx);
8114 let tab_size = if settings.hard_tabs {
8115 IndentSize::tab()
8116 } else {
8117 let tab_size = settings.tab_size.get();
8118 let char_column = snapshot
8119 .text_for_range(Point::new(cursor.row, 0)..cursor)
8120 .flat_map(str::chars)
8121 .count()
8122 + row_delta as usize;
8123 let chars_to_next_tab_stop = tab_size - (char_column as u32 % tab_size);
8124 IndentSize::spaces(chars_to_next_tab_stop)
8125 };
8126 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8127 selection.end = selection.start;
8128 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8129 row_delta += tab_size.len;
8130 }
8131
8132 self.transact(window, cx, |this, window, cx| {
8133 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8134 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8135 s.select(selections)
8136 });
8137 this.refresh_inline_completion(true, false, window, cx);
8138 });
8139 }
8140
8141 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8142 if self.read_only(cx) {
8143 return;
8144 }
8145 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8146 let mut selections = self.selections.all::<Point>(cx);
8147 let mut prev_edited_row = 0;
8148 let mut row_delta = 0;
8149 let mut edits = Vec::new();
8150 let buffer = self.buffer.read(cx);
8151 let snapshot = buffer.snapshot(cx);
8152 for selection in &mut selections {
8153 if selection.start.row != prev_edited_row {
8154 row_delta = 0;
8155 }
8156 prev_edited_row = selection.end.row;
8157
8158 row_delta =
8159 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8160 }
8161
8162 self.transact(window, cx, |this, window, cx| {
8163 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8164 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8165 s.select(selections)
8166 });
8167 });
8168 }
8169
8170 fn indent_selection(
8171 buffer: &MultiBuffer,
8172 snapshot: &MultiBufferSnapshot,
8173 selection: &mut Selection<Point>,
8174 edits: &mut Vec<(Range<Point>, String)>,
8175 delta_for_start_row: u32,
8176 cx: &App,
8177 ) -> u32 {
8178 let settings = buffer.language_settings_at(selection.start, cx);
8179 let tab_size = settings.tab_size.get();
8180 let indent_kind = if settings.hard_tabs {
8181 IndentKind::Tab
8182 } else {
8183 IndentKind::Space
8184 };
8185 let mut start_row = selection.start.row;
8186 let mut end_row = selection.end.row + 1;
8187
8188 // If a selection ends at the beginning of a line, don't indent
8189 // that last line.
8190 if selection.end.column == 0 && selection.end.row > selection.start.row {
8191 end_row -= 1;
8192 }
8193
8194 // Avoid re-indenting a row that has already been indented by a
8195 // previous selection, but still update this selection's column
8196 // to reflect that indentation.
8197 if delta_for_start_row > 0 {
8198 start_row += 1;
8199 selection.start.column += delta_for_start_row;
8200 if selection.end.row == selection.start.row {
8201 selection.end.column += delta_for_start_row;
8202 }
8203 }
8204
8205 let mut delta_for_end_row = 0;
8206 let has_multiple_rows = start_row + 1 != end_row;
8207 for row in start_row..end_row {
8208 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8209 let indent_delta = match (current_indent.kind, indent_kind) {
8210 (IndentKind::Space, IndentKind::Space) => {
8211 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8212 IndentSize::spaces(columns_to_next_tab_stop)
8213 }
8214 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8215 (_, IndentKind::Tab) => IndentSize::tab(),
8216 };
8217
8218 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8219 0
8220 } else {
8221 selection.start.column
8222 };
8223 let row_start = Point::new(row, start);
8224 edits.push((
8225 row_start..row_start,
8226 indent_delta.chars().collect::<String>(),
8227 ));
8228
8229 // Update this selection's endpoints to reflect the indentation.
8230 if row == selection.start.row {
8231 selection.start.column += indent_delta.len;
8232 }
8233 if row == selection.end.row {
8234 selection.end.column += indent_delta.len;
8235 delta_for_end_row = indent_delta.len;
8236 }
8237 }
8238
8239 if selection.start.row == selection.end.row {
8240 delta_for_start_row + delta_for_end_row
8241 } else {
8242 delta_for_end_row
8243 }
8244 }
8245
8246 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8247 if self.read_only(cx) {
8248 return;
8249 }
8250 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8252 let selections = self.selections.all::<Point>(cx);
8253 let mut deletion_ranges = Vec::new();
8254 let mut last_outdent = None;
8255 {
8256 let buffer = self.buffer.read(cx);
8257 let snapshot = buffer.snapshot(cx);
8258 for selection in &selections {
8259 let settings = buffer.language_settings_at(selection.start, cx);
8260 let tab_size = settings.tab_size.get();
8261 let mut rows = selection.spanned_rows(false, &display_map);
8262
8263 // Avoid re-outdenting a row that has already been outdented by a
8264 // previous selection.
8265 if let Some(last_row) = last_outdent {
8266 if last_row == rows.start {
8267 rows.start = rows.start.next_row();
8268 }
8269 }
8270 let has_multiple_rows = rows.len() > 1;
8271 for row in rows.iter_rows() {
8272 let indent_size = snapshot.indent_size_for_line(row);
8273 if indent_size.len > 0 {
8274 let deletion_len = match indent_size.kind {
8275 IndentKind::Space => {
8276 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8277 if columns_to_prev_tab_stop == 0 {
8278 tab_size
8279 } else {
8280 columns_to_prev_tab_stop
8281 }
8282 }
8283 IndentKind::Tab => 1,
8284 };
8285 let start = if has_multiple_rows
8286 || deletion_len > selection.start.column
8287 || indent_size.len < selection.start.column
8288 {
8289 0
8290 } else {
8291 selection.start.column - deletion_len
8292 };
8293 deletion_ranges.push(
8294 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8295 );
8296 last_outdent = Some(row);
8297 }
8298 }
8299 }
8300 }
8301
8302 self.transact(window, cx, |this, window, cx| {
8303 this.buffer.update(cx, |buffer, cx| {
8304 let empty_str: Arc<str> = Arc::default();
8305 buffer.edit(
8306 deletion_ranges
8307 .into_iter()
8308 .map(|range| (range, empty_str.clone())),
8309 None,
8310 cx,
8311 );
8312 });
8313 let selections = this.selections.all::<usize>(cx);
8314 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8315 s.select(selections)
8316 });
8317 });
8318 }
8319
8320 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8321 if self.read_only(cx) {
8322 return;
8323 }
8324 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8325 let selections = self
8326 .selections
8327 .all::<usize>(cx)
8328 .into_iter()
8329 .map(|s| s.range());
8330
8331 self.transact(window, cx, |this, window, cx| {
8332 this.buffer.update(cx, |buffer, cx| {
8333 buffer.autoindent_ranges(selections, cx);
8334 });
8335 let selections = this.selections.all::<usize>(cx);
8336 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8337 s.select(selections)
8338 });
8339 });
8340 }
8341
8342 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8343 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8344 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8345 let selections = self.selections.all::<Point>(cx);
8346
8347 let mut new_cursors = Vec::new();
8348 let mut edit_ranges = Vec::new();
8349 let mut selections = selections.iter().peekable();
8350 while let Some(selection) = selections.next() {
8351 let mut rows = selection.spanned_rows(false, &display_map);
8352 let goal_display_column = selection.head().to_display_point(&display_map).column();
8353
8354 // Accumulate contiguous regions of rows that we want to delete.
8355 while let Some(next_selection) = selections.peek() {
8356 let next_rows = next_selection.spanned_rows(false, &display_map);
8357 if next_rows.start <= rows.end {
8358 rows.end = next_rows.end;
8359 selections.next().unwrap();
8360 } else {
8361 break;
8362 }
8363 }
8364
8365 let buffer = &display_map.buffer_snapshot;
8366 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8367 let edit_end;
8368 let cursor_buffer_row;
8369 if buffer.max_point().row >= rows.end.0 {
8370 // If there's a line after the range, delete the \n from the end of the row range
8371 // and position the cursor on the next line.
8372 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8373 cursor_buffer_row = rows.end;
8374 } else {
8375 // If there isn't a line after the range, delete the \n from the line before the
8376 // start of the row range and position the cursor there.
8377 edit_start = edit_start.saturating_sub(1);
8378 edit_end = buffer.len();
8379 cursor_buffer_row = rows.start.previous_row();
8380 }
8381
8382 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8383 *cursor.column_mut() =
8384 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8385
8386 new_cursors.push((
8387 selection.id,
8388 buffer.anchor_after(cursor.to_point(&display_map)),
8389 ));
8390 edit_ranges.push(edit_start..edit_end);
8391 }
8392
8393 self.transact(window, cx, |this, window, cx| {
8394 let buffer = this.buffer.update(cx, |buffer, cx| {
8395 let empty_str: Arc<str> = Arc::default();
8396 buffer.edit(
8397 edit_ranges
8398 .into_iter()
8399 .map(|range| (range, empty_str.clone())),
8400 None,
8401 cx,
8402 );
8403 buffer.snapshot(cx)
8404 });
8405 let new_selections = new_cursors
8406 .into_iter()
8407 .map(|(id, cursor)| {
8408 let cursor = cursor.to_point(&buffer);
8409 Selection {
8410 id,
8411 start: cursor,
8412 end: cursor,
8413 reversed: false,
8414 goal: SelectionGoal::None,
8415 }
8416 })
8417 .collect();
8418
8419 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8420 s.select(new_selections);
8421 });
8422 });
8423 }
8424
8425 pub fn join_lines_impl(
8426 &mut self,
8427 insert_whitespace: bool,
8428 window: &mut Window,
8429 cx: &mut Context<Self>,
8430 ) {
8431 if self.read_only(cx) {
8432 return;
8433 }
8434 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8435 for selection in self.selections.all::<Point>(cx) {
8436 let start = MultiBufferRow(selection.start.row);
8437 // Treat single line selections as if they include the next line. Otherwise this action
8438 // would do nothing for single line selections individual cursors.
8439 let end = if selection.start.row == selection.end.row {
8440 MultiBufferRow(selection.start.row + 1)
8441 } else {
8442 MultiBufferRow(selection.end.row)
8443 };
8444
8445 if let Some(last_row_range) = row_ranges.last_mut() {
8446 if start <= last_row_range.end {
8447 last_row_range.end = end;
8448 continue;
8449 }
8450 }
8451 row_ranges.push(start..end);
8452 }
8453
8454 let snapshot = self.buffer.read(cx).snapshot(cx);
8455 let mut cursor_positions = Vec::new();
8456 for row_range in &row_ranges {
8457 let anchor = snapshot.anchor_before(Point::new(
8458 row_range.end.previous_row().0,
8459 snapshot.line_len(row_range.end.previous_row()),
8460 ));
8461 cursor_positions.push(anchor..anchor);
8462 }
8463
8464 self.transact(window, cx, |this, window, cx| {
8465 for row_range in row_ranges.into_iter().rev() {
8466 for row in row_range.iter_rows().rev() {
8467 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8468 let next_line_row = row.next_row();
8469 let indent = snapshot.indent_size_for_line(next_line_row);
8470 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8471
8472 let replace =
8473 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8474 " "
8475 } else {
8476 ""
8477 };
8478
8479 this.buffer.update(cx, |buffer, cx| {
8480 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8481 });
8482 }
8483 }
8484
8485 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8486 s.select_anchor_ranges(cursor_positions)
8487 });
8488 });
8489 }
8490
8491 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8493 self.join_lines_impl(true, window, cx);
8494 }
8495
8496 pub fn sort_lines_case_sensitive(
8497 &mut self,
8498 _: &SortLinesCaseSensitive,
8499 window: &mut Window,
8500 cx: &mut Context<Self>,
8501 ) {
8502 self.manipulate_lines(window, cx, |lines| lines.sort())
8503 }
8504
8505 pub fn sort_lines_case_insensitive(
8506 &mut self,
8507 _: &SortLinesCaseInsensitive,
8508 window: &mut Window,
8509 cx: &mut Context<Self>,
8510 ) {
8511 self.manipulate_lines(window, cx, |lines| {
8512 lines.sort_by_key(|line| line.to_lowercase())
8513 })
8514 }
8515
8516 pub fn unique_lines_case_insensitive(
8517 &mut self,
8518 _: &UniqueLinesCaseInsensitive,
8519 window: &mut Window,
8520 cx: &mut Context<Self>,
8521 ) {
8522 self.manipulate_lines(window, cx, |lines| {
8523 let mut seen = HashSet::default();
8524 lines.retain(|line| seen.insert(line.to_lowercase()));
8525 })
8526 }
8527
8528 pub fn unique_lines_case_sensitive(
8529 &mut self,
8530 _: &UniqueLinesCaseSensitive,
8531 window: &mut Window,
8532 cx: &mut Context<Self>,
8533 ) {
8534 self.manipulate_lines(window, cx, |lines| {
8535 let mut seen = HashSet::default();
8536 lines.retain(|line| seen.insert(*line));
8537 })
8538 }
8539
8540 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8541 let Some(project) = self.project.clone() else {
8542 return;
8543 };
8544 self.reload(project, window, cx)
8545 .detach_and_notify_err(window, cx);
8546 }
8547
8548 pub fn restore_file(
8549 &mut self,
8550 _: &::git::RestoreFile,
8551 window: &mut Window,
8552 cx: &mut Context<Self>,
8553 ) {
8554 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8555 let mut buffer_ids = HashSet::default();
8556 let snapshot = self.buffer().read(cx).snapshot(cx);
8557 for selection in self.selections.all::<usize>(cx) {
8558 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8559 }
8560
8561 let buffer = self.buffer().read(cx);
8562 let ranges = buffer_ids
8563 .into_iter()
8564 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8565 .collect::<Vec<_>>();
8566
8567 self.restore_hunks_in_ranges(ranges, window, cx);
8568 }
8569
8570 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8571 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8572 let selections = self
8573 .selections
8574 .all(cx)
8575 .into_iter()
8576 .map(|s| s.range())
8577 .collect();
8578 self.restore_hunks_in_ranges(selections, window, cx);
8579 }
8580
8581 pub fn restore_hunks_in_ranges(
8582 &mut self,
8583 ranges: Vec<Range<Point>>,
8584 window: &mut Window,
8585 cx: &mut Context<Editor>,
8586 ) {
8587 let mut revert_changes = HashMap::default();
8588 let chunk_by = self
8589 .snapshot(window, cx)
8590 .hunks_for_ranges(ranges)
8591 .into_iter()
8592 .chunk_by(|hunk| hunk.buffer_id);
8593 for (buffer_id, hunks) in &chunk_by {
8594 let hunks = hunks.collect::<Vec<_>>();
8595 for hunk in &hunks {
8596 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8597 }
8598 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8599 }
8600 drop(chunk_by);
8601 if !revert_changes.is_empty() {
8602 self.transact(window, cx, |editor, window, cx| {
8603 editor.restore(revert_changes, window, cx);
8604 });
8605 }
8606 }
8607
8608 pub fn open_active_item_in_terminal(
8609 &mut self,
8610 _: &OpenInTerminal,
8611 window: &mut Window,
8612 cx: &mut Context<Self>,
8613 ) {
8614 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8615 let project_path = buffer.read(cx).project_path(cx)?;
8616 let project = self.project.as_ref()?.read(cx);
8617 let entry = project.entry_for_path(&project_path, cx)?;
8618 let parent = match &entry.canonical_path {
8619 Some(canonical_path) => canonical_path.to_path_buf(),
8620 None => project.absolute_path(&project_path, cx)?,
8621 }
8622 .parent()?
8623 .to_path_buf();
8624 Some(parent)
8625 }) {
8626 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8627 }
8628 }
8629
8630 fn set_breakpoint_context_menu(
8631 &mut self,
8632 display_row: DisplayRow,
8633 position: Option<Anchor>,
8634 clicked_point: gpui::Point<Pixels>,
8635 window: &mut Window,
8636 cx: &mut Context<Self>,
8637 ) {
8638 if !cx.has_flag::<Debugger>() {
8639 return;
8640 }
8641 let source = self
8642 .buffer
8643 .read(cx)
8644 .snapshot(cx)
8645 .anchor_before(Point::new(display_row.0, 0u32));
8646
8647 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8648
8649 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8650 self,
8651 source,
8652 clicked_point,
8653 context_menu,
8654 window,
8655 cx,
8656 );
8657 }
8658
8659 fn add_edit_breakpoint_block(
8660 &mut self,
8661 anchor: Anchor,
8662 breakpoint: &Breakpoint,
8663 edit_action: BreakpointPromptEditAction,
8664 window: &mut Window,
8665 cx: &mut Context<Self>,
8666 ) {
8667 let weak_editor = cx.weak_entity();
8668 let bp_prompt = cx.new(|cx| {
8669 BreakpointPromptEditor::new(
8670 weak_editor,
8671 anchor,
8672 breakpoint.clone(),
8673 edit_action,
8674 window,
8675 cx,
8676 )
8677 });
8678
8679 let height = bp_prompt.update(cx, |this, cx| {
8680 this.prompt
8681 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8682 });
8683 let cloned_prompt = bp_prompt.clone();
8684 let blocks = vec![BlockProperties {
8685 style: BlockStyle::Sticky,
8686 placement: BlockPlacement::Above(anchor),
8687 height,
8688 render: Arc::new(move |cx| {
8689 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8690 cloned_prompt.clone().into_any_element()
8691 }),
8692 priority: 0,
8693 }];
8694
8695 let focus_handle = bp_prompt.focus_handle(cx);
8696 window.focus(&focus_handle);
8697
8698 let block_ids = self.insert_blocks(blocks, None, cx);
8699 bp_prompt.update(cx, |prompt, _| {
8700 prompt.add_block_ids(block_ids);
8701 });
8702 }
8703
8704 fn breakpoint_at_cursor_head(
8705 &self,
8706 window: &mut Window,
8707 cx: &mut Context<Self>,
8708 ) -> Option<(Anchor, Breakpoint)> {
8709 let cursor_position: Point = self.selections.newest(cx).head();
8710 self.breakpoint_at_row(cursor_position.row, window, cx)
8711 }
8712
8713 pub(crate) fn breakpoint_at_row(
8714 &self,
8715 row: u32,
8716 window: &mut Window,
8717 cx: &mut Context<Self>,
8718 ) -> Option<(Anchor, Breakpoint)> {
8719 let snapshot = self.snapshot(window, cx);
8720 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8721
8722 let project = self.project.clone()?;
8723
8724 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8725 snapshot
8726 .buffer_snapshot
8727 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8728 })?;
8729
8730 let enclosing_excerpt = breakpoint_position.excerpt_id;
8731 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8732 let buffer_snapshot = buffer.read(cx).snapshot();
8733
8734 let row = buffer_snapshot
8735 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8736 .row;
8737
8738 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8739 let anchor_end = snapshot
8740 .buffer_snapshot
8741 .anchor_after(Point::new(row, line_len));
8742
8743 let bp = self
8744 .breakpoint_store
8745 .as_ref()?
8746 .read_with(cx, |breakpoint_store, cx| {
8747 breakpoint_store
8748 .breakpoints(
8749 &buffer,
8750 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8751 &buffer_snapshot,
8752 cx,
8753 )
8754 .next()
8755 .and_then(|(anchor, bp)| {
8756 let breakpoint_row = buffer_snapshot
8757 .summary_for_anchor::<text::PointUtf16>(anchor)
8758 .row;
8759
8760 if breakpoint_row == row {
8761 snapshot
8762 .buffer_snapshot
8763 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8764 .map(|anchor| (anchor, bp.clone()))
8765 } else {
8766 None
8767 }
8768 })
8769 });
8770 bp
8771 }
8772
8773 pub fn edit_log_breakpoint(
8774 &mut self,
8775 _: &EditLogBreakpoint,
8776 window: &mut Window,
8777 cx: &mut Context<Self>,
8778 ) {
8779 let (anchor, bp) = self
8780 .breakpoint_at_cursor_head(window, cx)
8781 .unwrap_or_else(|| {
8782 let cursor_position: Point = self.selections.newest(cx).head();
8783
8784 let breakpoint_position = self
8785 .snapshot(window, cx)
8786 .display_snapshot
8787 .buffer_snapshot
8788 .anchor_after(Point::new(cursor_position.row, 0));
8789
8790 (
8791 breakpoint_position,
8792 Breakpoint {
8793 message: None,
8794 state: BreakpointState::Enabled,
8795 condition: None,
8796 hit_condition: None,
8797 },
8798 )
8799 });
8800
8801 self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
8802 }
8803
8804 pub fn enable_breakpoint(
8805 &mut self,
8806 _: &crate::actions::EnableBreakpoint,
8807 window: &mut Window,
8808 cx: &mut Context<Self>,
8809 ) {
8810 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8811 if breakpoint.is_disabled() {
8812 self.edit_breakpoint_at_anchor(
8813 anchor,
8814 breakpoint,
8815 BreakpointEditAction::InvertState,
8816 cx,
8817 );
8818 }
8819 }
8820 }
8821
8822 pub fn disable_breakpoint(
8823 &mut self,
8824 _: &crate::actions::DisableBreakpoint,
8825 window: &mut Window,
8826 cx: &mut Context<Self>,
8827 ) {
8828 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8829 if breakpoint.is_enabled() {
8830 self.edit_breakpoint_at_anchor(
8831 anchor,
8832 breakpoint,
8833 BreakpointEditAction::InvertState,
8834 cx,
8835 );
8836 }
8837 }
8838 }
8839
8840 pub fn toggle_breakpoint(
8841 &mut self,
8842 _: &crate::actions::ToggleBreakpoint,
8843 window: &mut Window,
8844 cx: &mut Context<Self>,
8845 ) {
8846 let edit_action = BreakpointEditAction::Toggle;
8847
8848 if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
8849 self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
8850 } else {
8851 let cursor_position: Point = self.selections.newest(cx).head();
8852
8853 let breakpoint_position = self
8854 .snapshot(window, cx)
8855 .display_snapshot
8856 .buffer_snapshot
8857 .anchor_after(Point::new(cursor_position.row, 0));
8858
8859 self.edit_breakpoint_at_anchor(
8860 breakpoint_position,
8861 Breakpoint::new_standard(),
8862 edit_action,
8863 cx,
8864 );
8865 }
8866 }
8867
8868 pub fn edit_breakpoint_at_anchor(
8869 &mut self,
8870 breakpoint_position: Anchor,
8871 breakpoint: Breakpoint,
8872 edit_action: BreakpointEditAction,
8873 cx: &mut Context<Self>,
8874 ) {
8875 let Some(breakpoint_store) = &self.breakpoint_store else {
8876 return;
8877 };
8878
8879 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
8880 if breakpoint_position == Anchor::min() {
8881 self.buffer()
8882 .read(cx)
8883 .excerpt_buffer_ids()
8884 .into_iter()
8885 .next()
8886 } else {
8887 None
8888 }
8889 }) else {
8890 return;
8891 };
8892
8893 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
8894 return;
8895 };
8896
8897 breakpoint_store.update(cx, |breakpoint_store, cx| {
8898 breakpoint_store.toggle_breakpoint(
8899 buffer,
8900 (breakpoint_position.text_anchor, breakpoint),
8901 edit_action,
8902 cx,
8903 );
8904 });
8905
8906 cx.notify();
8907 }
8908
8909 #[cfg(any(test, feature = "test-support"))]
8910 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
8911 self.breakpoint_store.clone()
8912 }
8913
8914 pub fn prepare_restore_change(
8915 &self,
8916 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
8917 hunk: &MultiBufferDiffHunk,
8918 cx: &mut App,
8919 ) -> Option<()> {
8920 if hunk.is_created_file() {
8921 return None;
8922 }
8923 let buffer = self.buffer.read(cx);
8924 let diff = buffer.diff_for(hunk.buffer_id)?;
8925 let buffer = buffer.buffer(hunk.buffer_id)?;
8926 let buffer = buffer.read(cx);
8927 let original_text = diff
8928 .read(cx)
8929 .base_text()
8930 .as_rope()
8931 .slice(hunk.diff_base_byte_range.clone());
8932 let buffer_snapshot = buffer.snapshot();
8933 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
8934 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
8935 probe
8936 .0
8937 .start
8938 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
8939 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
8940 }) {
8941 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
8942 Some(())
8943 } else {
8944 None
8945 }
8946 }
8947
8948 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
8949 self.manipulate_lines(window, cx, |lines| lines.reverse())
8950 }
8951
8952 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
8953 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
8954 }
8955
8956 fn manipulate_lines<Fn>(
8957 &mut self,
8958 window: &mut Window,
8959 cx: &mut Context<Self>,
8960 mut callback: Fn,
8961 ) where
8962 Fn: FnMut(&mut Vec<&str>),
8963 {
8964 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8965
8966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8967 let buffer = self.buffer.read(cx).snapshot(cx);
8968
8969 let mut edits = Vec::new();
8970
8971 let selections = self.selections.all::<Point>(cx);
8972 let mut selections = selections.iter().peekable();
8973 let mut contiguous_row_selections = Vec::new();
8974 let mut new_selections = Vec::new();
8975 let mut added_lines = 0;
8976 let mut removed_lines = 0;
8977
8978 while let Some(selection) = selections.next() {
8979 let (start_row, end_row) = consume_contiguous_rows(
8980 &mut contiguous_row_selections,
8981 selection,
8982 &display_map,
8983 &mut selections,
8984 );
8985
8986 let start_point = Point::new(start_row.0, 0);
8987 let end_point = Point::new(
8988 end_row.previous_row().0,
8989 buffer.line_len(end_row.previous_row()),
8990 );
8991 let text = buffer
8992 .text_for_range(start_point..end_point)
8993 .collect::<String>();
8994
8995 let mut lines = text.split('\n').collect_vec();
8996
8997 let lines_before = lines.len();
8998 callback(&mut lines);
8999 let lines_after = lines.len();
9000
9001 edits.push((start_point..end_point, lines.join("\n")));
9002
9003 // Selections must change based on added and removed line count
9004 let start_row =
9005 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9006 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9007 new_selections.push(Selection {
9008 id: selection.id,
9009 start: start_row,
9010 end: end_row,
9011 goal: SelectionGoal::None,
9012 reversed: selection.reversed,
9013 });
9014
9015 if lines_after > lines_before {
9016 added_lines += lines_after - lines_before;
9017 } else if lines_before > lines_after {
9018 removed_lines += lines_before - lines_after;
9019 }
9020 }
9021
9022 self.transact(window, cx, |this, window, cx| {
9023 let buffer = this.buffer.update(cx, |buffer, cx| {
9024 buffer.edit(edits, None, cx);
9025 buffer.snapshot(cx)
9026 });
9027
9028 // Recalculate offsets on newly edited buffer
9029 let new_selections = new_selections
9030 .iter()
9031 .map(|s| {
9032 let start_point = Point::new(s.start.0, 0);
9033 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9034 Selection {
9035 id: s.id,
9036 start: buffer.point_to_offset(start_point),
9037 end: buffer.point_to_offset(end_point),
9038 goal: s.goal,
9039 reversed: s.reversed,
9040 }
9041 })
9042 .collect();
9043
9044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9045 s.select(new_selections);
9046 });
9047
9048 this.request_autoscroll(Autoscroll::fit(), cx);
9049 });
9050 }
9051
9052 pub fn convert_to_upper_case(
9053 &mut self,
9054 _: &ConvertToUpperCase,
9055 window: &mut Window,
9056 cx: &mut Context<Self>,
9057 ) {
9058 self.manipulate_text(window, cx, |text| text.to_uppercase())
9059 }
9060
9061 pub fn convert_to_lower_case(
9062 &mut self,
9063 _: &ConvertToLowerCase,
9064 window: &mut Window,
9065 cx: &mut Context<Self>,
9066 ) {
9067 self.manipulate_text(window, cx, |text| text.to_lowercase())
9068 }
9069
9070 pub fn convert_to_title_case(
9071 &mut self,
9072 _: &ConvertToTitleCase,
9073 window: &mut Window,
9074 cx: &mut Context<Self>,
9075 ) {
9076 self.manipulate_text(window, cx, |text| {
9077 text.split('\n')
9078 .map(|line| line.to_case(Case::Title))
9079 .join("\n")
9080 })
9081 }
9082
9083 pub fn convert_to_snake_case(
9084 &mut self,
9085 _: &ConvertToSnakeCase,
9086 window: &mut Window,
9087 cx: &mut Context<Self>,
9088 ) {
9089 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9090 }
9091
9092 pub fn convert_to_kebab_case(
9093 &mut self,
9094 _: &ConvertToKebabCase,
9095 window: &mut Window,
9096 cx: &mut Context<Self>,
9097 ) {
9098 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9099 }
9100
9101 pub fn convert_to_upper_camel_case(
9102 &mut self,
9103 _: &ConvertToUpperCamelCase,
9104 window: &mut Window,
9105 cx: &mut Context<Self>,
9106 ) {
9107 self.manipulate_text(window, cx, |text| {
9108 text.split('\n')
9109 .map(|line| line.to_case(Case::UpperCamel))
9110 .join("\n")
9111 })
9112 }
9113
9114 pub fn convert_to_lower_camel_case(
9115 &mut self,
9116 _: &ConvertToLowerCamelCase,
9117 window: &mut Window,
9118 cx: &mut Context<Self>,
9119 ) {
9120 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9121 }
9122
9123 pub fn convert_to_opposite_case(
9124 &mut self,
9125 _: &ConvertToOppositeCase,
9126 window: &mut Window,
9127 cx: &mut Context<Self>,
9128 ) {
9129 self.manipulate_text(window, cx, |text| {
9130 text.chars()
9131 .fold(String::with_capacity(text.len()), |mut t, c| {
9132 if c.is_uppercase() {
9133 t.extend(c.to_lowercase());
9134 } else {
9135 t.extend(c.to_uppercase());
9136 }
9137 t
9138 })
9139 })
9140 }
9141
9142 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9143 where
9144 Fn: FnMut(&str) -> String,
9145 {
9146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9147 let buffer = self.buffer.read(cx).snapshot(cx);
9148
9149 let mut new_selections = Vec::new();
9150 let mut edits = Vec::new();
9151 let mut selection_adjustment = 0i32;
9152
9153 for selection in self.selections.all::<usize>(cx) {
9154 let selection_is_empty = selection.is_empty();
9155
9156 let (start, end) = if selection_is_empty {
9157 let word_range = movement::surrounding_word(
9158 &display_map,
9159 selection.start.to_display_point(&display_map),
9160 );
9161 let start = word_range.start.to_offset(&display_map, Bias::Left);
9162 let end = word_range.end.to_offset(&display_map, Bias::Left);
9163 (start, end)
9164 } else {
9165 (selection.start, selection.end)
9166 };
9167
9168 let text = buffer.text_for_range(start..end).collect::<String>();
9169 let old_length = text.len() as i32;
9170 let text = callback(&text);
9171
9172 new_selections.push(Selection {
9173 start: (start as i32 - selection_adjustment) as usize,
9174 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9175 goal: SelectionGoal::None,
9176 ..selection
9177 });
9178
9179 selection_adjustment += old_length - text.len() as i32;
9180
9181 edits.push((start..end, text));
9182 }
9183
9184 self.transact(window, cx, |this, window, cx| {
9185 this.buffer.update(cx, |buffer, cx| {
9186 buffer.edit(edits, None, cx);
9187 });
9188
9189 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9190 s.select(new_selections);
9191 });
9192
9193 this.request_autoscroll(Autoscroll::fit(), cx);
9194 });
9195 }
9196
9197 pub fn duplicate(
9198 &mut self,
9199 upwards: bool,
9200 whole_lines: bool,
9201 window: &mut Window,
9202 cx: &mut Context<Self>,
9203 ) {
9204 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9205
9206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9207 let buffer = &display_map.buffer_snapshot;
9208 let selections = self.selections.all::<Point>(cx);
9209
9210 let mut edits = Vec::new();
9211 let mut selections_iter = selections.iter().peekable();
9212 while let Some(selection) = selections_iter.next() {
9213 let mut rows = selection.spanned_rows(false, &display_map);
9214 // duplicate line-wise
9215 if whole_lines || selection.start == selection.end {
9216 // Avoid duplicating the same lines twice.
9217 while let Some(next_selection) = selections_iter.peek() {
9218 let next_rows = next_selection.spanned_rows(false, &display_map);
9219 if next_rows.start < rows.end {
9220 rows.end = next_rows.end;
9221 selections_iter.next().unwrap();
9222 } else {
9223 break;
9224 }
9225 }
9226
9227 // Copy the text from the selected row region and splice it either at the start
9228 // or end of the region.
9229 let start = Point::new(rows.start.0, 0);
9230 let end = Point::new(
9231 rows.end.previous_row().0,
9232 buffer.line_len(rows.end.previous_row()),
9233 );
9234 let text = buffer
9235 .text_for_range(start..end)
9236 .chain(Some("\n"))
9237 .collect::<String>();
9238 let insert_location = if upwards {
9239 Point::new(rows.end.0, 0)
9240 } else {
9241 start
9242 };
9243 edits.push((insert_location..insert_location, text));
9244 } else {
9245 // duplicate character-wise
9246 let start = selection.start;
9247 let end = selection.end;
9248 let text = buffer.text_for_range(start..end).collect::<String>();
9249 edits.push((selection.end..selection.end, text));
9250 }
9251 }
9252
9253 self.transact(window, cx, |this, _, cx| {
9254 this.buffer.update(cx, |buffer, cx| {
9255 buffer.edit(edits, None, cx);
9256 });
9257
9258 this.request_autoscroll(Autoscroll::fit(), cx);
9259 });
9260 }
9261
9262 pub fn duplicate_line_up(
9263 &mut self,
9264 _: &DuplicateLineUp,
9265 window: &mut Window,
9266 cx: &mut Context<Self>,
9267 ) {
9268 self.duplicate(true, true, window, cx);
9269 }
9270
9271 pub fn duplicate_line_down(
9272 &mut self,
9273 _: &DuplicateLineDown,
9274 window: &mut Window,
9275 cx: &mut Context<Self>,
9276 ) {
9277 self.duplicate(false, true, window, cx);
9278 }
9279
9280 pub fn duplicate_selection(
9281 &mut self,
9282 _: &DuplicateSelection,
9283 window: &mut Window,
9284 cx: &mut Context<Self>,
9285 ) {
9286 self.duplicate(false, false, window, cx);
9287 }
9288
9289 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9290 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9291
9292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9293 let buffer = self.buffer.read(cx).snapshot(cx);
9294
9295 let mut edits = Vec::new();
9296 let mut unfold_ranges = Vec::new();
9297 let mut refold_creases = Vec::new();
9298
9299 let selections = self.selections.all::<Point>(cx);
9300 let mut selections = selections.iter().peekable();
9301 let mut contiguous_row_selections = Vec::new();
9302 let mut new_selections = Vec::new();
9303
9304 while let Some(selection) = selections.next() {
9305 // Find all the selections that span a contiguous row range
9306 let (start_row, end_row) = consume_contiguous_rows(
9307 &mut contiguous_row_selections,
9308 selection,
9309 &display_map,
9310 &mut selections,
9311 );
9312
9313 // Move the text spanned by the row range to be before the line preceding the row range
9314 if start_row.0 > 0 {
9315 let range_to_move = Point::new(
9316 start_row.previous_row().0,
9317 buffer.line_len(start_row.previous_row()),
9318 )
9319 ..Point::new(
9320 end_row.previous_row().0,
9321 buffer.line_len(end_row.previous_row()),
9322 );
9323 let insertion_point = display_map
9324 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9325 .0;
9326
9327 // Don't move lines across excerpts
9328 if buffer
9329 .excerpt_containing(insertion_point..range_to_move.end)
9330 .is_some()
9331 {
9332 let text = buffer
9333 .text_for_range(range_to_move.clone())
9334 .flat_map(|s| s.chars())
9335 .skip(1)
9336 .chain(['\n'])
9337 .collect::<String>();
9338
9339 edits.push((
9340 buffer.anchor_after(range_to_move.start)
9341 ..buffer.anchor_before(range_to_move.end),
9342 String::new(),
9343 ));
9344 let insertion_anchor = buffer.anchor_after(insertion_point);
9345 edits.push((insertion_anchor..insertion_anchor, text));
9346
9347 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9348
9349 // Move selections up
9350 new_selections.extend(contiguous_row_selections.drain(..).map(
9351 |mut selection| {
9352 selection.start.row -= row_delta;
9353 selection.end.row -= row_delta;
9354 selection
9355 },
9356 ));
9357
9358 // Move folds up
9359 unfold_ranges.push(range_to_move.clone());
9360 for fold in display_map.folds_in_range(
9361 buffer.anchor_before(range_to_move.start)
9362 ..buffer.anchor_after(range_to_move.end),
9363 ) {
9364 let mut start = fold.range.start.to_point(&buffer);
9365 let mut end = fold.range.end.to_point(&buffer);
9366 start.row -= row_delta;
9367 end.row -= row_delta;
9368 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9369 }
9370 }
9371 }
9372
9373 // If we didn't move line(s), preserve the existing selections
9374 new_selections.append(&mut contiguous_row_selections);
9375 }
9376
9377 self.transact(window, cx, |this, window, cx| {
9378 this.unfold_ranges(&unfold_ranges, true, true, cx);
9379 this.buffer.update(cx, |buffer, cx| {
9380 for (range, text) in edits {
9381 buffer.edit([(range, text)], None, cx);
9382 }
9383 });
9384 this.fold_creases(refold_creases, true, window, cx);
9385 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9386 s.select(new_selections);
9387 })
9388 });
9389 }
9390
9391 pub fn move_line_down(
9392 &mut self,
9393 _: &MoveLineDown,
9394 window: &mut Window,
9395 cx: &mut Context<Self>,
9396 ) {
9397 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9398
9399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9400 let buffer = self.buffer.read(cx).snapshot(cx);
9401
9402 let mut edits = Vec::new();
9403 let mut unfold_ranges = Vec::new();
9404 let mut refold_creases = Vec::new();
9405
9406 let selections = self.selections.all::<Point>(cx);
9407 let mut selections = selections.iter().peekable();
9408 let mut contiguous_row_selections = Vec::new();
9409 let mut new_selections = Vec::new();
9410
9411 while let Some(selection) = selections.next() {
9412 // Find all the selections that span a contiguous row range
9413 let (start_row, end_row) = consume_contiguous_rows(
9414 &mut contiguous_row_selections,
9415 selection,
9416 &display_map,
9417 &mut selections,
9418 );
9419
9420 // Move the text spanned by the row range to be after the last line of the row range
9421 if end_row.0 <= buffer.max_point().row {
9422 let range_to_move =
9423 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9424 let insertion_point = display_map
9425 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9426 .0;
9427
9428 // Don't move lines across excerpt boundaries
9429 if buffer
9430 .excerpt_containing(range_to_move.start..insertion_point)
9431 .is_some()
9432 {
9433 let mut text = String::from("\n");
9434 text.extend(buffer.text_for_range(range_to_move.clone()));
9435 text.pop(); // Drop trailing newline
9436 edits.push((
9437 buffer.anchor_after(range_to_move.start)
9438 ..buffer.anchor_before(range_to_move.end),
9439 String::new(),
9440 ));
9441 let insertion_anchor = buffer.anchor_after(insertion_point);
9442 edits.push((insertion_anchor..insertion_anchor, text));
9443
9444 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9445
9446 // Move selections down
9447 new_selections.extend(contiguous_row_selections.drain(..).map(
9448 |mut selection| {
9449 selection.start.row += row_delta;
9450 selection.end.row += row_delta;
9451 selection
9452 },
9453 ));
9454
9455 // Move folds down
9456 unfold_ranges.push(range_to_move.clone());
9457 for fold in display_map.folds_in_range(
9458 buffer.anchor_before(range_to_move.start)
9459 ..buffer.anchor_after(range_to_move.end),
9460 ) {
9461 let mut start = fold.range.start.to_point(&buffer);
9462 let mut end = fold.range.end.to_point(&buffer);
9463 start.row += row_delta;
9464 end.row += row_delta;
9465 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9466 }
9467 }
9468 }
9469
9470 // If we didn't move line(s), preserve the existing selections
9471 new_selections.append(&mut contiguous_row_selections);
9472 }
9473
9474 self.transact(window, cx, |this, window, cx| {
9475 this.unfold_ranges(&unfold_ranges, true, true, cx);
9476 this.buffer.update(cx, |buffer, cx| {
9477 for (range, text) in edits {
9478 buffer.edit([(range, text)], None, cx);
9479 }
9480 });
9481 this.fold_creases(refold_creases, true, window, cx);
9482 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9483 s.select(new_selections)
9484 });
9485 });
9486 }
9487
9488 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9489 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9490 let text_layout_details = &self.text_layout_details(window);
9491 self.transact(window, cx, |this, window, cx| {
9492 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9493 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9494 s.move_with(|display_map, selection| {
9495 if !selection.is_empty() {
9496 return;
9497 }
9498
9499 let mut head = selection.head();
9500 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9501 if head.column() == display_map.line_len(head.row()) {
9502 transpose_offset = display_map
9503 .buffer_snapshot
9504 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9505 }
9506
9507 if transpose_offset == 0 {
9508 return;
9509 }
9510
9511 *head.column_mut() += 1;
9512 head = display_map.clip_point(head, Bias::Right);
9513 let goal = SelectionGoal::HorizontalPosition(
9514 display_map
9515 .x_for_display_point(head, text_layout_details)
9516 .into(),
9517 );
9518 selection.collapse_to(head, goal);
9519
9520 let transpose_start = display_map
9521 .buffer_snapshot
9522 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9523 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9524 let transpose_end = display_map
9525 .buffer_snapshot
9526 .clip_offset(transpose_offset + 1, Bias::Right);
9527 if let Some(ch) =
9528 display_map.buffer_snapshot.chars_at(transpose_start).next()
9529 {
9530 edits.push((transpose_start..transpose_offset, String::new()));
9531 edits.push((transpose_end..transpose_end, ch.to_string()));
9532 }
9533 }
9534 });
9535 edits
9536 });
9537 this.buffer
9538 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9539 let selections = this.selections.all::<usize>(cx);
9540 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9541 s.select(selections);
9542 });
9543 });
9544 }
9545
9546 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9547 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9548 self.rewrap_impl(RewrapOptions::default(), cx)
9549 }
9550
9551 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9552 let buffer = self.buffer.read(cx).snapshot(cx);
9553 let selections = self.selections.all::<Point>(cx);
9554 let mut selections = selections.iter().peekable();
9555
9556 let mut edits = Vec::new();
9557 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9558
9559 while let Some(selection) = selections.next() {
9560 let mut start_row = selection.start.row;
9561 let mut end_row = selection.end.row;
9562
9563 // Skip selections that overlap with a range that has already been rewrapped.
9564 let selection_range = start_row..end_row;
9565 if rewrapped_row_ranges
9566 .iter()
9567 .any(|range| range.overlaps(&selection_range))
9568 {
9569 continue;
9570 }
9571
9572 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9573
9574 // Since not all lines in the selection may be at the same indent
9575 // level, choose the indent size that is the most common between all
9576 // of the lines.
9577 //
9578 // If there is a tie, we use the deepest indent.
9579 let (indent_size, indent_end) = {
9580 let mut indent_size_occurrences = HashMap::default();
9581 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9582
9583 for row in start_row..=end_row {
9584 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9585 rows_by_indent_size.entry(indent).or_default().push(row);
9586 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9587 }
9588
9589 let indent_size = indent_size_occurrences
9590 .into_iter()
9591 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9592 .map(|(indent, _)| indent)
9593 .unwrap_or_default();
9594 let row = rows_by_indent_size[&indent_size][0];
9595 let indent_end = Point::new(row, indent_size.len);
9596
9597 (indent_size, indent_end)
9598 };
9599
9600 let mut line_prefix = indent_size.chars().collect::<String>();
9601
9602 let mut inside_comment = false;
9603 if let Some(comment_prefix) =
9604 buffer
9605 .language_scope_at(selection.head())
9606 .and_then(|language| {
9607 language
9608 .line_comment_prefixes()
9609 .iter()
9610 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9611 .cloned()
9612 })
9613 {
9614 line_prefix.push_str(&comment_prefix);
9615 inside_comment = true;
9616 }
9617
9618 let language_settings = buffer.language_settings_at(selection.head(), cx);
9619 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9620 RewrapBehavior::InComments => inside_comment,
9621 RewrapBehavior::InSelections => !selection.is_empty(),
9622 RewrapBehavior::Anywhere => true,
9623 };
9624
9625 let should_rewrap = options.override_language_settings
9626 || allow_rewrap_based_on_language
9627 || self.hard_wrap.is_some();
9628 if !should_rewrap {
9629 continue;
9630 }
9631
9632 if selection.is_empty() {
9633 'expand_upwards: while start_row > 0 {
9634 let prev_row = start_row - 1;
9635 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9636 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9637 {
9638 start_row = prev_row;
9639 } else {
9640 break 'expand_upwards;
9641 }
9642 }
9643
9644 'expand_downwards: while end_row < buffer.max_point().row {
9645 let next_row = end_row + 1;
9646 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9647 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9648 {
9649 end_row = next_row;
9650 } else {
9651 break 'expand_downwards;
9652 }
9653 }
9654 }
9655
9656 let start = Point::new(start_row, 0);
9657 let start_offset = start.to_offset(&buffer);
9658 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9659 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9660 let Some(lines_without_prefixes) = selection_text
9661 .lines()
9662 .map(|line| {
9663 line.strip_prefix(&line_prefix)
9664 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9665 .ok_or_else(|| {
9666 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9667 })
9668 })
9669 .collect::<Result<Vec<_>, _>>()
9670 .log_err()
9671 else {
9672 continue;
9673 };
9674
9675 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9676 buffer
9677 .language_settings_at(Point::new(start_row, 0), cx)
9678 .preferred_line_length as usize
9679 });
9680 let wrapped_text = wrap_with_prefix(
9681 line_prefix,
9682 lines_without_prefixes.join("\n"),
9683 wrap_column,
9684 tab_size,
9685 options.preserve_existing_whitespace,
9686 );
9687
9688 // TODO: should always use char-based diff while still supporting cursor behavior that
9689 // matches vim.
9690 let mut diff_options = DiffOptions::default();
9691 if options.override_language_settings {
9692 diff_options.max_word_diff_len = 0;
9693 diff_options.max_word_diff_line_count = 0;
9694 } else {
9695 diff_options.max_word_diff_len = usize::MAX;
9696 diff_options.max_word_diff_line_count = usize::MAX;
9697 }
9698
9699 for (old_range, new_text) in
9700 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9701 {
9702 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9703 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9704 edits.push((edit_start..edit_end, new_text));
9705 }
9706
9707 rewrapped_row_ranges.push(start_row..=end_row);
9708 }
9709
9710 self.buffer
9711 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9712 }
9713
9714 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
9715 let mut text = String::new();
9716 let buffer = self.buffer.read(cx).snapshot(cx);
9717 let mut selections = self.selections.all::<Point>(cx);
9718 let mut clipboard_selections = Vec::with_capacity(selections.len());
9719 {
9720 let max_point = buffer.max_point();
9721 let mut is_first = true;
9722 for selection in &mut selections {
9723 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9724 if is_entire_line {
9725 selection.start = Point::new(selection.start.row, 0);
9726 if !selection.is_empty() && selection.end.column == 0 {
9727 selection.end = cmp::min(max_point, selection.end);
9728 } else {
9729 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
9730 }
9731 selection.goal = SelectionGoal::None;
9732 }
9733 if is_first {
9734 is_first = false;
9735 } else {
9736 text += "\n";
9737 }
9738 let mut len = 0;
9739 for chunk in buffer.text_for_range(selection.start..selection.end) {
9740 text.push_str(chunk);
9741 len += chunk.len();
9742 }
9743 clipboard_selections.push(ClipboardSelection {
9744 len,
9745 is_entire_line,
9746 first_line_indent: buffer
9747 .indent_size_for_line(MultiBufferRow(selection.start.row))
9748 .len,
9749 });
9750 }
9751 }
9752
9753 self.transact(window, cx, |this, window, cx| {
9754 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9755 s.select(selections);
9756 });
9757 this.insert("", window, cx);
9758 });
9759 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
9760 }
9761
9762 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
9763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9764 let item = self.cut_common(window, cx);
9765 cx.write_to_clipboard(item);
9766 }
9767
9768 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
9769 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9770 self.change_selections(None, window, cx, |s| {
9771 s.move_with(|snapshot, sel| {
9772 if sel.is_empty() {
9773 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
9774 }
9775 });
9776 });
9777 let item = self.cut_common(window, cx);
9778 cx.set_global(KillRing(item))
9779 }
9780
9781 pub fn kill_ring_yank(
9782 &mut self,
9783 _: &KillRingYank,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) {
9787 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9788 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
9789 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
9790 (kill_ring.text().to_string(), kill_ring.metadata_json())
9791 } else {
9792 return;
9793 }
9794 } else {
9795 return;
9796 };
9797 self.do_paste(&text, metadata, false, window, cx);
9798 }
9799
9800 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
9801 self.do_copy(true, cx);
9802 }
9803
9804 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
9805 self.do_copy(false, cx);
9806 }
9807
9808 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
9809 let selections = self.selections.all::<Point>(cx);
9810 let buffer = self.buffer.read(cx).read(cx);
9811 let mut text = String::new();
9812
9813 let mut clipboard_selections = Vec::with_capacity(selections.len());
9814 {
9815 let max_point = buffer.max_point();
9816 let mut is_first = true;
9817 for selection in &selections {
9818 let mut start = selection.start;
9819 let mut end = selection.end;
9820 let is_entire_line = selection.is_empty() || self.selections.line_mode;
9821 if is_entire_line {
9822 start = Point::new(start.row, 0);
9823 end = cmp::min(max_point, Point::new(end.row + 1, 0));
9824 }
9825
9826 let mut trimmed_selections = Vec::new();
9827 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
9828 let row = MultiBufferRow(start.row);
9829 let first_indent = buffer.indent_size_for_line(row);
9830 if first_indent.len == 0 || start.column > first_indent.len {
9831 trimmed_selections.push(start..end);
9832 } else {
9833 trimmed_selections.push(
9834 Point::new(row.0, first_indent.len)
9835 ..Point::new(row.0, buffer.line_len(row)),
9836 );
9837 for row in start.row + 1..=end.row {
9838 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
9839 if row_indent_size.len >= first_indent.len {
9840 trimmed_selections.push(
9841 Point::new(row, first_indent.len)
9842 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
9843 );
9844 } else {
9845 trimmed_selections.clear();
9846 trimmed_selections.push(start..end);
9847 break;
9848 }
9849 }
9850 }
9851 } else {
9852 trimmed_selections.push(start..end);
9853 }
9854
9855 for trimmed_range in trimmed_selections {
9856 if is_first {
9857 is_first = false;
9858 } else {
9859 text += "\n";
9860 }
9861 let mut len = 0;
9862 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
9863 text.push_str(chunk);
9864 len += chunk.len();
9865 }
9866 clipboard_selections.push(ClipboardSelection {
9867 len,
9868 is_entire_line,
9869 first_line_indent: buffer
9870 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
9871 .len,
9872 });
9873 }
9874 }
9875 }
9876
9877 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
9878 text,
9879 clipboard_selections,
9880 ));
9881 }
9882
9883 pub fn do_paste(
9884 &mut self,
9885 text: &String,
9886 clipboard_selections: Option<Vec<ClipboardSelection>>,
9887 handle_entire_lines: bool,
9888 window: &mut Window,
9889 cx: &mut Context<Self>,
9890 ) {
9891 if self.read_only(cx) {
9892 return;
9893 }
9894
9895 let clipboard_text = Cow::Borrowed(text);
9896
9897 self.transact(window, cx, |this, window, cx| {
9898 if let Some(mut clipboard_selections) = clipboard_selections {
9899 let old_selections = this.selections.all::<usize>(cx);
9900 let all_selections_were_entire_line =
9901 clipboard_selections.iter().all(|s| s.is_entire_line);
9902 let first_selection_indent_column =
9903 clipboard_selections.first().map(|s| s.first_line_indent);
9904 if clipboard_selections.len() != old_selections.len() {
9905 clipboard_selections.drain(..);
9906 }
9907 let cursor_offset = this.selections.last::<usize>(cx).head();
9908 let mut auto_indent_on_paste = true;
9909
9910 this.buffer.update(cx, |buffer, cx| {
9911 let snapshot = buffer.read(cx);
9912 auto_indent_on_paste = snapshot
9913 .language_settings_at(cursor_offset, cx)
9914 .auto_indent_on_paste;
9915
9916 let mut start_offset = 0;
9917 let mut edits = Vec::new();
9918 let mut original_indent_columns = Vec::new();
9919 for (ix, selection) in old_selections.iter().enumerate() {
9920 let to_insert;
9921 let entire_line;
9922 let original_indent_column;
9923 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
9924 let end_offset = start_offset + clipboard_selection.len;
9925 to_insert = &clipboard_text[start_offset..end_offset];
9926 entire_line = clipboard_selection.is_entire_line;
9927 start_offset = end_offset + 1;
9928 original_indent_column = Some(clipboard_selection.first_line_indent);
9929 } else {
9930 to_insert = clipboard_text.as_str();
9931 entire_line = all_selections_were_entire_line;
9932 original_indent_column = first_selection_indent_column
9933 }
9934
9935 // If the corresponding selection was empty when this slice of the
9936 // clipboard text was written, then the entire line containing the
9937 // selection was copied. If this selection is also currently empty,
9938 // then paste the line before the current line of the buffer.
9939 let range = if selection.is_empty() && handle_entire_lines && entire_line {
9940 let column = selection.start.to_point(&snapshot).column as usize;
9941 let line_start = selection.start - column;
9942 line_start..line_start
9943 } else {
9944 selection.range()
9945 };
9946
9947 edits.push((range, to_insert));
9948 original_indent_columns.push(original_indent_column);
9949 }
9950 drop(snapshot);
9951
9952 buffer.edit(
9953 edits,
9954 if auto_indent_on_paste {
9955 Some(AutoindentMode::Block {
9956 original_indent_columns,
9957 })
9958 } else {
9959 None
9960 },
9961 cx,
9962 );
9963 });
9964
9965 let selections = this.selections.all::<usize>(cx);
9966 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9967 s.select(selections)
9968 });
9969 } else {
9970 this.insert(&clipboard_text, window, cx);
9971 }
9972 });
9973 }
9974
9975 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
9976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9977 if let Some(item) = cx.read_from_clipboard() {
9978 let entries = item.entries();
9979
9980 match entries.first() {
9981 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
9982 // of all the pasted entries.
9983 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
9984 .do_paste(
9985 clipboard_string.text(),
9986 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
9987 true,
9988 window,
9989 cx,
9990 ),
9991 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
9992 }
9993 }
9994 }
9995
9996 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
9997 if self.read_only(cx) {
9998 return;
9999 }
10000
10001 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10002
10003 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10004 if let Some((selections, _)) =
10005 self.selection_history.transaction(transaction_id).cloned()
10006 {
10007 self.change_selections(None, window, cx, |s| {
10008 s.select_anchors(selections.to_vec());
10009 });
10010 } else {
10011 log::error!(
10012 "No entry in selection_history found for undo. \
10013 This may correspond to a bug where undo does not update the selection. \
10014 If this is occurring, please add details to \
10015 https://github.com/zed-industries/zed/issues/22692"
10016 );
10017 }
10018 self.request_autoscroll(Autoscroll::fit(), cx);
10019 self.unmark_text(window, cx);
10020 self.refresh_inline_completion(true, false, window, cx);
10021 cx.emit(EditorEvent::Edited { transaction_id });
10022 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10023 }
10024 }
10025
10026 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10027 if self.read_only(cx) {
10028 return;
10029 }
10030
10031 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10032
10033 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10034 if let Some((_, Some(selections))) =
10035 self.selection_history.transaction(transaction_id).cloned()
10036 {
10037 self.change_selections(None, window, cx, |s| {
10038 s.select_anchors(selections.to_vec());
10039 });
10040 } else {
10041 log::error!(
10042 "No entry in selection_history found for redo. \
10043 This may correspond to a bug where undo does not update the selection. \
10044 If this is occurring, please add details to \
10045 https://github.com/zed-industries/zed/issues/22692"
10046 );
10047 }
10048 self.request_autoscroll(Autoscroll::fit(), cx);
10049 self.unmark_text(window, cx);
10050 self.refresh_inline_completion(true, false, window, cx);
10051 cx.emit(EditorEvent::Edited { transaction_id });
10052 }
10053 }
10054
10055 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10056 self.buffer
10057 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10058 }
10059
10060 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10061 self.buffer
10062 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10063 }
10064
10065 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10066 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10067 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10068 s.move_with(|map, selection| {
10069 let cursor = if selection.is_empty() {
10070 movement::left(map, selection.start)
10071 } else {
10072 selection.start
10073 };
10074 selection.collapse_to(cursor, SelectionGoal::None);
10075 });
10076 })
10077 }
10078
10079 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10081 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10082 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10083 })
10084 }
10085
10086 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10089 s.move_with(|map, selection| {
10090 let cursor = if selection.is_empty() {
10091 movement::right(map, selection.end)
10092 } else {
10093 selection.end
10094 };
10095 selection.collapse_to(cursor, SelectionGoal::None)
10096 });
10097 })
10098 }
10099
10100 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10103 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10104 })
10105 }
10106
10107 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10108 if self.take_rename(true, window, cx).is_some() {
10109 return;
10110 }
10111
10112 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10113 cx.propagate();
10114 return;
10115 }
10116
10117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10118
10119 let text_layout_details = &self.text_layout_details(window);
10120 let selection_count = self.selections.count();
10121 let first_selection = self.selections.first_anchor();
10122
10123 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10124 s.move_with(|map, selection| {
10125 if !selection.is_empty() {
10126 selection.goal = SelectionGoal::None;
10127 }
10128 let (cursor, goal) = movement::up(
10129 map,
10130 selection.start,
10131 selection.goal,
10132 false,
10133 text_layout_details,
10134 );
10135 selection.collapse_to(cursor, goal);
10136 });
10137 });
10138
10139 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10140 {
10141 cx.propagate();
10142 }
10143 }
10144
10145 pub fn move_up_by_lines(
10146 &mut self,
10147 action: &MoveUpByLines,
10148 window: &mut Window,
10149 cx: &mut Context<Self>,
10150 ) {
10151 if self.take_rename(true, window, cx).is_some() {
10152 return;
10153 }
10154
10155 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10156 cx.propagate();
10157 return;
10158 }
10159
10160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10161
10162 let text_layout_details = &self.text_layout_details(window);
10163
10164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10165 s.move_with(|map, selection| {
10166 if !selection.is_empty() {
10167 selection.goal = SelectionGoal::None;
10168 }
10169 let (cursor, goal) = movement::up_by_rows(
10170 map,
10171 selection.start,
10172 action.lines,
10173 selection.goal,
10174 false,
10175 text_layout_details,
10176 );
10177 selection.collapse_to(cursor, goal);
10178 });
10179 })
10180 }
10181
10182 pub fn move_down_by_lines(
10183 &mut self,
10184 action: &MoveDownByLines,
10185 window: &mut Window,
10186 cx: &mut Context<Self>,
10187 ) {
10188 if self.take_rename(true, window, cx).is_some() {
10189 return;
10190 }
10191
10192 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10193 cx.propagate();
10194 return;
10195 }
10196
10197 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10198
10199 let text_layout_details = &self.text_layout_details(window);
10200
10201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10202 s.move_with(|map, selection| {
10203 if !selection.is_empty() {
10204 selection.goal = SelectionGoal::None;
10205 }
10206 let (cursor, goal) = movement::down_by_rows(
10207 map,
10208 selection.start,
10209 action.lines,
10210 selection.goal,
10211 false,
10212 text_layout_details,
10213 );
10214 selection.collapse_to(cursor, goal);
10215 });
10216 })
10217 }
10218
10219 pub fn select_down_by_lines(
10220 &mut self,
10221 action: &SelectDownByLines,
10222 window: &mut Window,
10223 cx: &mut Context<Self>,
10224 ) {
10225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10226 let text_layout_details = &self.text_layout_details(window);
10227 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10228 s.move_heads_with(|map, head, goal| {
10229 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10230 })
10231 })
10232 }
10233
10234 pub fn select_up_by_lines(
10235 &mut self,
10236 action: &SelectUpByLines,
10237 window: &mut Window,
10238 cx: &mut Context<Self>,
10239 ) {
10240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10241 let text_layout_details = &self.text_layout_details(window);
10242 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10243 s.move_heads_with(|map, head, goal| {
10244 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10245 })
10246 })
10247 }
10248
10249 pub fn select_page_up(
10250 &mut self,
10251 _: &SelectPageUp,
10252 window: &mut Window,
10253 cx: &mut Context<Self>,
10254 ) {
10255 let Some(row_count) = self.visible_row_count() else {
10256 return;
10257 };
10258
10259 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10260
10261 let text_layout_details = &self.text_layout_details(window);
10262
10263 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10264 s.move_heads_with(|map, head, goal| {
10265 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10266 })
10267 })
10268 }
10269
10270 pub fn move_page_up(
10271 &mut self,
10272 action: &MovePageUp,
10273 window: &mut Window,
10274 cx: &mut Context<Self>,
10275 ) {
10276 if self.take_rename(true, window, cx).is_some() {
10277 return;
10278 }
10279
10280 if self
10281 .context_menu
10282 .borrow_mut()
10283 .as_mut()
10284 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10285 .unwrap_or(false)
10286 {
10287 return;
10288 }
10289
10290 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10291 cx.propagate();
10292 return;
10293 }
10294
10295 let Some(row_count) = self.visible_row_count() else {
10296 return;
10297 };
10298
10299 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10300
10301 let autoscroll = if action.center_cursor {
10302 Autoscroll::center()
10303 } else {
10304 Autoscroll::fit()
10305 };
10306
10307 let text_layout_details = &self.text_layout_details(window);
10308
10309 self.change_selections(Some(autoscroll), window, cx, |s| {
10310 s.move_with(|map, selection| {
10311 if !selection.is_empty() {
10312 selection.goal = SelectionGoal::None;
10313 }
10314 let (cursor, goal) = movement::up_by_rows(
10315 map,
10316 selection.end,
10317 row_count,
10318 selection.goal,
10319 false,
10320 text_layout_details,
10321 );
10322 selection.collapse_to(cursor, goal);
10323 });
10324 });
10325 }
10326
10327 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10329 let text_layout_details = &self.text_layout_details(window);
10330 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10331 s.move_heads_with(|map, head, goal| {
10332 movement::up(map, head, goal, false, text_layout_details)
10333 })
10334 })
10335 }
10336
10337 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10338 self.take_rename(true, window, cx);
10339
10340 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10341 cx.propagate();
10342 return;
10343 }
10344
10345 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10346
10347 let text_layout_details = &self.text_layout_details(window);
10348 let selection_count = self.selections.count();
10349 let first_selection = self.selections.first_anchor();
10350
10351 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10352 s.move_with(|map, selection| {
10353 if !selection.is_empty() {
10354 selection.goal = SelectionGoal::None;
10355 }
10356 let (cursor, goal) = movement::down(
10357 map,
10358 selection.end,
10359 selection.goal,
10360 false,
10361 text_layout_details,
10362 );
10363 selection.collapse_to(cursor, goal);
10364 });
10365 });
10366
10367 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10368 {
10369 cx.propagate();
10370 }
10371 }
10372
10373 pub fn select_page_down(
10374 &mut self,
10375 _: &SelectPageDown,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 let Some(row_count) = self.visible_row_count() else {
10380 return;
10381 };
10382
10383 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10384
10385 let text_layout_details = &self.text_layout_details(window);
10386
10387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10388 s.move_heads_with(|map, head, goal| {
10389 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10390 })
10391 })
10392 }
10393
10394 pub fn move_page_down(
10395 &mut self,
10396 action: &MovePageDown,
10397 window: &mut Window,
10398 cx: &mut Context<Self>,
10399 ) {
10400 if self.take_rename(true, window, cx).is_some() {
10401 return;
10402 }
10403
10404 if self
10405 .context_menu
10406 .borrow_mut()
10407 .as_mut()
10408 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10409 .unwrap_or(false)
10410 {
10411 return;
10412 }
10413
10414 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10415 cx.propagate();
10416 return;
10417 }
10418
10419 let Some(row_count) = self.visible_row_count() else {
10420 return;
10421 };
10422
10423 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10424
10425 let autoscroll = if action.center_cursor {
10426 Autoscroll::center()
10427 } else {
10428 Autoscroll::fit()
10429 };
10430
10431 let text_layout_details = &self.text_layout_details(window);
10432 self.change_selections(Some(autoscroll), window, cx, |s| {
10433 s.move_with(|map, selection| {
10434 if !selection.is_empty() {
10435 selection.goal = SelectionGoal::None;
10436 }
10437 let (cursor, goal) = movement::down_by_rows(
10438 map,
10439 selection.end,
10440 row_count,
10441 selection.goal,
10442 false,
10443 text_layout_details,
10444 );
10445 selection.collapse_to(cursor, goal);
10446 });
10447 });
10448 }
10449
10450 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10451 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10452 let text_layout_details = &self.text_layout_details(window);
10453 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10454 s.move_heads_with(|map, head, goal| {
10455 movement::down(map, head, goal, false, text_layout_details)
10456 })
10457 });
10458 }
10459
10460 pub fn context_menu_first(
10461 &mut self,
10462 _: &ContextMenuFirst,
10463 _window: &mut Window,
10464 cx: &mut Context<Self>,
10465 ) {
10466 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10467 context_menu.select_first(self.completion_provider.as_deref(), cx);
10468 }
10469 }
10470
10471 pub fn context_menu_prev(
10472 &mut self,
10473 _: &ContextMenuPrevious,
10474 _window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) {
10477 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10478 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10479 }
10480 }
10481
10482 pub fn context_menu_next(
10483 &mut self,
10484 _: &ContextMenuNext,
10485 _window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10489 context_menu.select_next(self.completion_provider.as_deref(), cx);
10490 }
10491 }
10492
10493 pub fn context_menu_last(
10494 &mut self,
10495 _: &ContextMenuLast,
10496 _window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10500 context_menu.select_last(self.completion_provider.as_deref(), cx);
10501 }
10502 }
10503
10504 pub fn move_to_previous_word_start(
10505 &mut self,
10506 _: &MoveToPreviousWordStart,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10512 s.move_cursors_with(|map, head, _| {
10513 (
10514 movement::previous_word_start(map, head),
10515 SelectionGoal::None,
10516 )
10517 });
10518 })
10519 }
10520
10521 pub fn move_to_previous_subword_start(
10522 &mut self,
10523 _: &MoveToPreviousSubwordStart,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10528 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10529 s.move_cursors_with(|map, head, _| {
10530 (
10531 movement::previous_subword_start(map, head),
10532 SelectionGoal::None,
10533 )
10534 });
10535 })
10536 }
10537
10538 pub fn select_to_previous_word_start(
10539 &mut self,
10540 _: &SelectToPreviousWordStart,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10545 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10546 s.move_heads_with(|map, head, _| {
10547 (
10548 movement::previous_word_start(map, head),
10549 SelectionGoal::None,
10550 )
10551 });
10552 })
10553 }
10554
10555 pub fn select_to_previous_subword_start(
10556 &mut self,
10557 _: &SelectToPreviousSubwordStart,
10558 window: &mut Window,
10559 cx: &mut Context<Self>,
10560 ) {
10561 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10562 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10563 s.move_heads_with(|map, head, _| {
10564 (
10565 movement::previous_subword_start(map, head),
10566 SelectionGoal::None,
10567 )
10568 });
10569 })
10570 }
10571
10572 pub fn delete_to_previous_word_start(
10573 &mut self,
10574 action: &DeleteToPreviousWordStart,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10579 self.transact(window, cx, |this, window, cx| {
10580 this.select_autoclose_pair(window, cx);
10581 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10582 s.move_with(|map, selection| {
10583 if selection.is_empty() {
10584 let cursor = if action.ignore_newlines {
10585 movement::previous_word_start(map, selection.head())
10586 } else {
10587 movement::previous_word_start_or_newline(map, selection.head())
10588 };
10589 selection.set_head(cursor, SelectionGoal::None);
10590 }
10591 });
10592 });
10593 this.insert("", window, cx);
10594 });
10595 }
10596
10597 pub fn delete_to_previous_subword_start(
10598 &mut self,
10599 _: &DeleteToPreviousSubwordStart,
10600 window: &mut Window,
10601 cx: &mut Context<Self>,
10602 ) {
10603 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10604 self.transact(window, cx, |this, window, cx| {
10605 this.select_autoclose_pair(window, cx);
10606 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10607 s.move_with(|map, selection| {
10608 if selection.is_empty() {
10609 let cursor = movement::previous_subword_start(map, selection.head());
10610 selection.set_head(cursor, SelectionGoal::None);
10611 }
10612 });
10613 });
10614 this.insert("", window, cx);
10615 });
10616 }
10617
10618 pub fn move_to_next_word_end(
10619 &mut self,
10620 _: &MoveToNextWordEnd,
10621 window: &mut Window,
10622 cx: &mut Context<Self>,
10623 ) {
10624 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10625 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10626 s.move_cursors_with(|map, head, _| {
10627 (movement::next_word_end(map, head), SelectionGoal::None)
10628 });
10629 })
10630 }
10631
10632 pub fn move_to_next_subword_end(
10633 &mut self,
10634 _: &MoveToNextSubwordEnd,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10639 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10640 s.move_cursors_with(|map, head, _| {
10641 (movement::next_subword_end(map, head), SelectionGoal::None)
10642 });
10643 })
10644 }
10645
10646 pub fn select_to_next_word_end(
10647 &mut self,
10648 _: &SelectToNextWordEnd,
10649 window: &mut Window,
10650 cx: &mut Context<Self>,
10651 ) {
10652 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10654 s.move_heads_with(|map, head, _| {
10655 (movement::next_word_end(map, head), SelectionGoal::None)
10656 });
10657 })
10658 }
10659
10660 pub fn select_to_next_subword_end(
10661 &mut self,
10662 _: &SelectToNextSubwordEnd,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10668 s.move_heads_with(|map, head, _| {
10669 (movement::next_subword_end(map, head), SelectionGoal::None)
10670 });
10671 })
10672 }
10673
10674 pub fn delete_to_next_word_end(
10675 &mut self,
10676 action: &DeleteToNextWordEnd,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 ) {
10680 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10681 self.transact(window, cx, |this, window, cx| {
10682 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10683 s.move_with(|map, selection| {
10684 if selection.is_empty() {
10685 let cursor = if action.ignore_newlines {
10686 movement::next_word_end(map, selection.head())
10687 } else {
10688 movement::next_word_end_or_newline(map, selection.head())
10689 };
10690 selection.set_head(cursor, SelectionGoal::None);
10691 }
10692 });
10693 });
10694 this.insert("", window, cx);
10695 });
10696 }
10697
10698 pub fn delete_to_next_subword_end(
10699 &mut self,
10700 _: &DeleteToNextSubwordEnd,
10701 window: &mut Window,
10702 cx: &mut Context<Self>,
10703 ) {
10704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10705 self.transact(window, cx, |this, window, cx| {
10706 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10707 s.move_with(|map, selection| {
10708 if selection.is_empty() {
10709 let cursor = movement::next_subword_end(map, selection.head());
10710 selection.set_head(cursor, SelectionGoal::None);
10711 }
10712 });
10713 });
10714 this.insert("", window, cx);
10715 });
10716 }
10717
10718 pub fn move_to_beginning_of_line(
10719 &mut self,
10720 action: &MoveToBeginningOfLine,
10721 window: &mut Window,
10722 cx: &mut Context<Self>,
10723 ) {
10724 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10725 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10726 s.move_cursors_with(|map, head, _| {
10727 (
10728 movement::indented_line_beginning(
10729 map,
10730 head,
10731 action.stop_at_soft_wraps,
10732 action.stop_at_indent,
10733 ),
10734 SelectionGoal::None,
10735 )
10736 });
10737 })
10738 }
10739
10740 pub fn select_to_beginning_of_line(
10741 &mut self,
10742 action: &SelectToBeginningOfLine,
10743 window: &mut Window,
10744 cx: &mut Context<Self>,
10745 ) {
10746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10748 s.move_heads_with(|map, head, _| {
10749 (
10750 movement::indented_line_beginning(
10751 map,
10752 head,
10753 action.stop_at_soft_wraps,
10754 action.stop_at_indent,
10755 ),
10756 SelectionGoal::None,
10757 )
10758 });
10759 });
10760 }
10761
10762 pub fn delete_to_beginning_of_line(
10763 &mut self,
10764 action: &DeleteToBeginningOfLine,
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.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10771 s.move_with(|_, selection| {
10772 selection.reversed = true;
10773 });
10774 });
10775
10776 this.select_to_beginning_of_line(
10777 &SelectToBeginningOfLine {
10778 stop_at_soft_wraps: false,
10779 stop_at_indent: action.stop_at_indent,
10780 },
10781 window,
10782 cx,
10783 );
10784 this.backspace(&Backspace, window, cx);
10785 });
10786 }
10787
10788 pub fn move_to_end_of_line(
10789 &mut self,
10790 action: &MoveToEndOfLine,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10796 s.move_cursors_with(|map, head, _| {
10797 (
10798 movement::line_end(map, head, action.stop_at_soft_wraps),
10799 SelectionGoal::None,
10800 )
10801 });
10802 })
10803 }
10804
10805 pub fn select_to_end_of_line(
10806 &mut self,
10807 action: &SelectToEndOfLine,
10808 window: &mut Window,
10809 cx: &mut Context<Self>,
10810 ) {
10811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10812 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10813 s.move_heads_with(|map, head, _| {
10814 (
10815 movement::line_end(map, head, action.stop_at_soft_wraps),
10816 SelectionGoal::None,
10817 )
10818 });
10819 })
10820 }
10821
10822 pub fn delete_to_end_of_line(
10823 &mut self,
10824 _: &DeleteToEndOfLine,
10825 window: &mut Window,
10826 cx: &mut Context<Self>,
10827 ) {
10828 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10829 self.transact(window, cx, |this, window, cx| {
10830 this.select_to_end_of_line(
10831 &SelectToEndOfLine {
10832 stop_at_soft_wraps: false,
10833 },
10834 window,
10835 cx,
10836 );
10837 this.delete(&Delete, window, cx);
10838 });
10839 }
10840
10841 pub fn cut_to_end_of_line(
10842 &mut self,
10843 _: &CutToEndOfLine,
10844 window: &mut Window,
10845 cx: &mut Context<Self>,
10846 ) {
10847 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10848 self.transact(window, cx, |this, window, cx| {
10849 this.select_to_end_of_line(
10850 &SelectToEndOfLine {
10851 stop_at_soft_wraps: false,
10852 },
10853 window,
10854 cx,
10855 );
10856 this.cut(&Cut, window, cx);
10857 });
10858 }
10859
10860 pub fn move_to_start_of_paragraph(
10861 &mut self,
10862 _: &MoveToStartOfParagraph,
10863 window: &mut Window,
10864 cx: &mut Context<Self>,
10865 ) {
10866 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10867 cx.propagate();
10868 return;
10869 }
10870 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10871 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10872 s.move_with(|map, selection| {
10873 selection.collapse_to(
10874 movement::start_of_paragraph(map, selection.head(), 1),
10875 SelectionGoal::None,
10876 )
10877 });
10878 })
10879 }
10880
10881 pub fn move_to_end_of_paragraph(
10882 &mut self,
10883 _: &MoveToEndOfParagraph,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10888 cx.propagate();
10889 return;
10890 }
10891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10893 s.move_with(|map, selection| {
10894 selection.collapse_to(
10895 movement::end_of_paragraph(map, selection.head(), 1),
10896 SelectionGoal::None,
10897 )
10898 });
10899 })
10900 }
10901
10902 pub fn select_to_start_of_paragraph(
10903 &mut self,
10904 _: &SelectToStartOfParagraph,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 ) {
10908 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10909 cx.propagate();
10910 return;
10911 }
10912 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10913 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10914 s.move_heads_with(|map, head, _| {
10915 (
10916 movement::start_of_paragraph(map, head, 1),
10917 SelectionGoal::None,
10918 )
10919 });
10920 })
10921 }
10922
10923 pub fn select_to_end_of_paragraph(
10924 &mut self,
10925 _: &SelectToEndOfParagraph,
10926 window: &mut Window,
10927 cx: &mut Context<Self>,
10928 ) {
10929 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10930 cx.propagate();
10931 return;
10932 }
10933 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10935 s.move_heads_with(|map, head, _| {
10936 (
10937 movement::end_of_paragraph(map, head, 1),
10938 SelectionGoal::None,
10939 )
10940 });
10941 })
10942 }
10943
10944 pub fn move_to_start_of_excerpt(
10945 &mut self,
10946 _: &MoveToStartOfExcerpt,
10947 window: &mut Window,
10948 cx: &mut Context<Self>,
10949 ) {
10950 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10951 cx.propagate();
10952 return;
10953 }
10954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10956 s.move_with(|map, selection| {
10957 selection.collapse_to(
10958 movement::start_of_excerpt(
10959 map,
10960 selection.head(),
10961 workspace::searchable::Direction::Prev,
10962 ),
10963 SelectionGoal::None,
10964 )
10965 });
10966 })
10967 }
10968
10969 pub fn move_to_start_of_next_excerpt(
10970 &mut self,
10971 _: &MoveToStartOfNextExcerpt,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10976 cx.propagate();
10977 return;
10978 }
10979
10980 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10981 s.move_with(|map, selection| {
10982 selection.collapse_to(
10983 movement::start_of_excerpt(
10984 map,
10985 selection.head(),
10986 workspace::searchable::Direction::Next,
10987 ),
10988 SelectionGoal::None,
10989 )
10990 });
10991 })
10992 }
10993
10994 pub fn move_to_end_of_excerpt(
10995 &mut self,
10996 _: &MoveToEndOfExcerpt,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 ) {
11000 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11001 cx.propagate();
11002 return;
11003 }
11004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11006 s.move_with(|map, selection| {
11007 selection.collapse_to(
11008 movement::end_of_excerpt(
11009 map,
11010 selection.head(),
11011 workspace::searchable::Direction::Next,
11012 ),
11013 SelectionGoal::None,
11014 )
11015 });
11016 })
11017 }
11018
11019 pub fn move_to_end_of_previous_excerpt(
11020 &mut self,
11021 _: &MoveToEndOfPreviousExcerpt,
11022 window: &mut Window,
11023 cx: &mut Context<Self>,
11024 ) {
11025 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11026 cx.propagate();
11027 return;
11028 }
11029 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11031 s.move_with(|map, selection| {
11032 selection.collapse_to(
11033 movement::end_of_excerpt(
11034 map,
11035 selection.head(),
11036 workspace::searchable::Direction::Prev,
11037 ),
11038 SelectionGoal::None,
11039 )
11040 });
11041 })
11042 }
11043
11044 pub fn select_to_start_of_excerpt(
11045 &mut self,
11046 _: &SelectToStartOfExcerpt,
11047 window: &mut Window,
11048 cx: &mut Context<Self>,
11049 ) {
11050 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11051 cx.propagate();
11052 return;
11053 }
11054 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11056 s.move_heads_with(|map, head, _| {
11057 (
11058 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11059 SelectionGoal::None,
11060 )
11061 });
11062 })
11063 }
11064
11065 pub fn select_to_start_of_next_excerpt(
11066 &mut self,
11067 _: &SelectToStartOfNextExcerpt,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11072 cx.propagate();
11073 return;
11074 }
11075 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11076 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11077 s.move_heads_with(|map, head, _| {
11078 (
11079 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11080 SelectionGoal::None,
11081 )
11082 });
11083 })
11084 }
11085
11086 pub fn select_to_end_of_excerpt(
11087 &mut self,
11088 _: &SelectToEndOfExcerpt,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11093 cx.propagate();
11094 return;
11095 }
11096 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11097 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11098 s.move_heads_with(|map, head, _| {
11099 (
11100 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11101 SelectionGoal::None,
11102 )
11103 });
11104 })
11105 }
11106
11107 pub fn select_to_end_of_previous_excerpt(
11108 &mut self,
11109 _: &SelectToEndOfPreviousExcerpt,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) {
11113 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11114 cx.propagate();
11115 return;
11116 }
11117 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11119 s.move_heads_with(|map, head, _| {
11120 (
11121 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11122 SelectionGoal::None,
11123 )
11124 });
11125 })
11126 }
11127
11128 pub fn move_to_beginning(
11129 &mut self,
11130 _: &MoveToBeginning,
11131 window: &mut Window,
11132 cx: &mut Context<Self>,
11133 ) {
11134 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11135 cx.propagate();
11136 return;
11137 }
11138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11139 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11140 s.select_ranges(vec![0..0]);
11141 });
11142 }
11143
11144 pub fn select_to_beginning(
11145 &mut self,
11146 _: &SelectToBeginning,
11147 window: &mut Window,
11148 cx: &mut Context<Self>,
11149 ) {
11150 let mut selection = self.selections.last::<Point>(cx);
11151 selection.set_head(Point::zero(), SelectionGoal::None);
11152 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11153 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11154 s.select(vec![selection]);
11155 });
11156 }
11157
11158 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11159 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11160 cx.propagate();
11161 return;
11162 }
11163 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11164 let cursor = self.buffer.read(cx).read(cx).len();
11165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11166 s.select_ranges(vec![cursor..cursor])
11167 });
11168 }
11169
11170 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11171 self.nav_history = nav_history;
11172 }
11173
11174 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11175 self.nav_history.as_ref()
11176 }
11177
11178 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11179 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11180 }
11181
11182 fn push_to_nav_history(
11183 &mut self,
11184 cursor_anchor: Anchor,
11185 new_position: Option<Point>,
11186 is_deactivate: bool,
11187 cx: &mut Context<Self>,
11188 ) {
11189 if let Some(nav_history) = self.nav_history.as_mut() {
11190 let buffer = self.buffer.read(cx).read(cx);
11191 let cursor_position = cursor_anchor.to_point(&buffer);
11192 let scroll_state = self.scroll_manager.anchor();
11193 let scroll_top_row = scroll_state.top_row(&buffer);
11194 drop(buffer);
11195
11196 if let Some(new_position) = new_position {
11197 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11198 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11199 return;
11200 }
11201 }
11202
11203 nav_history.push(
11204 Some(NavigationData {
11205 cursor_anchor,
11206 cursor_position,
11207 scroll_anchor: scroll_state,
11208 scroll_top_row,
11209 }),
11210 cx,
11211 );
11212 cx.emit(EditorEvent::PushedToNavHistory {
11213 anchor: cursor_anchor,
11214 is_deactivate,
11215 })
11216 }
11217 }
11218
11219 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11220 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11221 let buffer = self.buffer.read(cx).snapshot(cx);
11222 let mut selection = self.selections.first::<usize>(cx);
11223 selection.set_head(buffer.len(), SelectionGoal::None);
11224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11225 s.select(vec![selection]);
11226 });
11227 }
11228
11229 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11230 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11231 let end = self.buffer.read(cx).read(cx).len();
11232 self.change_selections(None, window, cx, |s| {
11233 s.select_ranges(vec![0..end]);
11234 });
11235 }
11236
11237 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11238 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11240 let mut selections = self.selections.all::<Point>(cx);
11241 let max_point = display_map.buffer_snapshot.max_point();
11242 for selection in &mut selections {
11243 let rows = selection.spanned_rows(true, &display_map);
11244 selection.start = Point::new(rows.start.0, 0);
11245 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11246 selection.reversed = false;
11247 }
11248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11249 s.select(selections);
11250 });
11251 }
11252
11253 pub fn split_selection_into_lines(
11254 &mut self,
11255 _: &SplitSelectionIntoLines,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 ) {
11259 let selections = self
11260 .selections
11261 .all::<Point>(cx)
11262 .into_iter()
11263 .map(|selection| selection.start..selection.end)
11264 .collect::<Vec<_>>();
11265 self.unfold_ranges(&selections, true, true, cx);
11266
11267 let mut new_selection_ranges = Vec::new();
11268 {
11269 let buffer = self.buffer.read(cx).read(cx);
11270 for selection in selections {
11271 for row in selection.start.row..selection.end.row {
11272 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11273 new_selection_ranges.push(cursor..cursor);
11274 }
11275
11276 let is_multiline_selection = selection.start.row != selection.end.row;
11277 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11278 // so this action feels more ergonomic when paired with other selection operations
11279 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11280 if !should_skip_last {
11281 new_selection_ranges.push(selection.end..selection.end);
11282 }
11283 }
11284 }
11285 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11286 s.select_ranges(new_selection_ranges);
11287 });
11288 }
11289
11290 pub fn add_selection_above(
11291 &mut self,
11292 _: &AddSelectionAbove,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) {
11296 self.add_selection(true, window, cx);
11297 }
11298
11299 pub fn add_selection_below(
11300 &mut self,
11301 _: &AddSelectionBelow,
11302 window: &mut Window,
11303 cx: &mut Context<Self>,
11304 ) {
11305 self.add_selection(false, window, cx);
11306 }
11307
11308 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11309 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11310
11311 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11312 let mut selections = self.selections.all::<Point>(cx);
11313 let text_layout_details = self.text_layout_details(window);
11314 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11315 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11316 let range = oldest_selection.display_range(&display_map).sorted();
11317
11318 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11319 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11320 let positions = start_x.min(end_x)..start_x.max(end_x);
11321
11322 selections.clear();
11323 let mut stack = Vec::new();
11324 for row in range.start.row().0..=range.end.row().0 {
11325 if let Some(selection) = self.selections.build_columnar_selection(
11326 &display_map,
11327 DisplayRow(row),
11328 &positions,
11329 oldest_selection.reversed,
11330 &text_layout_details,
11331 ) {
11332 stack.push(selection.id);
11333 selections.push(selection);
11334 }
11335 }
11336
11337 if above {
11338 stack.reverse();
11339 }
11340
11341 AddSelectionsState { above, stack }
11342 });
11343
11344 let last_added_selection = *state.stack.last().unwrap();
11345 let mut new_selections = Vec::new();
11346 if above == state.above {
11347 let end_row = if above {
11348 DisplayRow(0)
11349 } else {
11350 display_map.max_point().row()
11351 };
11352
11353 'outer: for selection in selections {
11354 if selection.id == last_added_selection {
11355 let range = selection.display_range(&display_map).sorted();
11356 debug_assert_eq!(range.start.row(), range.end.row());
11357 let mut row = range.start.row();
11358 let positions =
11359 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11360 px(start)..px(end)
11361 } else {
11362 let start_x =
11363 display_map.x_for_display_point(range.start, &text_layout_details);
11364 let end_x =
11365 display_map.x_for_display_point(range.end, &text_layout_details);
11366 start_x.min(end_x)..start_x.max(end_x)
11367 };
11368
11369 while row != end_row {
11370 if above {
11371 row.0 -= 1;
11372 } else {
11373 row.0 += 1;
11374 }
11375
11376 if let Some(new_selection) = self.selections.build_columnar_selection(
11377 &display_map,
11378 row,
11379 &positions,
11380 selection.reversed,
11381 &text_layout_details,
11382 ) {
11383 state.stack.push(new_selection.id);
11384 if above {
11385 new_selections.push(new_selection);
11386 new_selections.push(selection);
11387 } else {
11388 new_selections.push(selection);
11389 new_selections.push(new_selection);
11390 }
11391
11392 continue 'outer;
11393 }
11394 }
11395 }
11396
11397 new_selections.push(selection);
11398 }
11399 } else {
11400 new_selections = selections;
11401 new_selections.retain(|s| s.id != last_added_selection);
11402 state.stack.pop();
11403 }
11404
11405 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11406 s.select(new_selections);
11407 });
11408 if state.stack.len() > 1 {
11409 self.add_selections_state = Some(state);
11410 }
11411 }
11412
11413 pub fn select_next_match_internal(
11414 &mut self,
11415 display_map: &DisplaySnapshot,
11416 replace_newest: bool,
11417 autoscroll: Option<Autoscroll>,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) -> Result<()> {
11421 fn select_next_match_ranges(
11422 this: &mut Editor,
11423 range: Range<usize>,
11424 replace_newest: bool,
11425 auto_scroll: Option<Autoscroll>,
11426 window: &mut Window,
11427 cx: &mut Context<Editor>,
11428 ) {
11429 this.unfold_ranges(&[range.clone()], false, true, cx);
11430 this.change_selections(auto_scroll, window, cx, |s| {
11431 if replace_newest {
11432 s.delete(s.newest_anchor().id);
11433 }
11434 s.insert_range(range.clone());
11435 });
11436 }
11437
11438 let buffer = &display_map.buffer_snapshot;
11439 let mut selections = self.selections.all::<usize>(cx);
11440 if let Some(mut select_next_state) = self.select_next_state.take() {
11441 let query = &select_next_state.query;
11442 if !select_next_state.done {
11443 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11444 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11445 let mut next_selected_range = None;
11446
11447 let bytes_after_last_selection =
11448 buffer.bytes_in_range(last_selection.end..buffer.len());
11449 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11450 let query_matches = query
11451 .stream_find_iter(bytes_after_last_selection)
11452 .map(|result| (last_selection.end, result))
11453 .chain(
11454 query
11455 .stream_find_iter(bytes_before_first_selection)
11456 .map(|result| (0, result)),
11457 );
11458
11459 for (start_offset, query_match) in query_matches {
11460 let query_match = query_match.unwrap(); // can only fail due to I/O
11461 let offset_range =
11462 start_offset + query_match.start()..start_offset + query_match.end();
11463 let display_range = offset_range.start.to_display_point(display_map)
11464 ..offset_range.end.to_display_point(display_map);
11465
11466 if !select_next_state.wordwise
11467 || (!movement::is_inside_word(display_map, display_range.start)
11468 && !movement::is_inside_word(display_map, display_range.end))
11469 {
11470 // TODO: This is n^2, because we might check all the selections
11471 if !selections
11472 .iter()
11473 .any(|selection| selection.range().overlaps(&offset_range))
11474 {
11475 next_selected_range = Some(offset_range);
11476 break;
11477 }
11478 }
11479 }
11480
11481 if let Some(next_selected_range) = next_selected_range {
11482 select_next_match_ranges(
11483 self,
11484 next_selected_range,
11485 replace_newest,
11486 autoscroll,
11487 window,
11488 cx,
11489 );
11490 } else {
11491 select_next_state.done = true;
11492 }
11493 }
11494
11495 self.select_next_state = Some(select_next_state);
11496 } else {
11497 let mut only_carets = true;
11498 let mut same_text_selected = true;
11499 let mut selected_text = None;
11500
11501 let mut selections_iter = selections.iter().peekable();
11502 while let Some(selection) = selections_iter.next() {
11503 if selection.start != selection.end {
11504 only_carets = false;
11505 }
11506
11507 if same_text_selected {
11508 if selected_text.is_none() {
11509 selected_text =
11510 Some(buffer.text_for_range(selection.range()).collect::<String>());
11511 }
11512
11513 if let Some(next_selection) = selections_iter.peek() {
11514 if next_selection.range().len() == selection.range().len() {
11515 let next_selected_text = buffer
11516 .text_for_range(next_selection.range())
11517 .collect::<String>();
11518 if Some(next_selected_text) != selected_text {
11519 same_text_selected = false;
11520 selected_text = None;
11521 }
11522 } else {
11523 same_text_selected = false;
11524 selected_text = None;
11525 }
11526 }
11527 }
11528 }
11529
11530 if only_carets {
11531 for selection in &mut selections {
11532 let word_range = movement::surrounding_word(
11533 display_map,
11534 selection.start.to_display_point(display_map),
11535 );
11536 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11537 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11538 selection.goal = SelectionGoal::None;
11539 selection.reversed = false;
11540 select_next_match_ranges(
11541 self,
11542 selection.start..selection.end,
11543 replace_newest,
11544 autoscroll,
11545 window,
11546 cx,
11547 );
11548 }
11549
11550 if selections.len() == 1 {
11551 let selection = selections
11552 .last()
11553 .expect("ensured that there's only one selection");
11554 let query = buffer
11555 .text_for_range(selection.start..selection.end)
11556 .collect::<String>();
11557 let is_empty = query.is_empty();
11558 let select_state = SelectNextState {
11559 query: AhoCorasick::new(&[query])?,
11560 wordwise: true,
11561 done: is_empty,
11562 };
11563 self.select_next_state = Some(select_state);
11564 } else {
11565 self.select_next_state = None;
11566 }
11567 } else if let Some(selected_text) = selected_text {
11568 self.select_next_state = Some(SelectNextState {
11569 query: AhoCorasick::new(&[selected_text])?,
11570 wordwise: false,
11571 done: false,
11572 });
11573 self.select_next_match_internal(
11574 display_map,
11575 replace_newest,
11576 autoscroll,
11577 window,
11578 cx,
11579 )?;
11580 }
11581 }
11582 Ok(())
11583 }
11584
11585 pub fn select_all_matches(
11586 &mut self,
11587 _action: &SelectAllMatches,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) -> Result<()> {
11591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11592
11593 self.push_to_selection_history();
11594 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11595
11596 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11597 let Some(select_next_state) = self.select_next_state.as_mut() else {
11598 return Ok(());
11599 };
11600 if select_next_state.done {
11601 return Ok(());
11602 }
11603
11604 let mut new_selections = self.selections.all::<usize>(cx);
11605
11606 let buffer = &display_map.buffer_snapshot;
11607 let query_matches = select_next_state
11608 .query
11609 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11610
11611 for query_match in query_matches {
11612 let query_match = query_match.unwrap(); // can only fail due to I/O
11613 let offset_range = query_match.start()..query_match.end();
11614 let display_range = offset_range.start.to_display_point(&display_map)
11615 ..offset_range.end.to_display_point(&display_map);
11616
11617 if !select_next_state.wordwise
11618 || (!movement::is_inside_word(&display_map, display_range.start)
11619 && !movement::is_inside_word(&display_map, display_range.end))
11620 {
11621 self.selections.change_with(cx, |selections| {
11622 new_selections.push(Selection {
11623 id: selections.new_selection_id(),
11624 start: offset_range.start,
11625 end: offset_range.end,
11626 reversed: false,
11627 goal: SelectionGoal::None,
11628 });
11629 });
11630 }
11631 }
11632
11633 new_selections.sort_by_key(|selection| selection.start);
11634 let mut ix = 0;
11635 while ix + 1 < new_selections.len() {
11636 let current_selection = &new_selections[ix];
11637 let next_selection = &new_selections[ix + 1];
11638 if current_selection.range().overlaps(&next_selection.range()) {
11639 if current_selection.id < next_selection.id {
11640 new_selections.remove(ix + 1);
11641 } else {
11642 new_selections.remove(ix);
11643 }
11644 } else {
11645 ix += 1;
11646 }
11647 }
11648
11649 let reversed = self.selections.oldest::<usize>(cx).reversed;
11650
11651 for selection in new_selections.iter_mut() {
11652 selection.reversed = reversed;
11653 }
11654
11655 select_next_state.done = true;
11656 self.unfold_ranges(
11657 &new_selections
11658 .iter()
11659 .map(|selection| selection.range())
11660 .collect::<Vec<_>>(),
11661 false,
11662 false,
11663 cx,
11664 );
11665 self.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
11666 selections.select(new_selections)
11667 });
11668
11669 Ok(())
11670 }
11671
11672 pub fn select_next(
11673 &mut self,
11674 action: &SelectNext,
11675 window: &mut Window,
11676 cx: &mut Context<Self>,
11677 ) -> Result<()> {
11678 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11679 self.push_to_selection_history();
11680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11681 self.select_next_match_internal(
11682 &display_map,
11683 action.replace_newest,
11684 Some(Autoscroll::newest()),
11685 window,
11686 cx,
11687 )?;
11688 Ok(())
11689 }
11690
11691 pub fn select_previous(
11692 &mut self,
11693 action: &SelectPrevious,
11694 window: &mut Window,
11695 cx: &mut Context<Self>,
11696 ) -> Result<()> {
11697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11698 self.push_to_selection_history();
11699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11700 let buffer = &display_map.buffer_snapshot;
11701 let mut selections = self.selections.all::<usize>(cx);
11702 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11703 let query = &select_prev_state.query;
11704 if !select_prev_state.done {
11705 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11706 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11707 let mut next_selected_range = None;
11708 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11709 let bytes_before_last_selection =
11710 buffer.reversed_bytes_in_range(0..last_selection.start);
11711 let bytes_after_first_selection =
11712 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11713 let query_matches = query
11714 .stream_find_iter(bytes_before_last_selection)
11715 .map(|result| (last_selection.start, result))
11716 .chain(
11717 query
11718 .stream_find_iter(bytes_after_first_selection)
11719 .map(|result| (buffer.len(), result)),
11720 );
11721 for (end_offset, query_match) in query_matches {
11722 let query_match = query_match.unwrap(); // can only fail due to I/O
11723 let offset_range =
11724 end_offset - query_match.end()..end_offset - query_match.start();
11725 let display_range = offset_range.start.to_display_point(&display_map)
11726 ..offset_range.end.to_display_point(&display_map);
11727
11728 if !select_prev_state.wordwise
11729 || (!movement::is_inside_word(&display_map, display_range.start)
11730 && !movement::is_inside_word(&display_map, display_range.end))
11731 {
11732 next_selected_range = Some(offset_range);
11733 break;
11734 }
11735 }
11736
11737 if let Some(next_selected_range) = next_selected_range {
11738 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11739 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11740 if action.replace_newest {
11741 s.delete(s.newest_anchor().id);
11742 }
11743 s.insert_range(next_selected_range);
11744 });
11745 } else {
11746 select_prev_state.done = true;
11747 }
11748 }
11749
11750 self.select_prev_state = Some(select_prev_state);
11751 } else {
11752 let mut only_carets = true;
11753 let mut same_text_selected = true;
11754 let mut selected_text = None;
11755
11756 let mut selections_iter = selections.iter().peekable();
11757 while let Some(selection) = selections_iter.next() {
11758 if selection.start != selection.end {
11759 only_carets = false;
11760 }
11761
11762 if same_text_selected {
11763 if selected_text.is_none() {
11764 selected_text =
11765 Some(buffer.text_for_range(selection.range()).collect::<String>());
11766 }
11767
11768 if let Some(next_selection) = selections_iter.peek() {
11769 if next_selection.range().len() == selection.range().len() {
11770 let next_selected_text = buffer
11771 .text_for_range(next_selection.range())
11772 .collect::<String>();
11773 if Some(next_selected_text) != selected_text {
11774 same_text_selected = false;
11775 selected_text = None;
11776 }
11777 } else {
11778 same_text_selected = false;
11779 selected_text = None;
11780 }
11781 }
11782 }
11783 }
11784
11785 if only_carets {
11786 for selection in &mut selections {
11787 let word_range = movement::surrounding_word(
11788 &display_map,
11789 selection.start.to_display_point(&display_map),
11790 );
11791 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
11792 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
11793 selection.goal = SelectionGoal::None;
11794 selection.reversed = false;
11795 }
11796 if selections.len() == 1 {
11797 let selection = selections
11798 .last()
11799 .expect("ensured that there's only one selection");
11800 let query = buffer
11801 .text_for_range(selection.start..selection.end)
11802 .collect::<String>();
11803 let is_empty = query.is_empty();
11804 let select_state = SelectNextState {
11805 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
11806 wordwise: true,
11807 done: is_empty,
11808 };
11809 self.select_prev_state = Some(select_state);
11810 } else {
11811 self.select_prev_state = None;
11812 }
11813
11814 self.unfold_ranges(
11815 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
11816 false,
11817 true,
11818 cx,
11819 );
11820 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11821 s.select(selections);
11822 });
11823 } else if let Some(selected_text) = selected_text {
11824 self.select_prev_state = Some(SelectNextState {
11825 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
11826 wordwise: false,
11827 done: false,
11828 });
11829 self.select_previous(action, window, cx)?;
11830 }
11831 }
11832 Ok(())
11833 }
11834
11835 pub fn toggle_comments(
11836 &mut self,
11837 action: &ToggleComments,
11838 window: &mut Window,
11839 cx: &mut Context<Self>,
11840 ) {
11841 if self.read_only(cx) {
11842 return;
11843 }
11844 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11845 let text_layout_details = &self.text_layout_details(window);
11846 self.transact(window, cx, |this, window, cx| {
11847 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
11848 let mut edits = Vec::new();
11849 let mut selection_edit_ranges = Vec::new();
11850 let mut last_toggled_row = None;
11851 let snapshot = this.buffer.read(cx).read(cx);
11852 let empty_str: Arc<str> = Arc::default();
11853 let mut suffixes_inserted = Vec::new();
11854 let ignore_indent = action.ignore_indent;
11855
11856 fn comment_prefix_range(
11857 snapshot: &MultiBufferSnapshot,
11858 row: MultiBufferRow,
11859 comment_prefix: &str,
11860 comment_prefix_whitespace: &str,
11861 ignore_indent: bool,
11862 ) -> Range<Point> {
11863 let indent_size = if ignore_indent {
11864 0
11865 } else {
11866 snapshot.indent_size_for_line(row).len
11867 };
11868
11869 let start = Point::new(row.0, indent_size);
11870
11871 let mut line_bytes = snapshot
11872 .bytes_in_range(start..snapshot.max_point())
11873 .flatten()
11874 .copied();
11875
11876 // If this line currently begins with the line comment prefix, then record
11877 // the range containing the prefix.
11878 if line_bytes
11879 .by_ref()
11880 .take(comment_prefix.len())
11881 .eq(comment_prefix.bytes())
11882 {
11883 // Include any whitespace that matches the comment prefix.
11884 let matching_whitespace_len = line_bytes
11885 .zip(comment_prefix_whitespace.bytes())
11886 .take_while(|(a, b)| a == b)
11887 .count() as u32;
11888 let end = Point::new(
11889 start.row,
11890 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
11891 );
11892 start..end
11893 } else {
11894 start..start
11895 }
11896 }
11897
11898 fn comment_suffix_range(
11899 snapshot: &MultiBufferSnapshot,
11900 row: MultiBufferRow,
11901 comment_suffix: &str,
11902 comment_suffix_has_leading_space: bool,
11903 ) -> Range<Point> {
11904 let end = Point::new(row.0, snapshot.line_len(row));
11905 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
11906
11907 let mut line_end_bytes = snapshot
11908 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
11909 .flatten()
11910 .copied();
11911
11912 let leading_space_len = if suffix_start_column > 0
11913 && line_end_bytes.next() == Some(b' ')
11914 && comment_suffix_has_leading_space
11915 {
11916 1
11917 } else {
11918 0
11919 };
11920
11921 // If this line currently begins with the line comment prefix, then record
11922 // the range containing the prefix.
11923 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
11924 let start = Point::new(end.row, suffix_start_column - leading_space_len);
11925 start..end
11926 } else {
11927 end..end
11928 }
11929 }
11930
11931 // TODO: Handle selections that cross excerpts
11932 for selection in &mut selections {
11933 let start_column = snapshot
11934 .indent_size_for_line(MultiBufferRow(selection.start.row))
11935 .len;
11936 let language = if let Some(language) =
11937 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
11938 {
11939 language
11940 } else {
11941 continue;
11942 };
11943
11944 selection_edit_ranges.clear();
11945
11946 // If multiple selections contain a given row, avoid processing that
11947 // row more than once.
11948 let mut start_row = MultiBufferRow(selection.start.row);
11949 if last_toggled_row == Some(start_row) {
11950 start_row = start_row.next_row();
11951 }
11952 let end_row =
11953 if selection.end.row > selection.start.row && selection.end.column == 0 {
11954 MultiBufferRow(selection.end.row - 1)
11955 } else {
11956 MultiBufferRow(selection.end.row)
11957 };
11958 last_toggled_row = Some(end_row);
11959
11960 if start_row > end_row {
11961 continue;
11962 }
11963
11964 // If the language has line comments, toggle those.
11965 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
11966
11967 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
11968 if ignore_indent {
11969 full_comment_prefixes = full_comment_prefixes
11970 .into_iter()
11971 .map(|s| Arc::from(s.trim_end()))
11972 .collect();
11973 }
11974
11975 if !full_comment_prefixes.is_empty() {
11976 let first_prefix = full_comment_prefixes
11977 .first()
11978 .expect("prefixes is non-empty");
11979 let prefix_trimmed_lengths = full_comment_prefixes
11980 .iter()
11981 .map(|p| p.trim_end_matches(' ').len())
11982 .collect::<SmallVec<[usize; 4]>>();
11983
11984 let mut all_selection_lines_are_comments = true;
11985
11986 for row in start_row.0..=end_row.0 {
11987 let row = MultiBufferRow(row);
11988 if start_row < end_row && snapshot.is_line_blank(row) {
11989 continue;
11990 }
11991
11992 let prefix_range = full_comment_prefixes
11993 .iter()
11994 .zip(prefix_trimmed_lengths.iter().copied())
11995 .map(|(prefix, trimmed_prefix_len)| {
11996 comment_prefix_range(
11997 snapshot.deref(),
11998 row,
11999 &prefix[..trimmed_prefix_len],
12000 &prefix[trimmed_prefix_len..],
12001 ignore_indent,
12002 )
12003 })
12004 .max_by_key(|range| range.end.column - range.start.column)
12005 .expect("prefixes is non-empty");
12006
12007 if prefix_range.is_empty() {
12008 all_selection_lines_are_comments = false;
12009 }
12010
12011 selection_edit_ranges.push(prefix_range);
12012 }
12013
12014 if all_selection_lines_are_comments {
12015 edits.extend(
12016 selection_edit_ranges
12017 .iter()
12018 .cloned()
12019 .map(|range| (range, empty_str.clone())),
12020 );
12021 } else {
12022 let min_column = selection_edit_ranges
12023 .iter()
12024 .map(|range| range.start.column)
12025 .min()
12026 .unwrap_or(0);
12027 edits.extend(selection_edit_ranges.iter().map(|range| {
12028 let position = Point::new(range.start.row, min_column);
12029 (position..position, first_prefix.clone())
12030 }));
12031 }
12032 } else if let Some((full_comment_prefix, comment_suffix)) =
12033 language.block_comment_delimiters()
12034 {
12035 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12036 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12037 let prefix_range = comment_prefix_range(
12038 snapshot.deref(),
12039 start_row,
12040 comment_prefix,
12041 comment_prefix_whitespace,
12042 ignore_indent,
12043 );
12044 let suffix_range = comment_suffix_range(
12045 snapshot.deref(),
12046 end_row,
12047 comment_suffix.trim_start_matches(' '),
12048 comment_suffix.starts_with(' '),
12049 );
12050
12051 if prefix_range.is_empty() || suffix_range.is_empty() {
12052 edits.push((
12053 prefix_range.start..prefix_range.start,
12054 full_comment_prefix.clone(),
12055 ));
12056 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12057 suffixes_inserted.push((end_row, comment_suffix.len()));
12058 } else {
12059 edits.push((prefix_range, empty_str.clone()));
12060 edits.push((suffix_range, empty_str.clone()));
12061 }
12062 } else {
12063 continue;
12064 }
12065 }
12066
12067 drop(snapshot);
12068 this.buffer.update(cx, |buffer, cx| {
12069 buffer.edit(edits, None, cx);
12070 });
12071
12072 // Adjust selections so that they end before any comment suffixes that
12073 // were inserted.
12074 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12075 let mut selections = this.selections.all::<Point>(cx);
12076 let snapshot = this.buffer.read(cx).read(cx);
12077 for selection in &mut selections {
12078 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12079 match row.cmp(&MultiBufferRow(selection.end.row)) {
12080 Ordering::Less => {
12081 suffixes_inserted.next();
12082 continue;
12083 }
12084 Ordering::Greater => break,
12085 Ordering::Equal => {
12086 if selection.end.column == snapshot.line_len(row) {
12087 if selection.is_empty() {
12088 selection.start.column -= suffix_len as u32;
12089 }
12090 selection.end.column -= suffix_len as u32;
12091 }
12092 break;
12093 }
12094 }
12095 }
12096 }
12097
12098 drop(snapshot);
12099 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12100 s.select(selections)
12101 });
12102
12103 let selections = this.selections.all::<Point>(cx);
12104 let selections_on_single_row = selections.windows(2).all(|selections| {
12105 selections[0].start.row == selections[1].start.row
12106 && selections[0].end.row == selections[1].end.row
12107 && selections[0].start.row == selections[0].end.row
12108 });
12109 let selections_selecting = selections
12110 .iter()
12111 .any(|selection| selection.start != selection.end);
12112 let advance_downwards = action.advance_downwards
12113 && selections_on_single_row
12114 && !selections_selecting
12115 && !matches!(this.mode, EditorMode::SingleLine { .. });
12116
12117 if advance_downwards {
12118 let snapshot = this.buffer.read(cx).snapshot(cx);
12119
12120 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12121 s.move_cursors_with(|display_snapshot, display_point, _| {
12122 let mut point = display_point.to_point(display_snapshot);
12123 point.row += 1;
12124 point = snapshot.clip_point(point, Bias::Left);
12125 let display_point = point.to_display_point(display_snapshot);
12126 let goal = SelectionGoal::HorizontalPosition(
12127 display_snapshot
12128 .x_for_display_point(display_point, text_layout_details)
12129 .into(),
12130 );
12131 (display_point, goal)
12132 })
12133 });
12134 }
12135 });
12136 }
12137
12138 pub fn select_enclosing_symbol(
12139 &mut self,
12140 _: &SelectEnclosingSymbol,
12141 window: &mut Window,
12142 cx: &mut Context<Self>,
12143 ) {
12144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12145
12146 let buffer = self.buffer.read(cx).snapshot(cx);
12147 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12148
12149 fn update_selection(
12150 selection: &Selection<usize>,
12151 buffer_snap: &MultiBufferSnapshot,
12152 ) -> Option<Selection<usize>> {
12153 let cursor = selection.head();
12154 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12155 for symbol in symbols.iter().rev() {
12156 let start = symbol.range.start.to_offset(buffer_snap);
12157 let end = symbol.range.end.to_offset(buffer_snap);
12158 let new_range = start..end;
12159 if start < selection.start || end > selection.end {
12160 return Some(Selection {
12161 id: selection.id,
12162 start: new_range.start,
12163 end: new_range.end,
12164 goal: SelectionGoal::None,
12165 reversed: selection.reversed,
12166 });
12167 }
12168 }
12169 None
12170 }
12171
12172 let mut selected_larger_symbol = false;
12173 let new_selections = old_selections
12174 .iter()
12175 .map(|selection| match update_selection(selection, &buffer) {
12176 Some(new_selection) => {
12177 if new_selection.range() != selection.range() {
12178 selected_larger_symbol = true;
12179 }
12180 new_selection
12181 }
12182 None => selection.clone(),
12183 })
12184 .collect::<Vec<_>>();
12185
12186 if selected_larger_symbol {
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.select(new_selections);
12189 });
12190 }
12191 }
12192
12193 pub fn select_larger_syntax_node(
12194 &mut self,
12195 _: &SelectLargerSyntaxNode,
12196 window: &mut Window,
12197 cx: &mut Context<Self>,
12198 ) {
12199 let Some(visible_row_count) = self.visible_row_count() else {
12200 return;
12201 };
12202 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12203 if old_selections.is_empty() {
12204 return;
12205 }
12206
12207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12208
12209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12210 let buffer = self.buffer.read(cx).snapshot(cx);
12211
12212 let mut selected_larger_node = false;
12213 let mut new_selections = old_selections
12214 .iter()
12215 .map(|selection| {
12216 let old_range = selection.start..selection.end;
12217 let mut new_range = old_range.clone();
12218 let mut new_node = None;
12219 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12220 {
12221 new_node = Some(node);
12222 new_range = match containing_range {
12223 MultiOrSingleBufferOffsetRange::Single(_) => break,
12224 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12225 };
12226 if !display_map.intersects_fold(new_range.start)
12227 && !display_map.intersects_fold(new_range.end)
12228 {
12229 break;
12230 }
12231 }
12232
12233 if let Some(node) = new_node {
12234 // Log the ancestor, to support using this action as a way to explore TreeSitter
12235 // nodes. Parent and grandparent are also logged because this operation will not
12236 // visit nodes that have the same range as their parent.
12237 log::info!("Node: {node:?}");
12238 let parent = node.parent();
12239 log::info!("Parent: {parent:?}");
12240 let grandparent = parent.and_then(|x| x.parent());
12241 log::info!("Grandparent: {grandparent:?}");
12242 }
12243
12244 selected_larger_node |= new_range != old_range;
12245 Selection {
12246 id: selection.id,
12247 start: new_range.start,
12248 end: new_range.end,
12249 goal: SelectionGoal::None,
12250 reversed: selection.reversed,
12251 }
12252 })
12253 .collect::<Vec<_>>();
12254
12255 if !selected_larger_node {
12256 return; // don't put this call in the history
12257 }
12258
12259 // scroll based on transformation done to the last selection created by the user
12260 let (last_old, last_new) = old_selections
12261 .last()
12262 .zip(new_selections.last().cloned())
12263 .expect("old_selections isn't empty");
12264
12265 // revert selection
12266 let is_selection_reversed = {
12267 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12268 new_selections.last_mut().expect("checked above").reversed =
12269 should_newest_selection_be_reversed;
12270 should_newest_selection_be_reversed
12271 };
12272
12273 if selected_larger_node {
12274 self.select_syntax_node_history.disable_clearing = true;
12275 self.change_selections(None, window, cx, |s| {
12276 s.select(new_selections.clone());
12277 });
12278 self.select_syntax_node_history.disable_clearing = false;
12279 }
12280
12281 let start_row = last_new.start.to_display_point(&display_map).row().0;
12282 let end_row = last_new.end.to_display_point(&display_map).row().0;
12283 let selection_height = end_row - start_row + 1;
12284 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12285
12286 // if fits on screen (considering margin), keep it in the middle, else, scroll to selection head
12287 let scroll_behavior = if visible_row_count >= selection_height + scroll_margin_rows * 2 {
12288 let middle_row = (end_row + start_row) / 2;
12289 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12290 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12291 SelectSyntaxNodeScrollBehavior::CenterSelection
12292 } else if is_selection_reversed {
12293 self.scroll_cursor_top(&Default::default(), window, cx);
12294 SelectSyntaxNodeScrollBehavior::CursorTop
12295 } else {
12296 self.scroll_cursor_bottom(&Default::default(), window, cx);
12297 SelectSyntaxNodeScrollBehavior::CursorBottom
12298 };
12299
12300 self.select_syntax_node_history.push((
12301 old_selections,
12302 scroll_behavior,
12303 is_selection_reversed,
12304 ));
12305 }
12306
12307 pub fn select_smaller_syntax_node(
12308 &mut self,
12309 _: &SelectSmallerSyntaxNode,
12310 window: &mut Window,
12311 cx: &mut Context<Self>,
12312 ) {
12313 let Some(visible_row_count) = self.visible_row_count() else {
12314 return;
12315 };
12316
12317 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12318
12319 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12320 self.select_syntax_node_history.pop()
12321 {
12322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12323
12324 if let Some(selection) = selections.last_mut() {
12325 selection.reversed = is_selection_reversed;
12326 }
12327
12328 self.select_syntax_node_history.disable_clearing = true;
12329 self.change_selections(None, window, cx, |s| {
12330 s.select(selections.to_vec());
12331 });
12332 self.select_syntax_node_history.disable_clearing = false;
12333
12334 let newest = self.selections.newest::<usize>(cx);
12335 let start_row = newest.start.to_display_point(&display_map).row().0;
12336 let end_row = newest.end.to_display_point(&display_map).row().0;
12337
12338 match scroll_behavior {
12339 SelectSyntaxNodeScrollBehavior::CursorTop => {
12340 self.scroll_cursor_top(&Default::default(), window, cx);
12341 }
12342 SelectSyntaxNodeScrollBehavior::CenterSelection => {
12343 let middle_row = (end_row + start_row) / 2;
12344 let selection_center = middle_row.saturating_sub(visible_row_count / 2);
12345 // centralize the selection, not the cursor
12346 self.set_scroll_top_row(DisplayRow(selection_center), window, cx);
12347 }
12348 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12349 self.scroll_cursor_bottom(&Default::default(), window, cx);
12350 }
12351 }
12352 }
12353 }
12354
12355 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12356 if !EditorSettings::get_global(cx).gutter.runnables {
12357 self.clear_tasks();
12358 return Task::ready(());
12359 }
12360 let project = self.project.as_ref().map(Entity::downgrade);
12361 cx.spawn_in(window, async move |this, cx| {
12362 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12363 let Some(project) = project.and_then(|p| p.upgrade()) else {
12364 return;
12365 };
12366 let Ok(display_snapshot) = this.update(cx, |this, cx| {
12367 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12368 }) else {
12369 return;
12370 };
12371
12372 let hide_runnables = project
12373 .update(cx, |project, cx| {
12374 // Do not display any test indicators in non-dev server remote projects.
12375 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12376 })
12377 .unwrap_or(true);
12378 if hide_runnables {
12379 return;
12380 }
12381 let new_rows =
12382 cx.background_spawn({
12383 let snapshot = display_snapshot.clone();
12384 async move {
12385 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12386 }
12387 })
12388 .await;
12389
12390 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12391 this.update(cx, |this, _| {
12392 this.clear_tasks();
12393 for (key, value) in rows {
12394 this.insert_tasks(key, value);
12395 }
12396 })
12397 .ok();
12398 })
12399 }
12400 fn fetch_runnable_ranges(
12401 snapshot: &DisplaySnapshot,
12402 range: Range<Anchor>,
12403 ) -> Vec<language::RunnableRange> {
12404 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12405 }
12406
12407 fn runnable_rows(
12408 project: Entity<Project>,
12409 snapshot: DisplaySnapshot,
12410 runnable_ranges: Vec<RunnableRange>,
12411 mut cx: AsyncWindowContext,
12412 ) -> Vec<((BufferId, u32), RunnableTasks)> {
12413 runnable_ranges
12414 .into_iter()
12415 .filter_map(|mut runnable| {
12416 let tasks = cx
12417 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12418 .ok()?;
12419 if tasks.is_empty() {
12420 return None;
12421 }
12422
12423 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12424
12425 let row = snapshot
12426 .buffer_snapshot
12427 .buffer_line_for_row(MultiBufferRow(point.row))?
12428 .1
12429 .start
12430 .row;
12431
12432 let context_range =
12433 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12434 Some((
12435 (runnable.buffer_id, row),
12436 RunnableTasks {
12437 templates: tasks,
12438 offset: snapshot
12439 .buffer_snapshot
12440 .anchor_before(runnable.run_range.start),
12441 context_range,
12442 column: point.column,
12443 extra_variables: runnable.extra_captures,
12444 },
12445 ))
12446 })
12447 .collect()
12448 }
12449
12450 fn templates_with_tags(
12451 project: &Entity<Project>,
12452 runnable: &mut Runnable,
12453 cx: &mut App,
12454 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12455 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12456 let (worktree_id, file) = project
12457 .buffer_for_id(runnable.buffer, cx)
12458 .and_then(|buffer| buffer.read(cx).file())
12459 .map(|file| (file.worktree_id(cx), file.clone()))
12460 .unzip();
12461
12462 (
12463 project.task_store().read(cx).task_inventory().cloned(),
12464 worktree_id,
12465 file,
12466 )
12467 });
12468
12469 let tags = mem::take(&mut runnable.tags);
12470 let mut tags: Vec<_> = tags
12471 .into_iter()
12472 .flat_map(|tag| {
12473 let tag = tag.0.clone();
12474 inventory
12475 .as_ref()
12476 .into_iter()
12477 .flat_map(|inventory| {
12478 inventory.read(cx).list_tasks(
12479 file.clone(),
12480 Some(runnable.language.clone()),
12481 worktree_id,
12482 cx,
12483 )
12484 })
12485 .filter(move |(_, template)| {
12486 template.tags.iter().any(|source_tag| source_tag == &tag)
12487 })
12488 })
12489 .sorted_by_key(|(kind, _)| kind.to_owned())
12490 .collect();
12491 if let Some((leading_tag_source, _)) = tags.first() {
12492 // Strongest source wins; if we have worktree tag binding, prefer that to
12493 // global and language bindings;
12494 // if we have a global binding, prefer that to language binding.
12495 let first_mismatch = tags
12496 .iter()
12497 .position(|(tag_source, _)| tag_source != leading_tag_source);
12498 if let Some(index) = first_mismatch {
12499 tags.truncate(index);
12500 }
12501 }
12502
12503 tags
12504 }
12505
12506 pub fn move_to_enclosing_bracket(
12507 &mut self,
12508 _: &MoveToEnclosingBracket,
12509 window: &mut Window,
12510 cx: &mut Context<Self>,
12511 ) {
12512 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12514 s.move_offsets_with(|snapshot, selection| {
12515 let Some(enclosing_bracket_ranges) =
12516 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12517 else {
12518 return;
12519 };
12520
12521 let mut best_length = usize::MAX;
12522 let mut best_inside = false;
12523 let mut best_in_bracket_range = false;
12524 let mut best_destination = None;
12525 for (open, close) in enclosing_bracket_ranges {
12526 let close = close.to_inclusive();
12527 let length = close.end() - open.start;
12528 let inside = selection.start >= open.end && selection.end <= *close.start();
12529 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12530 || close.contains(&selection.head());
12531
12532 // If best is next to a bracket and current isn't, skip
12533 if !in_bracket_range && best_in_bracket_range {
12534 continue;
12535 }
12536
12537 // Prefer smaller lengths unless best is inside and current isn't
12538 if length > best_length && (best_inside || !inside) {
12539 continue;
12540 }
12541
12542 best_length = length;
12543 best_inside = inside;
12544 best_in_bracket_range = in_bracket_range;
12545 best_destination = Some(
12546 if close.contains(&selection.start) && close.contains(&selection.end) {
12547 if inside { open.end } else { open.start }
12548 } else if inside {
12549 *close.start()
12550 } else {
12551 *close.end()
12552 },
12553 );
12554 }
12555
12556 if let Some(destination) = best_destination {
12557 selection.collapse_to(destination, SelectionGoal::None);
12558 }
12559 })
12560 });
12561 }
12562
12563 pub fn undo_selection(
12564 &mut self,
12565 _: &UndoSelection,
12566 window: &mut Window,
12567 cx: &mut Context<Self>,
12568 ) {
12569 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12570 self.end_selection(window, cx);
12571 self.selection_history.mode = SelectionHistoryMode::Undoing;
12572 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12573 self.change_selections(None, window, cx, |s| {
12574 s.select_anchors(entry.selections.to_vec())
12575 });
12576 self.select_next_state = entry.select_next_state;
12577 self.select_prev_state = entry.select_prev_state;
12578 self.add_selections_state = entry.add_selections_state;
12579 self.request_autoscroll(Autoscroll::newest(), cx);
12580 }
12581 self.selection_history.mode = SelectionHistoryMode::Normal;
12582 }
12583
12584 pub fn redo_selection(
12585 &mut self,
12586 _: &RedoSelection,
12587 window: &mut Window,
12588 cx: &mut Context<Self>,
12589 ) {
12590 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12591 self.end_selection(window, cx);
12592 self.selection_history.mode = SelectionHistoryMode::Redoing;
12593 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12594 self.change_selections(None, window, cx, |s| {
12595 s.select_anchors(entry.selections.to_vec())
12596 });
12597 self.select_next_state = entry.select_next_state;
12598 self.select_prev_state = entry.select_prev_state;
12599 self.add_selections_state = entry.add_selections_state;
12600 self.request_autoscroll(Autoscroll::newest(), cx);
12601 }
12602 self.selection_history.mode = SelectionHistoryMode::Normal;
12603 }
12604
12605 pub fn expand_excerpts(
12606 &mut self,
12607 action: &ExpandExcerpts,
12608 _: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) {
12611 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12612 }
12613
12614 pub fn expand_excerpts_down(
12615 &mut self,
12616 action: &ExpandExcerptsDown,
12617 _: &mut Window,
12618 cx: &mut Context<Self>,
12619 ) {
12620 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12621 }
12622
12623 pub fn expand_excerpts_up(
12624 &mut self,
12625 action: &ExpandExcerptsUp,
12626 _: &mut Window,
12627 cx: &mut Context<Self>,
12628 ) {
12629 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12630 }
12631
12632 pub fn expand_excerpts_for_direction(
12633 &mut self,
12634 lines: u32,
12635 direction: ExpandExcerptDirection,
12636
12637 cx: &mut Context<Self>,
12638 ) {
12639 let selections = self.selections.disjoint_anchors();
12640
12641 let lines = if lines == 0 {
12642 EditorSettings::get_global(cx).expand_excerpt_lines
12643 } else {
12644 lines
12645 };
12646
12647 self.buffer.update(cx, |buffer, cx| {
12648 let snapshot = buffer.snapshot(cx);
12649 let mut excerpt_ids = selections
12650 .iter()
12651 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
12652 .collect::<Vec<_>>();
12653 excerpt_ids.sort();
12654 excerpt_ids.dedup();
12655 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
12656 })
12657 }
12658
12659 pub fn expand_excerpt(
12660 &mut self,
12661 excerpt: ExcerptId,
12662 direction: ExpandExcerptDirection,
12663 window: &mut Window,
12664 cx: &mut Context<Self>,
12665 ) {
12666 let current_scroll_position = self.scroll_position(cx);
12667 let lines = EditorSettings::get_global(cx).expand_excerpt_lines;
12668 self.buffer.update(cx, |buffer, cx| {
12669 buffer.expand_excerpts([excerpt], lines, direction, cx)
12670 });
12671 if direction == ExpandExcerptDirection::Down {
12672 let new_scroll_position = current_scroll_position + gpui::Point::new(0.0, lines as f32);
12673 self.set_scroll_position(new_scroll_position, window, cx);
12674 }
12675 }
12676
12677 pub fn go_to_singleton_buffer_point(
12678 &mut self,
12679 point: Point,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 self.go_to_singleton_buffer_range(point..point, window, cx);
12684 }
12685
12686 pub fn go_to_singleton_buffer_range(
12687 &mut self,
12688 range: Range<Point>,
12689 window: &mut Window,
12690 cx: &mut Context<Self>,
12691 ) {
12692 let multibuffer = self.buffer().read(cx);
12693 let Some(buffer) = multibuffer.as_singleton() else {
12694 return;
12695 };
12696 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
12697 return;
12698 };
12699 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
12700 return;
12701 };
12702 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
12703 s.select_anchor_ranges([start..end])
12704 });
12705 }
12706
12707 fn go_to_diagnostic(
12708 &mut self,
12709 _: &GoToDiagnostic,
12710 window: &mut Window,
12711 cx: &mut Context<Self>,
12712 ) {
12713 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12714 self.go_to_diagnostic_impl(Direction::Next, window, cx)
12715 }
12716
12717 fn go_to_prev_diagnostic(
12718 &mut self,
12719 _: &GoToPreviousDiagnostic,
12720 window: &mut Window,
12721 cx: &mut Context<Self>,
12722 ) {
12723 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12724 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
12725 }
12726
12727 pub fn go_to_diagnostic_impl(
12728 &mut self,
12729 direction: Direction,
12730 window: &mut Window,
12731 cx: &mut Context<Self>,
12732 ) {
12733 let buffer = self.buffer.read(cx).snapshot(cx);
12734 let selection = self.selections.newest::<usize>(cx);
12735
12736 // If there is an active Diagnostic Popover jump to its diagnostic instead.
12737 if direction == Direction::Next {
12738 if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() {
12739 let Some(buffer_id) = popover.local_diagnostic.range.start.buffer_id else {
12740 return;
12741 };
12742 self.activate_diagnostics(
12743 buffer_id,
12744 popover.local_diagnostic.diagnostic.group_id,
12745 window,
12746 cx,
12747 );
12748 if let Some(active_diagnostics) = self.active_diagnostics.as_ref() {
12749 let primary_range_start = active_diagnostics.primary_range.start;
12750 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12751 let mut new_selection = s.newest_anchor().clone();
12752 new_selection.collapse_to(primary_range_start, SelectionGoal::None);
12753 s.select_anchors(vec![new_selection.clone()]);
12754 });
12755 self.refresh_inline_completion(false, true, window, cx);
12756 }
12757 return;
12758 }
12759 }
12760
12761 let active_group_id = self
12762 .active_diagnostics
12763 .as_ref()
12764 .map(|active_group| active_group.group_id);
12765 let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| {
12766 active_diagnostics
12767 .primary_range
12768 .to_offset(&buffer)
12769 .to_inclusive()
12770 });
12771 let search_start = if let Some(active_primary_range) = active_primary_range.as_ref() {
12772 if active_primary_range.contains(&selection.head()) {
12773 *active_primary_range.start()
12774 } else {
12775 selection.head()
12776 }
12777 } else {
12778 selection.head()
12779 };
12780
12781 let snapshot = self.snapshot(window, cx);
12782 let primary_diagnostics_before = buffer
12783 .diagnostics_in_range::<usize>(0..search_start)
12784 .filter(|entry| entry.diagnostic.is_primary)
12785 .filter(|entry| entry.range.start != entry.range.end)
12786 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12787 .filter(|entry| !snapshot.intersects_fold(entry.range.start))
12788 .collect::<Vec<_>>();
12789 let last_same_group_diagnostic_before = active_group_id.and_then(|active_group_id| {
12790 primary_diagnostics_before
12791 .iter()
12792 .position(|entry| entry.diagnostic.group_id == active_group_id)
12793 });
12794
12795 let primary_diagnostics_after = buffer
12796 .diagnostics_in_range::<usize>(search_start..buffer.len())
12797 .filter(|entry| entry.diagnostic.is_primary)
12798 .filter(|entry| entry.range.start != entry.range.end)
12799 .filter(|entry| entry.diagnostic.severity <= DiagnosticSeverity::WARNING)
12800 .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start))
12801 .collect::<Vec<_>>();
12802 let last_same_group_diagnostic_after = active_group_id.and_then(|active_group_id| {
12803 primary_diagnostics_after
12804 .iter()
12805 .enumerate()
12806 .rev()
12807 .find_map(|(i, entry)| {
12808 if entry.diagnostic.group_id == active_group_id {
12809 Some(i)
12810 } else {
12811 None
12812 }
12813 })
12814 });
12815
12816 let next_primary_diagnostic = match direction {
12817 Direction::Prev => primary_diagnostics_before
12818 .iter()
12819 .take(last_same_group_diagnostic_before.unwrap_or(usize::MAX))
12820 .rev()
12821 .next(),
12822 Direction::Next => primary_diagnostics_after
12823 .iter()
12824 .skip(
12825 last_same_group_diagnostic_after
12826 .map(|index| index + 1)
12827 .unwrap_or(0),
12828 )
12829 .next(),
12830 };
12831
12832 // Cycle around to the start of the buffer, potentially moving back to the start of
12833 // the currently active diagnostic.
12834 let cycle_around = || match direction {
12835 Direction::Prev => primary_diagnostics_after
12836 .iter()
12837 .rev()
12838 .chain(primary_diagnostics_before.iter().rev())
12839 .next(),
12840 Direction::Next => primary_diagnostics_before
12841 .iter()
12842 .chain(primary_diagnostics_after.iter())
12843 .next(),
12844 };
12845
12846 if let Some((primary_range, group_id)) = next_primary_diagnostic
12847 .or_else(cycle_around)
12848 .map(|entry| (&entry.range, entry.diagnostic.group_id))
12849 {
12850 let Some(buffer_id) = buffer.anchor_after(primary_range.start).buffer_id else {
12851 return;
12852 };
12853 self.activate_diagnostics(buffer_id, group_id, window, cx);
12854 if self.active_diagnostics.is_some() {
12855 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12856 s.select(vec![Selection {
12857 id: selection.id,
12858 start: primary_range.start,
12859 end: primary_range.start,
12860 reversed: false,
12861 goal: SelectionGoal::None,
12862 }]);
12863 });
12864 self.refresh_inline_completion(false, true, window, cx);
12865 }
12866 }
12867 }
12868
12869 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
12870 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12871 let snapshot = self.snapshot(window, cx);
12872 let selection = self.selections.newest::<Point>(cx);
12873 self.go_to_hunk_before_or_after_position(
12874 &snapshot,
12875 selection.head(),
12876 Direction::Next,
12877 window,
12878 cx,
12879 );
12880 }
12881
12882 pub fn go_to_hunk_before_or_after_position(
12883 &mut self,
12884 snapshot: &EditorSnapshot,
12885 position: Point,
12886 direction: Direction,
12887 window: &mut Window,
12888 cx: &mut Context<Editor>,
12889 ) {
12890 let row = if direction == Direction::Next {
12891 self.hunk_after_position(snapshot, position)
12892 .map(|hunk| hunk.row_range.start)
12893 } else {
12894 self.hunk_before_position(snapshot, position)
12895 };
12896
12897 if let Some(row) = row {
12898 let destination = Point::new(row.0, 0);
12899 let autoscroll = Autoscroll::center();
12900
12901 self.unfold_ranges(&[destination..destination], false, false, cx);
12902 self.change_selections(Some(autoscroll), window, cx, |s| {
12903 s.select_ranges([destination..destination]);
12904 });
12905 }
12906 }
12907
12908 fn hunk_after_position(
12909 &mut self,
12910 snapshot: &EditorSnapshot,
12911 position: Point,
12912 ) -> Option<MultiBufferDiffHunk> {
12913 snapshot
12914 .buffer_snapshot
12915 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
12916 .find(|hunk| hunk.row_range.start.0 > position.row)
12917 .or_else(|| {
12918 snapshot
12919 .buffer_snapshot
12920 .diff_hunks_in_range(Point::zero()..position)
12921 .find(|hunk| hunk.row_range.end.0 < position.row)
12922 })
12923 }
12924
12925 fn go_to_prev_hunk(
12926 &mut self,
12927 _: &GoToPreviousHunk,
12928 window: &mut Window,
12929 cx: &mut Context<Self>,
12930 ) {
12931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12932 let snapshot = self.snapshot(window, cx);
12933 let selection = self.selections.newest::<Point>(cx);
12934 self.go_to_hunk_before_or_after_position(
12935 &snapshot,
12936 selection.head(),
12937 Direction::Prev,
12938 window,
12939 cx,
12940 );
12941 }
12942
12943 fn hunk_before_position(
12944 &mut self,
12945 snapshot: &EditorSnapshot,
12946 position: Point,
12947 ) -> Option<MultiBufferRow> {
12948 snapshot
12949 .buffer_snapshot
12950 .diff_hunk_before(position)
12951 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
12952 }
12953
12954 fn go_to_line<T: 'static>(
12955 &mut self,
12956 position: Anchor,
12957 highlight_color: Option<Hsla>,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 let snapshot = self.snapshot(window, cx).display_snapshot;
12962 let position = position.to_point(&snapshot.buffer_snapshot);
12963 let start = snapshot
12964 .buffer_snapshot
12965 .clip_point(Point::new(position.row, 0), Bias::Left);
12966 let end = start + Point::new(1, 0);
12967 let start = snapshot.buffer_snapshot.anchor_before(start);
12968 let end = snapshot.buffer_snapshot.anchor_before(end);
12969
12970 self.highlight_rows::<T>(
12971 start..end,
12972 highlight_color
12973 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
12974 false,
12975 cx,
12976 );
12977 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
12978 }
12979
12980 pub fn go_to_definition(
12981 &mut self,
12982 _: &GoToDefinition,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) -> Task<Result<Navigated>> {
12986 let definition =
12987 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
12988 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
12989 cx.spawn_in(window, async move |editor, cx| {
12990 if definition.await? == Navigated::Yes {
12991 return Ok(Navigated::Yes);
12992 }
12993 match fallback_strategy {
12994 GoToDefinitionFallback::None => Ok(Navigated::No),
12995 GoToDefinitionFallback::FindAllReferences => {
12996 match editor.update_in(cx, |editor, window, cx| {
12997 editor.find_all_references(&FindAllReferences, window, cx)
12998 })? {
12999 Some(references) => references.await,
13000 None => Ok(Navigated::No),
13001 }
13002 }
13003 }
13004 })
13005 }
13006
13007 pub fn go_to_declaration(
13008 &mut self,
13009 _: &GoToDeclaration,
13010 window: &mut Window,
13011 cx: &mut Context<Self>,
13012 ) -> Task<Result<Navigated>> {
13013 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13014 }
13015
13016 pub fn go_to_declaration_split(
13017 &mut self,
13018 _: &GoToDeclaration,
13019 window: &mut Window,
13020 cx: &mut Context<Self>,
13021 ) -> Task<Result<Navigated>> {
13022 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13023 }
13024
13025 pub fn go_to_implementation(
13026 &mut self,
13027 _: &GoToImplementation,
13028 window: &mut Window,
13029 cx: &mut Context<Self>,
13030 ) -> Task<Result<Navigated>> {
13031 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13032 }
13033
13034 pub fn go_to_implementation_split(
13035 &mut self,
13036 _: &GoToImplementationSplit,
13037 window: &mut Window,
13038 cx: &mut Context<Self>,
13039 ) -> Task<Result<Navigated>> {
13040 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13041 }
13042
13043 pub fn go_to_type_definition(
13044 &mut self,
13045 _: &GoToTypeDefinition,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) -> Task<Result<Navigated>> {
13049 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13050 }
13051
13052 pub fn go_to_definition_split(
13053 &mut self,
13054 _: &GoToDefinitionSplit,
13055 window: &mut Window,
13056 cx: &mut Context<Self>,
13057 ) -> Task<Result<Navigated>> {
13058 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13059 }
13060
13061 pub fn go_to_type_definition_split(
13062 &mut self,
13063 _: &GoToTypeDefinitionSplit,
13064 window: &mut Window,
13065 cx: &mut Context<Self>,
13066 ) -> Task<Result<Navigated>> {
13067 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13068 }
13069
13070 fn go_to_definition_of_kind(
13071 &mut self,
13072 kind: GotoDefinitionKind,
13073 split: bool,
13074 window: &mut Window,
13075 cx: &mut Context<Self>,
13076 ) -> Task<Result<Navigated>> {
13077 let Some(provider) = self.semantics_provider.clone() else {
13078 return Task::ready(Ok(Navigated::No));
13079 };
13080 let head = self.selections.newest::<usize>(cx).head();
13081 let buffer = self.buffer.read(cx);
13082 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13083 text_anchor
13084 } else {
13085 return Task::ready(Ok(Navigated::No));
13086 };
13087
13088 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13089 return Task::ready(Ok(Navigated::No));
13090 };
13091
13092 cx.spawn_in(window, async move |editor, cx| {
13093 let definitions = definitions.await?;
13094 let navigated = editor
13095 .update_in(cx, |editor, window, cx| {
13096 editor.navigate_to_hover_links(
13097 Some(kind),
13098 definitions
13099 .into_iter()
13100 .filter(|location| {
13101 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13102 })
13103 .map(HoverLink::Text)
13104 .collect::<Vec<_>>(),
13105 split,
13106 window,
13107 cx,
13108 )
13109 })?
13110 .await?;
13111 anyhow::Ok(navigated)
13112 })
13113 }
13114
13115 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13116 let selection = self.selections.newest_anchor();
13117 let head = selection.head();
13118 let tail = selection.tail();
13119
13120 let Some((buffer, start_position)) =
13121 self.buffer.read(cx).text_anchor_for_position(head, cx)
13122 else {
13123 return;
13124 };
13125
13126 let end_position = if head != tail {
13127 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13128 return;
13129 };
13130 Some(pos)
13131 } else {
13132 None
13133 };
13134
13135 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13136 let url = if let Some(end_pos) = end_position {
13137 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13138 } else {
13139 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13140 };
13141
13142 if let Some(url) = url {
13143 editor.update(cx, |_, cx| {
13144 cx.open_url(&url);
13145 })
13146 } else {
13147 Ok(())
13148 }
13149 });
13150
13151 url_finder.detach();
13152 }
13153
13154 pub fn open_selected_filename(
13155 &mut self,
13156 _: &OpenSelectedFilename,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 let Some(workspace) = self.workspace() else {
13161 return;
13162 };
13163
13164 let position = self.selections.newest_anchor().head();
13165
13166 let Some((buffer, buffer_position)) =
13167 self.buffer.read(cx).text_anchor_for_position(position, cx)
13168 else {
13169 return;
13170 };
13171
13172 let project = self.project.clone();
13173
13174 cx.spawn_in(window, async move |_, cx| {
13175 let result = find_file(&buffer, project, buffer_position, cx).await;
13176
13177 if let Some((_, path)) = result {
13178 workspace
13179 .update_in(cx, |workspace, window, cx| {
13180 workspace.open_resolved_path(path, window, cx)
13181 })?
13182 .await?;
13183 }
13184 anyhow::Ok(())
13185 })
13186 .detach();
13187 }
13188
13189 pub(crate) fn navigate_to_hover_links(
13190 &mut self,
13191 kind: Option<GotoDefinitionKind>,
13192 mut definitions: Vec<HoverLink>,
13193 split: bool,
13194 window: &mut Window,
13195 cx: &mut Context<Editor>,
13196 ) -> Task<Result<Navigated>> {
13197 // If there is one definition, just open it directly
13198 if definitions.len() == 1 {
13199 let definition = definitions.pop().unwrap();
13200
13201 enum TargetTaskResult {
13202 Location(Option<Location>),
13203 AlreadyNavigated,
13204 }
13205
13206 let target_task = match definition {
13207 HoverLink::Text(link) => {
13208 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13209 }
13210 HoverLink::InlayHint(lsp_location, server_id) => {
13211 let computation =
13212 self.compute_target_location(lsp_location, server_id, window, cx);
13213 cx.background_spawn(async move {
13214 let location = computation.await?;
13215 Ok(TargetTaskResult::Location(location))
13216 })
13217 }
13218 HoverLink::Url(url) => {
13219 cx.open_url(&url);
13220 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13221 }
13222 HoverLink::File(path) => {
13223 if let Some(workspace) = self.workspace() {
13224 cx.spawn_in(window, async move |_, cx| {
13225 workspace
13226 .update_in(cx, |workspace, window, cx| {
13227 workspace.open_resolved_path(path, window, cx)
13228 })?
13229 .await
13230 .map(|_| TargetTaskResult::AlreadyNavigated)
13231 })
13232 } else {
13233 Task::ready(Ok(TargetTaskResult::Location(None)))
13234 }
13235 }
13236 };
13237 cx.spawn_in(window, async move |editor, cx| {
13238 let target = match target_task.await.context("target resolution task")? {
13239 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13240 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13241 TargetTaskResult::Location(Some(target)) => target,
13242 };
13243
13244 editor.update_in(cx, |editor, window, cx| {
13245 let Some(workspace) = editor.workspace() else {
13246 return Navigated::No;
13247 };
13248 let pane = workspace.read(cx).active_pane().clone();
13249
13250 let range = target.range.to_point(target.buffer.read(cx));
13251 let range = editor.range_for_match(&range);
13252 let range = collapse_multiline_range(range);
13253
13254 if !split
13255 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13256 {
13257 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13258 } else {
13259 window.defer(cx, move |window, cx| {
13260 let target_editor: Entity<Self> =
13261 workspace.update(cx, |workspace, cx| {
13262 let pane = if split {
13263 workspace.adjacent_pane(window, cx)
13264 } else {
13265 workspace.active_pane().clone()
13266 };
13267
13268 workspace.open_project_item(
13269 pane,
13270 target.buffer.clone(),
13271 true,
13272 true,
13273 window,
13274 cx,
13275 )
13276 });
13277 target_editor.update(cx, |target_editor, cx| {
13278 // When selecting a definition in a different buffer, disable the nav history
13279 // to avoid creating a history entry at the previous cursor location.
13280 pane.update(cx, |pane, _| pane.disable_history());
13281 target_editor.go_to_singleton_buffer_range(range, window, cx);
13282 pane.update(cx, |pane, _| pane.enable_history());
13283 });
13284 });
13285 }
13286 Navigated::Yes
13287 })
13288 })
13289 } else if !definitions.is_empty() {
13290 cx.spawn_in(window, async move |editor, cx| {
13291 let (title, location_tasks, workspace) = editor
13292 .update_in(cx, |editor, window, cx| {
13293 let tab_kind = match kind {
13294 Some(GotoDefinitionKind::Implementation) => "Implementations",
13295 _ => "Definitions",
13296 };
13297 let title = definitions
13298 .iter()
13299 .find_map(|definition| match definition {
13300 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13301 let buffer = origin.buffer.read(cx);
13302 format!(
13303 "{} for {}",
13304 tab_kind,
13305 buffer
13306 .text_for_range(origin.range.clone())
13307 .collect::<String>()
13308 )
13309 }),
13310 HoverLink::InlayHint(_, _) => None,
13311 HoverLink::Url(_) => None,
13312 HoverLink::File(_) => None,
13313 })
13314 .unwrap_or(tab_kind.to_string());
13315 let location_tasks = definitions
13316 .into_iter()
13317 .map(|definition| match definition {
13318 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13319 HoverLink::InlayHint(lsp_location, server_id) => editor
13320 .compute_target_location(lsp_location, server_id, window, cx),
13321 HoverLink::Url(_) => Task::ready(Ok(None)),
13322 HoverLink::File(_) => Task::ready(Ok(None)),
13323 })
13324 .collect::<Vec<_>>();
13325 (title, location_tasks, editor.workspace().clone())
13326 })
13327 .context("location tasks preparation")?;
13328
13329 let locations = future::join_all(location_tasks)
13330 .await
13331 .into_iter()
13332 .filter_map(|location| location.transpose())
13333 .collect::<Result<_>>()
13334 .context("location tasks")?;
13335
13336 let Some(workspace) = workspace else {
13337 return Ok(Navigated::No);
13338 };
13339 let opened = workspace
13340 .update_in(cx, |workspace, window, cx| {
13341 Self::open_locations_in_multibuffer(
13342 workspace,
13343 locations,
13344 title,
13345 split,
13346 MultibufferSelectionMode::First,
13347 window,
13348 cx,
13349 )
13350 })
13351 .ok();
13352
13353 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13354 })
13355 } else {
13356 Task::ready(Ok(Navigated::No))
13357 }
13358 }
13359
13360 fn compute_target_location(
13361 &self,
13362 lsp_location: lsp::Location,
13363 server_id: LanguageServerId,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) -> Task<anyhow::Result<Option<Location>>> {
13367 let Some(project) = self.project.clone() else {
13368 return Task::ready(Ok(None));
13369 };
13370
13371 cx.spawn_in(window, async move |editor, cx| {
13372 let location_task = editor.update(cx, |_, cx| {
13373 project.update(cx, |project, cx| {
13374 let language_server_name = project
13375 .language_server_statuses(cx)
13376 .find(|(id, _)| server_id == *id)
13377 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13378 language_server_name.map(|language_server_name| {
13379 project.open_local_buffer_via_lsp(
13380 lsp_location.uri.clone(),
13381 server_id,
13382 language_server_name,
13383 cx,
13384 )
13385 })
13386 })
13387 })?;
13388 let location = match location_task {
13389 Some(task) => Some({
13390 let target_buffer_handle = task.await.context("open local buffer")?;
13391 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13392 let target_start = target_buffer
13393 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13394 let target_end = target_buffer
13395 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13396 target_buffer.anchor_after(target_start)
13397 ..target_buffer.anchor_before(target_end)
13398 })?;
13399 Location {
13400 buffer: target_buffer_handle,
13401 range,
13402 }
13403 }),
13404 None => None,
13405 };
13406 Ok(location)
13407 })
13408 }
13409
13410 pub fn find_all_references(
13411 &mut self,
13412 _: &FindAllReferences,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) -> Option<Task<Result<Navigated>>> {
13416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13417
13418 let selection = self.selections.newest::<usize>(cx);
13419 let multi_buffer = self.buffer.read(cx);
13420 let head = selection.head();
13421
13422 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13423 let head_anchor = multi_buffer_snapshot.anchor_at(
13424 head,
13425 if head < selection.tail() {
13426 Bias::Right
13427 } else {
13428 Bias::Left
13429 },
13430 );
13431
13432 match self
13433 .find_all_references_task_sources
13434 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13435 {
13436 Ok(_) => {
13437 log::info!(
13438 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13439 );
13440 return None;
13441 }
13442 Err(i) => {
13443 self.find_all_references_task_sources.insert(i, head_anchor);
13444 }
13445 }
13446
13447 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13448 let workspace = self.workspace()?;
13449 let project = workspace.read(cx).project().clone();
13450 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13451 Some(cx.spawn_in(window, async move |editor, cx| {
13452 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13453 if let Ok(i) = editor
13454 .find_all_references_task_sources
13455 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13456 {
13457 editor.find_all_references_task_sources.remove(i);
13458 }
13459 });
13460
13461 let locations = references.await?;
13462 if locations.is_empty() {
13463 return anyhow::Ok(Navigated::No);
13464 }
13465
13466 workspace.update_in(cx, |workspace, window, cx| {
13467 let title = locations
13468 .first()
13469 .as_ref()
13470 .map(|location| {
13471 let buffer = location.buffer.read(cx);
13472 format!(
13473 "References to `{}`",
13474 buffer
13475 .text_for_range(location.range.clone())
13476 .collect::<String>()
13477 )
13478 })
13479 .unwrap();
13480 Self::open_locations_in_multibuffer(
13481 workspace,
13482 locations,
13483 title,
13484 false,
13485 MultibufferSelectionMode::First,
13486 window,
13487 cx,
13488 );
13489 Navigated::Yes
13490 })
13491 }))
13492 }
13493
13494 /// Opens a multibuffer with the given project locations in it
13495 pub fn open_locations_in_multibuffer(
13496 workspace: &mut Workspace,
13497 mut locations: Vec<Location>,
13498 title: String,
13499 split: bool,
13500 multibuffer_selection_mode: MultibufferSelectionMode,
13501 window: &mut Window,
13502 cx: &mut Context<Workspace>,
13503 ) {
13504 // If there are multiple definitions, open them in a multibuffer
13505 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13506 let mut locations = locations.into_iter().peekable();
13507 let mut ranges = Vec::new();
13508 let capability = workspace.project().read(cx).capability();
13509
13510 let excerpt_buffer = cx.new(|cx| {
13511 let mut multibuffer = MultiBuffer::new(capability);
13512 while let Some(location) = locations.next() {
13513 let buffer = location.buffer.read(cx);
13514 let mut ranges_for_buffer = Vec::new();
13515 let range = location.range.to_offset(buffer);
13516 ranges_for_buffer.push(range.clone());
13517
13518 while let Some(next_location) = locations.peek() {
13519 if next_location.buffer == location.buffer {
13520 ranges_for_buffer.push(next_location.range.to_offset(buffer));
13521 locations.next();
13522 } else {
13523 break;
13524 }
13525 }
13526
13527 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13528 ranges.extend(multibuffer.push_excerpts_with_context_lines(
13529 location.buffer.clone(),
13530 ranges_for_buffer,
13531 DEFAULT_MULTIBUFFER_CONTEXT,
13532 cx,
13533 ))
13534 }
13535
13536 multibuffer.with_title(title)
13537 });
13538
13539 let editor = cx.new(|cx| {
13540 Editor::for_multibuffer(
13541 excerpt_buffer,
13542 Some(workspace.project().clone()),
13543 window,
13544 cx,
13545 )
13546 });
13547 editor.update(cx, |editor, cx| {
13548 match multibuffer_selection_mode {
13549 MultibufferSelectionMode::First => {
13550 if let Some(first_range) = ranges.first() {
13551 editor.change_selections(None, window, cx, |selections| {
13552 selections.clear_disjoint();
13553 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13554 });
13555 }
13556 editor.highlight_background::<Self>(
13557 &ranges,
13558 |theme| theme.editor_highlighted_line_background,
13559 cx,
13560 );
13561 }
13562 MultibufferSelectionMode::All => {
13563 editor.change_selections(None, window, cx, |selections| {
13564 selections.clear_disjoint();
13565 selections.select_anchor_ranges(ranges);
13566 });
13567 }
13568 }
13569 editor.register_buffers_with_language_servers(cx);
13570 });
13571
13572 let item = Box::new(editor);
13573 let item_id = item.item_id();
13574
13575 if split {
13576 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13577 } else {
13578 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13579 let (preview_item_id, preview_item_idx) =
13580 workspace.active_pane().update(cx, |pane, _| {
13581 (pane.preview_item_id(), pane.preview_item_idx())
13582 });
13583
13584 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13585
13586 if let Some(preview_item_id) = preview_item_id {
13587 workspace.active_pane().update(cx, |pane, cx| {
13588 pane.remove_item(preview_item_id, false, false, window, cx);
13589 });
13590 }
13591 } else {
13592 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13593 }
13594 }
13595 workspace.active_pane().update(cx, |pane, cx| {
13596 pane.set_preview_item_id(Some(item_id), cx);
13597 });
13598 }
13599
13600 pub fn rename(
13601 &mut self,
13602 _: &Rename,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) -> Option<Task<Result<()>>> {
13606 use language::ToOffset as _;
13607
13608 let provider = self.semantics_provider.clone()?;
13609 let selection = self.selections.newest_anchor().clone();
13610 let (cursor_buffer, cursor_buffer_position) = self
13611 .buffer
13612 .read(cx)
13613 .text_anchor_for_position(selection.head(), cx)?;
13614 let (tail_buffer, cursor_buffer_position_end) = self
13615 .buffer
13616 .read(cx)
13617 .text_anchor_for_position(selection.tail(), cx)?;
13618 if tail_buffer != cursor_buffer {
13619 return None;
13620 }
13621
13622 let snapshot = cursor_buffer.read(cx).snapshot();
13623 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13624 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13625 let prepare_rename = provider
13626 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13627 .unwrap_or_else(|| Task::ready(Ok(None)));
13628 drop(snapshot);
13629
13630 Some(cx.spawn_in(window, async move |this, cx| {
13631 let rename_range = if let Some(range) = prepare_rename.await? {
13632 Some(range)
13633 } else {
13634 this.update(cx, |this, cx| {
13635 let buffer = this.buffer.read(cx).snapshot(cx);
13636 let mut buffer_highlights = this
13637 .document_highlights_for_position(selection.head(), &buffer)
13638 .filter(|highlight| {
13639 highlight.start.excerpt_id == selection.head().excerpt_id
13640 && highlight.end.excerpt_id == selection.head().excerpt_id
13641 });
13642 buffer_highlights
13643 .next()
13644 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13645 })?
13646 };
13647 if let Some(rename_range) = rename_range {
13648 this.update_in(cx, |this, window, cx| {
13649 let snapshot = cursor_buffer.read(cx).snapshot();
13650 let rename_buffer_range = rename_range.to_offset(&snapshot);
13651 let cursor_offset_in_rename_range =
13652 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13653 let cursor_offset_in_rename_range_end =
13654 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13655
13656 this.take_rename(false, window, cx);
13657 let buffer = this.buffer.read(cx).read(cx);
13658 let cursor_offset = selection.head().to_offset(&buffer);
13659 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13660 let rename_end = rename_start + rename_buffer_range.len();
13661 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13662 let mut old_highlight_id = None;
13663 let old_name: Arc<str> = buffer
13664 .chunks(rename_start..rename_end, true)
13665 .map(|chunk| {
13666 if old_highlight_id.is_none() {
13667 old_highlight_id = chunk.syntax_highlight_id;
13668 }
13669 chunk.text
13670 })
13671 .collect::<String>()
13672 .into();
13673
13674 drop(buffer);
13675
13676 // Position the selection in the rename editor so that it matches the current selection.
13677 this.show_local_selections = false;
13678 let rename_editor = cx.new(|cx| {
13679 let mut editor = Editor::single_line(window, cx);
13680 editor.buffer.update(cx, |buffer, cx| {
13681 buffer.edit([(0..0, old_name.clone())], None, cx)
13682 });
13683 let rename_selection_range = match cursor_offset_in_rename_range
13684 .cmp(&cursor_offset_in_rename_range_end)
13685 {
13686 Ordering::Equal => {
13687 editor.select_all(&SelectAll, window, cx);
13688 return editor;
13689 }
13690 Ordering::Less => {
13691 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
13692 }
13693 Ordering::Greater => {
13694 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
13695 }
13696 };
13697 if rename_selection_range.end > old_name.len() {
13698 editor.select_all(&SelectAll, window, cx);
13699 } else {
13700 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13701 s.select_ranges([rename_selection_range]);
13702 });
13703 }
13704 editor
13705 });
13706 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
13707 if e == &EditorEvent::Focused {
13708 cx.emit(EditorEvent::FocusedIn)
13709 }
13710 })
13711 .detach();
13712
13713 let write_highlights =
13714 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
13715 let read_highlights =
13716 this.clear_background_highlights::<DocumentHighlightRead>(cx);
13717 let ranges = write_highlights
13718 .iter()
13719 .flat_map(|(_, ranges)| ranges.iter())
13720 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
13721 .cloned()
13722 .collect();
13723
13724 this.highlight_text::<Rename>(
13725 ranges,
13726 HighlightStyle {
13727 fade_out: Some(0.6),
13728 ..Default::default()
13729 },
13730 cx,
13731 );
13732 let rename_focus_handle = rename_editor.focus_handle(cx);
13733 window.focus(&rename_focus_handle);
13734 let block_id = this.insert_blocks(
13735 [BlockProperties {
13736 style: BlockStyle::Flex,
13737 placement: BlockPlacement::Below(range.start),
13738 height: 1,
13739 render: Arc::new({
13740 let rename_editor = rename_editor.clone();
13741 move |cx: &mut BlockContext| {
13742 let mut text_style = cx.editor_style.text.clone();
13743 if let Some(highlight_style) = old_highlight_id
13744 .and_then(|h| h.style(&cx.editor_style.syntax))
13745 {
13746 text_style = text_style.highlight(highlight_style);
13747 }
13748 div()
13749 .block_mouse_down()
13750 .pl(cx.anchor_x)
13751 .child(EditorElement::new(
13752 &rename_editor,
13753 EditorStyle {
13754 background: cx.theme().system().transparent,
13755 local_player: cx.editor_style.local_player,
13756 text: text_style,
13757 scrollbar_width: cx.editor_style.scrollbar_width,
13758 syntax: cx.editor_style.syntax.clone(),
13759 status: cx.editor_style.status.clone(),
13760 inlay_hints_style: HighlightStyle {
13761 font_weight: Some(FontWeight::BOLD),
13762 ..make_inlay_hints_style(cx.app)
13763 },
13764 inline_completion_styles: make_suggestion_styles(
13765 cx.app,
13766 ),
13767 ..EditorStyle::default()
13768 },
13769 ))
13770 .into_any_element()
13771 }
13772 }),
13773 priority: 0,
13774 }],
13775 Some(Autoscroll::fit()),
13776 cx,
13777 )[0];
13778 this.pending_rename = Some(RenameState {
13779 range,
13780 old_name,
13781 editor: rename_editor,
13782 block_id,
13783 });
13784 })?;
13785 }
13786
13787 Ok(())
13788 }))
13789 }
13790
13791 pub fn confirm_rename(
13792 &mut self,
13793 _: &ConfirmRename,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) -> Option<Task<Result<()>>> {
13797 let rename = self.take_rename(false, window, cx)?;
13798 let workspace = self.workspace()?.downgrade();
13799 let (buffer, start) = self
13800 .buffer
13801 .read(cx)
13802 .text_anchor_for_position(rename.range.start, cx)?;
13803 let (end_buffer, _) = self
13804 .buffer
13805 .read(cx)
13806 .text_anchor_for_position(rename.range.end, cx)?;
13807 if buffer != end_buffer {
13808 return None;
13809 }
13810
13811 let old_name = rename.old_name;
13812 let new_name = rename.editor.read(cx).text(cx);
13813
13814 let rename = self.semantics_provider.as_ref()?.perform_rename(
13815 &buffer,
13816 start,
13817 new_name.clone(),
13818 cx,
13819 )?;
13820
13821 Some(cx.spawn_in(window, async move |editor, cx| {
13822 let project_transaction = rename.await?;
13823 Self::open_project_transaction(
13824 &editor,
13825 workspace,
13826 project_transaction,
13827 format!("Rename: {} → {}", old_name, new_name),
13828 cx,
13829 )
13830 .await?;
13831
13832 editor.update(cx, |editor, cx| {
13833 editor.refresh_document_highlights(cx);
13834 })?;
13835 Ok(())
13836 }))
13837 }
13838
13839 fn take_rename(
13840 &mut self,
13841 moving_cursor: bool,
13842 window: &mut Window,
13843 cx: &mut Context<Self>,
13844 ) -> Option<RenameState> {
13845 let rename = self.pending_rename.take()?;
13846 if rename.editor.focus_handle(cx).is_focused(window) {
13847 window.focus(&self.focus_handle);
13848 }
13849
13850 self.remove_blocks(
13851 [rename.block_id].into_iter().collect(),
13852 Some(Autoscroll::fit()),
13853 cx,
13854 );
13855 self.clear_highlights::<Rename>(cx);
13856 self.show_local_selections = true;
13857
13858 if moving_cursor {
13859 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
13860 editor.selections.newest::<usize>(cx).head()
13861 });
13862
13863 // Update the selection to match the position of the selection inside
13864 // the rename editor.
13865 let snapshot = self.buffer.read(cx).read(cx);
13866 let rename_range = rename.range.to_offset(&snapshot);
13867 let cursor_in_editor = snapshot
13868 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
13869 .min(rename_range.end);
13870 drop(snapshot);
13871
13872 self.change_selections(None, window, cx, |s| {
13873 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
13874 });
13875 } else {
13876 self.refresh_document_highlights(cx);
13877 }
13878
13879 Some(rename)
13880 }
13881
13882 pub fn pending_rename(&self) -> Option<&RenameState> {
13883 self.pending_rename.as_ref()
13884 }
13885
13886 fn format(
13887 &mut self,
13888 _: &Format,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) -> Option<Task<Result<()>>> {
13892 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13893
13894 let project = match &self.project {
13895 Some(project) => project.clone(),
13896 None => return None,
13897 };
13898
13899 Some(self.perform_format(
13900 project,
13901 FormatTrigger::Manual,
13902 FormatTarget::Buffers,
13903 window,
13904 cx,
13905 ))
13906 }
13907
13908 fn format_selections(
13909 &mut self,
13910 _: &FormatSelections,
13911 window: &mut Window,
13912 cx: &mut Context<Self>,
13913 ) -> Option<Task<Result<()>>> {
13914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13915
13916 let project = match &self.project {
13917 Some(project) => project.clone(),
13918 None => return None,
13919 };
13920
13921 let ranges = self
13922 .selections
13923 .all_adjusted(cx)
13924 .into_iter()
13925 .map(|selection| selection.range())
13926 .collect_vec();
13927
13928 Some(self.perform_format(
13929 project,
13930 FormatTrigger::Manual,
13931 FormatTarget::Ranges(ranges),
13932 window,
13933 cx,
13934 ))
13935 }
13936
13937 fn perform_format(
13938 &mut self,
13939 project: Entity<Project>,
13940 trigger: FormatTrigger,
13941 target: FormatTarget,
13942 window: &mut Window,
13943 cx: &mut Context<Self>,
13944 ) -> Task<Result<()>> {
13945 let buffer = self.buffer.clone();
13946 let (buffers, target) = match target {
13947 FormatTarget::Buffers => {
13948 let mut buffers = buffer.read(cx).all_buffers();
13949 if trigger == FormatTrigger::Save {
13950 buffers.retain(|buffer| buffer.read(cx).is_dirty());
13951 }
13952 (buffers, LspFormatTarget::Buffers)
13953 }
13954 FormatTarget::Ranges(selection_ranges) => {
13955 let multi_buffer = buffer.read(cx);
13956 let snapshot = multi_buffer.read(cx);
13957 let mut buffers = HashSet::default();
13958 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
13959 BTreeMap::new();
13960 for selection_range in selection_ranges {
13961 for (buffer, buffer_range, _) in
13962 snapshot.range_to_buffer_ranges(selection_range)
13963 {
13964 let buffer_id = buffer.remote_id();
13965 let start = buffer.anchor_before(buffer_range.start);
13966 let end = buffer.anchor_after(buffer_range.end);
13967 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
13968 buffer_id_to_ranges
13969 .entry(buffer_id)
13970 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
13971 .or_insert_with(|| vec![start..end]);
13972 }
13973 }
13974 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
13975 }
13976 };
13977
13978 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
13979 let format = project.update(cx, |project, cx| {
13980 project.format(buffers, target, true, trigger, cx)
13981 });
13982
13983 cx.spawn_in(window, async move |_, cx| {
13984 let transaction = futures::select_biased! {
13985 transaction = format.log_err().fuse() => transaction,
13986 () = timeout => {
13987 log::warn!("timed out waiting for formatting");
13988 None
13989 }
13990 };
13991
13992 buffer
13993 .update(cx, |buffer, cx| {
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
14003 Ok(())
14004 })
14005 }
14006
14007 fn organize_imports(
14008 &mut self,
14009 _: &OrganizeImports,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) -> Option<Task<Result<()>>> {
14013 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14014 let project = match &self.project {
14015 Some(project) => project.clone(),
14016 None => return None,
14017 };
14018 Some(self.perform_code_action_kind(
14019 project,
14020 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14021 window,
14022 cx,
14023 ))
14024 }
14025
14026 fn perform_code_action_kind(
14027 &mut self,
14028 project: Entity<Project>,
14029 kind: CodeActionKind,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) -> Task<Result<()>> {
14033 let buffer = self.buffer.clone();
14034 let buffers = buffer.read(cx).all_buffers();
14035 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14036 let apply_action = project.update(cx, |project, cx| {
14037 project.apply_code_action_kind(buffers, kind, true, cx)
14038 });
14039 cx.spawn_in(window, async move |_, cx| {
14040 let transaction = futures::select_biased! {
14041 () = timeout => {
14042 log::warn!("timed out waiting for executing code action");
14043 None
14044 }
14045 transaction = apply_action.log_err().fuse() => transaction,
14046 };
14047 buffer
14048 .update(cx, |buffer, cx| {
14049 // check if we need this
14050 if let Some(transaction) = transaction {
14051 if !buffer.is_singleton() {
14052 buffer.push_transaction(&transaction.0, cx);
14053 }
14054 }
14055 cx.notify();
14056 })
14057 .ok();
14058 Ok(())
14059 })
14060 }
14061
14062 fn restart_language_server(
14063 &mut self,
14064 _: &RestartLanguageServer,
14065 _: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 if let Some(project) = self.project.clone() {
14069 self.buffer.update(cx, |multi_buffer, cx| {
14070 project.update(cx, |project, cx| {
14071 project.restart_language_servers_for_buffers(
14072 multi_buffer.all_buffers().into_iter().collect(),
14073 cx,
14074 );
14075 });
14076 })
14077 }
14078 }
14079
14080 fn cancel_language_server_work(
14081 workspace: &mut Workspace,
14082 _: &actions::CancelLanguageServerWork,
14083 _: &mut Window,
14084 cx: &mut Context<Workspace>,
14085 ) {
14086 let project = workspace.project();
14087 let buffers = workspace
14088 .active_item(cx)
14089 .and_then(|item| item.act_as::<Editor>(cx))
14090 .map_or(HashSet::default(), |editor| {
14091 editor.read(cx).buffer.read(cx).all_buffers()
14092 });
14093 project.update(cx, |project, cx| {
14094 project.cancel_language_server_work_for_buffers(buffers, cx);
14095 });
14096 }
14097
14098 fn show_character_palette(
14099 &mut self,
14100 _: &ShowCharacterPalette,
14101 window: &mut Window,
14102 _: &mut Context<Self>,
14103 ) {
14104 window.show_character_palette();
14105 }
14106
14107 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14108 if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
14109 let buffer = self.buffer.read(cx).snapshot(cx);
14110 let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
14111 let primary_range_end = active_diagnostics.primary_range.end.to_offset(&buffer);
14112 let is_valid = buffer
14113 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14114 .any(|entry| {
14115 entry.diagnostic.is_primary
14116 && !entry.range.is_empty()
14117 && entry.range.start == primary_range_start
14118 && entry.diagnostic.message == active_diagnostics.primary_message
14119 });
14120
14121 if is_valid != active_diagnostics.is_valid {
14122 active_diagnostics.is_valid = is_valid;
14123 if is_valid {
14124 let mut new_styles = HashMap::default();
14125 for (block_id, diagnostic) in &active_diagnostics.blocks {
14126 new_styles.insert(
14127 *block_id,
14128 diagnostic_block_renderer(diagnostic.clone(), None, true),
14129 );
14130 }
14131 self.display_map.update(cx, |display_map, _cx| {
14132 display_map.replace_blocks(new_styles);
14133 });
14134 } else {
14135 self.dismiss_diagnostics(cx);
14136 }
14137 }
14138 }
14139 }
14140
14141 fn activate_diagnostics(
14142 &mut self,
14143 buffer_id: BufferId,
14144 group_id: usize,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) {
14148 self.dismiss_diagnostics(cx);
14149 let snapshot = self.snapshot(window, cx);
14150 self.active_diagnostics = self.display_map.update(cx, |display_map, cx| {
14151 let buffer = self.buffer.read(cx).snapshot(cx);
14152
14153 let mut primary_range = None;
14154 let mut primary_message = None;
14155 let diagnostic_group = buffer
14156 .diagnostic_group(buffer_id, group_id)
14157 .filter_map(|entry| {
14158 let start = entry.range.start;
14159 let end = entry.range.end;
14160 if snapshot.is_line_folded(MultiBufferRow(start.row))
14161 && (start.row == end.row
14162 || snapshot.is_line_folded(MultiBufferRow(end.row)))
14163 {
14164 return None;
14165 }
14166 if entry.diagnostic.is_primary {
14167 primary_range = Some(entry.range.clone());
14168 primary_message = Some(entry.diagnostic.message.clone());
14169 }
14170 Some(entry)
14171 })
14172 .collect::<Vec<_>>();
14173 let primary_range = primary_range?;
14174 let primary_message = primary_message?;
14175
14176 let blocks = display_map
14177 .insert_blocks(
14178 diagnostic_group.iter().map(|entry| {
14179 let diagnostic = entry.diagnostic.clone();
14180 let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
14181 BlockProperties {
14182 style: BlockStyle::Fixed,
14183 placement: BlockPlacement::Below(
14184 buffer.anchor_after(entry.range.start),
14185 ),
14186 height: message_height,
14187 render: diagnostic_block_renderer(diagnostic, None, true),
14188 priority: 0,
14189 }
14190 }),
14191 cx,
14192 )
14193 .into_iter()
14194 .zip(diagnostic_group.into_iter().map(|entry| entry.diagnostic))
14195 .collect();
14196
14197 Some(ActiveDiagnosticGroup {
14198 primary_range: buffer.anchor_before(primary_range.start)
14199 ..buffer.anchor_after(primary_range.end),
14200 primary_message,
14201 group_id,
14202 blocks,
14203 is_valid: true,
14204 })
14205 });
14206 }
14207
14208 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14209 if let Some(active_diagnostic_group) = self.active_diagnostics.take() {
14210 self.display_map.update(cx, |display_map, cx| {
14211 display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx);
14212 });
14213 cx.notify();
14214 }
14215 }
14216
14217 /// Disable inline diagnostics rendering for this editor.
14218 pub fn disable_inline_diagnostics(&mut self) {
14219 self.inline_diagnostics_enabled = false;
14220 self.inline_diagnostics_update = Task::ready(());
14221 self.inline_diagnostics.clear();
14222 }
14223
14224 pub fn inline_diagnostics_enabled(&self) -> bool {
14225 self.inline_diagnostics_enabled
14226 }
14227
14228 pub fn show_inline_diagnostics(&self) -> bool {
14229 self.show_inline_diagnostics
14230 }
14231
14232 pub fn toggle_inline_diagnostics(
14233 &mut self,
14234 _: &ToggleInlineDiagnostics,
14235 window: &mut Window,
14236 cx: &mut Context<Editor>,
14237 ) {
14238 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14239 self.refresh_inline_diagnostics(false, window, cx);
14240 }
14241
14242 fn refresh_inline_diagnostics(
14243 &mut self,
14244 debounce: bool,
14245 window: &mut Window,
14246 cx: &mut Context<Self>,
14247 ) {
14248 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14249 self.inline_diagnostics_update = Task::ready(());
14250 self.inline_diagnostics.clear();
14251 return;
14252 }
14253
14254 let debounce_ms = ProjectSettings::get_global(cx)
14255 .diagnostics
14256 .inline
14257 .update_debounce_ms;
14258 let debounce = if debounce && debounce_ms > 0 {
14259 Some(Duration::from_millis(debounce_ms))
14260 } else {
14261 None
14262 };
14263 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14264 if let Some(debounce) = debounce {
14265 cx.background_executor().timer(debounce).await;
14266 }
14267 let Some(snapshot) = editor
14268 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14269 .ok()
14270 else {
14271 return;
14272 };
14273
14274 let new_inline_diagnostics = cx
14275 .background_spawn(async move {
14276 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14277 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14278 let message = diagnostic_entry
14279 .diagnostic
14280 .message
14281 .split_once('\n')
14282 .map(|(line, _)| line)
14283 .map(SharedString::new)
14284 .unwrap_or_else(|| {
14285 SharedString::from(diagnostic_entry.diagnostic.message)
14286 });
14287 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14288 let (Ok(i) | Err(i)) = inline_diagnostics
14289 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14290 inline_diagnostics.insert(
14291 i,
14292 (
14293 start_anchor,
14294 InlineDiagnostic {
14295 message,
14296 group_id: diagnostic_entry.diagnostic.group_id,
14297 start: diagnostic_entry.range.start.to_point(&snapshot),
14298 is_primary: diagnostic_entry.diagnostic.is_primary,
14299 severity: diagnostic_entry.diagnostic.severity,
14300 },
14301 ),
14302 );
14303 }
14304 inline_diagnostics
14305 })
14306 .await;
14307
14308 editor
14309 .update(cx, |editor, cx| {
14310 editor.inline_diagnostics = new_inline_diagnostics;
14311 cx.notify();
14312 })
14313 .ok();
14314 });
14315 }
14316
14317 pub fn set_selections_from_remote(
14318 &mut self,
14319 selections: Vec<Selection<Anchor>>,
14320 pending_selection: Option<Selection<Anchor>>,
14321 window: &mut Window,
14322 cx: &mut Context<Self>,
14323 ) {
14324 let old_cursor_position = self.selections.newest_anchor().head();
14325 self.selections.change_with(cx, |s| {
14326 s.select_anchors(selections);
14327 if let Some(pending_selection) = pending_selection {
14328 s.set_pending(pending_selection, SelectMode::Character);
14329 } else {
14330 s.clear_pending();
14331 }
14332 });
14333 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14334 }
14335
14336 fn push_to_selection_history(&mut self) {
14337 self.selection_history.push(SelectionHistoryEntry {
14338 selections: self.selections.disjoint_anchors(),
14339 select_next_state: self.select_next_state.clone(),
14340 select_prev_state: self.select_prev_state.clone(),
14341 add_selections_state: self.add_selections_state.clone(),
14342 });
14343 }
14344
14345 pub fn transact(
14346 &mut self,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14350 ) -> Option<TransactionId> {
14351 self.start_transaction_at(Instant::now(), window, cx);
14352 update(self, window, cx);
14353 self.end_transaction_at(Instant::now(), cx)
14354 }
14355
14356 pub fn start_transaction_at(
14357 &mut self,
14358 now: Instant,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) {
14362 self.end_selection(window, cx);
14363 if let Some(tx_id) = self
14364 .buffer
14365 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14366 {
14367 self.selection_history
14368 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14369 cx.emit(EditorEvent::TransactionBegun {
14370 transaction_id: tx_id,
14371 })
14372 }
14373 }
14374
14375 pub fn end_transaction_at(
14376 &mut self,
14377 now: Instant,
14378 cx: &mut Context<Self>,
14379 ) -> Option<TransactionId> {
14380 if let Some(transaction_id) = self
14381 .buffer
14382 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14383 {
14384 if let Some((_, end_selections)) =
14385 self.selection_history.transaction_mut(transaction_id)
14386 {
14387 *end_selections = Some(self.selections.disjoint_anchors());
14388 } else {
14389 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14390 }
14391
14392 cx.emit(EditorEvent::Edited { transaction_id });
14393 Some(transaction_id)
14394 } else {
14395 None
14396 }
14397 }
14398
14399 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14400 if self.selection_mark_mode {
14401 self.change_selections(None, window, cx, |s| {
14402 s.move_with(|_, sel| {
14403 sel.collapse_to(sel.head(), SelectionGoal::None);
14404 });
14405 })
14406 }
14407 self.selection_mark_mode = true;
14408 cx.notify();
14409 }
14410
14411 pub fn swap_selection_ends(
14412 &mut self,
14413 _: &actions::SwapSelectionEnds,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 self.change_selections(None, window, cx, |s| {
14418 s.move_with(|_, sel| {
14419 if sel.start != sel.end {
14420 sel.reversed = !sel.reversed
14421 }
14422 });
14423 });
14424 self.request_autoscroll(Autoscroll::newest(), cx);
14425 cx.notify();
14426 }
14427
14428 pub fn toggle_fold(
14429 &mut self,
14430 _: &actions::ToggleFold,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 if self.is_singleton(cx) {
14435 let selection = self.selections.newest::<Point>(cx);
14436
14437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14438 let range = if selection.is_empty() {
14439 let point = selection.head().to_display_point(&display_map);
14440 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14441 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14442 .to_point(&display_map);
14443 start..end
14444 } else {
14445 selection.range()
14446 };
14447 if display_map.folds_in_range(range).next().is_some() {
14448 self.unfold_lines(&Default::default(), window, cx)
14449 } else {
14450 self.fold(&Default::default(), window, cx)
14451 }
14452 } else {
14453 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14454 let buffer_ids: HashSet<_> = self
14455 .selections
14456 .disjoint_anchor_ranges()
14457 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14458 .collect();
14459
14460 let should_unfold = buffer_ids
14461 .iter()
14462 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14463
14464 for buffer_id in buffer_ids {
14465 if should_unfold {
14466 self.unfold_buffer(buffer_id, cx);
14467 } else {
14468 self.fold_buffer(buffer_id, cx);
14469 }
14470 }
14471 }
14472 }
14473
14474 pub fn toggle_fold_recursive(
14475 &mut self,
14476 _: &actions::ToggleFoldRecursive,
14477 window: &mut Window,
14478 cx: &mut Context<Self>,
14479 ) {
14480 let selection = self.selections.newest::<Point>(cx);
14481
14482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14483 let range = if selection.is_empty() {
14484 let point = selection.head().to_display_point(&display_map);
14485 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14486 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14487 .to_point(&display_map);
14488 start..end
14489 } else {
14490 selection.range()
14491 };
14492 if display_map.folds_in_range(range).next().is_some() {
14493 self.unfold_recursive(&Default::default(), window, cx)
14494 } else {
14495 self.fold_recursive(&Default::default(), window, cx)
14496 }
14497 }
14498
14499 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14500 if self.is_singleton(cx) {
14501 let mut to_fold = Vec::new();
14502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14503 let selections = self.selections.all_adjusted(cx);
14504
14505 for selection in selections {
14506 let range = selection.range().sorted();
14507 let buffer_start_row = range.start.row;
14508
14509 if range.start.row != range.end.row {
14510 let mut found = false;
14511 let mut row = range.start.row;
14512 while row <= range.end.row {
14513 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14514 {
14515 found = true;
14516 row = crease.range().end.row + 1;
14517 to_fold.push(crease);
14518 } else {
14519 row += 1
14520 }
14521 }
14522 if found {
14523 continue;
14524 }
14525 }
14526
14527 for row in (0..=range.start.row).rev() {
14528 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14529 if crease.range().end.row >= buffer_start_row {
14530 to_fold.push(crease);
14531 if row <= range.start.row {
14532 break;
14533 }
14534 }
14535 }
14536 }
14537 }
14538
14539 self.fold_creases(to_fold, true, window, cx);
14540 } else {
14541 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14542 let buffer_ids = self
14543 .selections
14544 .disjoint_anchor_ranges()
14545 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14546 .collect::<HashSet<_>>();
14547 for buffer_id in buffer_ids {
14548 self.fold_buffer(buffer_id, cx);
14549 }
14550 }
14551 }
14552
14553 fn fold_at_level(
14554 &mut self,
14555 fold_at: &FoldAtLevel,
14556 window: &mut Window,
14557 cx: &mut Context<Self>,
14558 ) {
14559 if !self.buffer.read(cx).is_singleton() {
14560 return;
14561 }
14562
14563 let fold_at_level = fold_at.0;
14564 let snapshot = self.buffer.read(cx).snapshot(cx);
14565 let mut to_fold = Vec::new();
14566 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14567
14568 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14569 while start_row < end_row {
14570 match self
14571 .snapshot(window, cx)
14572 .crease_for_buffer_row(MultiBufferRow(start_row))
14573 {
14574 Some(crease) => {
14575 let nested_start_row = crease.range().start.row + 1;
14576 let nested_end_row = crease.range().end.row;
14577
14578 if current_level < fold_at_level {
14579 stack.push((nested_start_row, nested_end_row, current_level + 1));
14580 } else if current_level == fold_at_level {
14581 to_fold.push(crease);
14582 }
14583
14584 start_row = nested_end_row + 1;
14585 }
14586 None => start_row += 1,
14587 }
14588 }
14589 }
14590
14591 self.fold_creases(to_fold, true, window, cx);
14592 }
14593
14594 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14595 if self.buffer.read(cx).is_singleton() {
14596 let mut fold_ranges = Vec::new();
14597 let snapshot = self.buffer.read(cx).snapshot(cx);
14598
14599 for row in 0..snapshot.max_row().0 {
14600 if let Some(foldable_range) = self
14601 .snapshot(window, cx)
14602 .crease_for_buffer_row(MultiBufferRow(row))
14603 {
14604 fold_ranges.push(foldable_range);
14605 }
14606 }
14607
14608 self.fold_creases(fold_ranges, true, window, cx);
14609 } else {
14610 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14611 editor
14612 .update_in(cx, |editor, _, cx| {
14613 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14614 editor.fold_buffer(buffer_id, cx);
14615 }
14616 })
14617 .ok();
14618 });
14619 }
14620 }
14621
14622 pub fn fold_function_bodies(
14623 &mut self,
14624 _: &actions::FoldFunctionBodies,
14625 window: &mut Window,
14626 cx: &mut Context<Self>,
14627 ) {
14628 let snapshot = self.buffer.read(cx).snapshot(cx);
14629
14630 let ranges = snapshot
14631 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14632 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14633 .collect::<Vec<_>>();
14634
14635 let creases = ranges
14636 .into_iter()
14637 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14638 .collect();
14639
14640 self.fold_creases(creases, true, window, cx);
14641 }
14642
14643 pub fn fold_recursive(
14644 &mut self,
14645 _: &actions::FoldRecursive,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 let mut to_fold = Vec::new();
14650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14651 let selections = self.selections.all_adjusted(cx);
14652
14653 for selection in selections {
14654 let range = selection.range().sorted();
14655 let buffer_start_row = range.start.row;
14656
14657 if range.start.row != range.end.row {
14658 let mut found = false;
14659 for row in range.start.row..=range.end.row {
14660 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14661 found = true;
14662 to_fold.push(crease);
14663 }
14664 }
14665 if found {
14666 continue;
14667 }
14668 }
14669
14670 for row in (0..=range.start.row).rev() {
14671 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14672 if crease.range().end.row >= buffer_start_row {
14673 to_fold.push(crease);
14674 } else {
14675 break;
14676 }
14677 }
14678 }
14679 }
14680
14681 self.fold_creases(to_fold, true, window, cx);
14682 }
14683
14684 pub fn fold_at(&mut self, fold_at: &FoldAt, window: &mut Window, cx: &mut Context<Self>) {
14685 let buffer_row = fold_at.buffer_row;
14686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14687
14688 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
14689 let autoscroll = self
14690 .selections
14691 .all::<Point>(cx)
14692 .iter()
14693 .any(|selection| crease.range().overlaps(&selection.range()));
14694
14695 self.fold_creases(vec![crease], autoscroll, window, cx);
14696 }
14697 }
14698
14699 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
14700 if self.is_singleton(cx) {
14701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14702 let buffer = &display_map.buffer_snapshot;
14703 let selections = self.selections.all::<Point>(cx);
14704 let ranges = selections
14705 .iter()
14706 .map(|s| {
14707 let range = s.display_range(&display_map).sorted();
14708 let mut start = range.start.to_point(&display_map);
14709 let mut end = range.end.to_point(&display_map);
14710 start.column = 0;
14711 end.column = buffer.line_len(MultiBufferRow(end.row));
14712 start..end
14713 })
14714 .collect::<Vec<_>>();
14715
14716 self.unfold_ranges(&ranges, true, true, cx);
14717 } else {
14718 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14719 let buffer_ids = self
14720 .selections
14721 .disjoint_anchor_ranges()
14722 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14723 .collect::<HashSet<_>>();
14724 for buffer_id in buffer_ids {
14725 self.unfold_buffer(buffer_id, cx);
14726 }
14727 }
14728 }
14729
14730 pub fn unfold_recursive(
14731 &mut self,
14732 _: &UnfoldRecursive,
14733 _window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) {
14736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14737 let selections = self.selections.all::<Point>(cx);
14738 let ranges = selections
14739 .iter()
14740 .map(|s| {
14741 let mut range = s.display_range(&display_map).sorted();
14742 *range.start.column_mut() = 0;
14743 *range.end.column_mut() = display_map.line_len(range.end.row());
14744 let start = range.start.to_point(&display_map);
14745 let end = range.end.to_point(&display_map);
14746 start..end
14747 })
14748 .collect::<Vec<_>>();
14749
14750 self.unfold_ranges(&ranges, true, true, cx);
14751 }
14752
14753 pub fn unfold_at(
14754 &mut self,
14755 unfold_at: &UnfoldAt,
14756 _window: &mut Window,
14757 cx: &mut Context<Self>,
14758 ) {
14759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14760
14761 let intersection_range = Point::new(unfold_at.buffer_row.0, 0)
14762 ..Point::new(
14763 unfold_at.buffer_row.0,
14764 display_map.buffer_snapshot.line_len(unfold_at.buffer_row),
14765 );
14766
14767 let autoscroll = self
14768 .selections
14769 .all::<Point>(cx)
14770 .iter()
14771 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
14772
14773 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
14774 }
14775
14776 pub fn unfold_all(
14777 &mut self,
14778 _: &actions::UnfoldAll,
14779 _window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 if self.buffer.read(cx).is_singleton() {
14783 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14784 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
14785 } else {
14786 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
14787 editor
14788 .update(cx, |editor, cx| {
14789 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14790 editor.unfold_buffer(buffer_id, cx);
14791 }
14792 })
14793 .ok();
14794 });
14795 }
14796 }
14797
14798 pub fn fold_selected_ranges(
14799 &mut self,
14800 _: &FoldSelectedRanges,
14801 window: &mut Window,
14802 cx: &mut Context<Self>,
14803 ) {
14804 let selections = self.selections.all_adjusted(cx);
14805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14806 let ranges = selections
14807 .into_iter()
14808 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
14809 .collect::<Vec<_>>();
14810 self.fold_creases(ranges, true, window, cx);
14811 }
14812
14813 pub fn fold_ranges<T: ToOffset + Clone>(
14814 &mut self,
14815 ranges: Vec<Range<T>>,
14816 auto_scroll: bool,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) {
14820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14821 let ranges = ranges
14822 .into_iter()
14823 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
14824 .collect::<Vec<_>>();
14825 self.fold_creases(ranges, auto_scroll, window, cx);
14826 }
14827
14828 pub fn fold_creases<T: ToOffset + Clone>(
14829 &mut self,
14830 creases: Vec<Crease<T>>,
14831 auto_scroll: bool,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) {
14835 if creases.is_empty() {
14836 return;
14837 }
14838
14839 let mut buffers_affected = HashSet::default();
14840 let multi_buffer = self.buffer().read(cx);
14841 for crease in &creases {
14842 if let Some((_, buffer, _)) =
14843 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
14844 {
14845 buffers_affected.insert(buffer.read(cx).remote_id());
14846 };
14847 }
14848
14849 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
14850
14851 if auto_scroll {
14852 self.request_autoscroll(Autoscroll::fit(), cx);
14853 }
14854
14855 cx.notify();
14856
14857 if let Some(active_diagnostics) = self.active_diagnostics.take() {
14858 // Clear diagnostics block when folding a range that contains it.
14859 let snapshot = self.snapshot(window, cx);
14860 if snapshot.intersects_fold(active_diagnostics.primary_range.start) {
14861 drop(snapshot);
14862 self.active_diagnostics = Some(active_diagnostics);
14863 self.dismiss_diagnostics(cx);
14864 } else {
14865 self.active_diagnostics = Some(active_diagnostics);
14866 }
14867 }
14868
14869 self.scrollbar_marker_state.dirty = true;
14870 self.folds_did_change(cx);
14871 }
14872
14873 /// Removes any folds whose ranges intersect any of the given ranges.
14874 pub fn unfold_ranges<T: ToOffset + Clone>(
14875 &mut self,
14876 ranges: &[Range<T>],
14877 inclusive: bool,
14878 auto_scroll: bool,
14879 cx: &mut Context<Self>,
14880 ) {
14881 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14882 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
14883 });
14884 self.folds_did_change(cx);
14885 }
14886
14887 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14888 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
14889 return;
14890 }
14891 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14892 self.display_map.update(cx, |display_map, cx| {
14893 display_map.fold_buffers([buffer_id], cx)
14894 });
14895 cx.emit(EditorEvent::BufferFoldToggled {
14896 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
14897 folded: true,
14898 });
14899 cx.notify();
14900 }
14901
14902 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
14903 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
14904 return;
14905 }
14906 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
14907 self.display_map.update(cx, |display_map, cx| {
14908 display_map.unfold_buffers([buffer_id], cx);
14909 });
14910 cx.emit(EditorEvent::BufferFoldToggled {
14911 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
14912 folded: false,
14913 });
14914 cx.notify();
14915 }
14916
14917 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
14918 self.display_map.read(cx).is_buffer_folded(buffer)
14919 }
14920
14921 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
14922 self.display_map.read(cx).folded_buffers()
14923 }
14924
14925 /// Removes any folds with the given ranges.
14926 pub fn remove_folds_with_type<T: ToOffset + Clone>(
14927 &mut self,
14928 ranges: &[Range<T>],
14929 type_id: TypeId,
14930 auto_scroll: bool,
14931 cx: &mut Context<Self>,
14932 ) {
14933 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
14934 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
14935 });
14936 self.folds_did_change(cx);
14937 }
14938
14939 fn remove_folds_with<T: ToOffset + Clone>(
14940 &mut self,
14941 ranges: &[Range<T>],
14942 auto_scroll: bool,
14943 cx: &mut Context<Self>,
14944 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
14945 ) {
14946 if ranges.is_empty() {
14947 return;
14948 }
14949
14950 let mut buffers_affected = HashSet::default();
14951 let multi_buffer = self.buffer().read(cx);
14952 for range in ranges {
14953 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
14954 buffers_affected.insert(buffer.read(cx).remote_id());
14955 };
14956 }
14957
14958 self.display_map.update(cx, update);
14959
14960 if auto_scroll {
14961 self.request_autoscroll(Autoscroll::fit(), cx);
14962 }
14963
14964 cx.notify();
14965 self.scrollbar_marker_state.dirty = true;
14966 self.active_indent_guides_state.dirty = true;
14967 }
14968
14969 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
14970 self.display_map.read(cx).fold_placeholder.clone()
14971 }
14972
14973 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
14974 self.buffer.update(cx, |buffer, cx| {
14975 buffer.set_all_diff_hunks_expanded(cx);
14976 });
14977 }
14978
14979 pub fn expand_all_diff_hunks(
14980 &mut self,
14981 _: &ExpandAllDiffHunks,
14982 _window: &mut Window,
14983 cx: &mut Context<Self>,
14984 ) {
14985 self.buffer.update(cx, |buffer, cx| {
14986 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
14987 });
14988 }
14989
14990 pub fn toggle_selected_diff_hunks(
14991 &mut self,
14992 _: &ToggleSelectedDiffHunks,
14993 _window: &mut Window,
14994 cx: &mut Context<Self>,
14995 ) {
14996 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
14997 self.toggle_diff_hunks_in_ranges(ranges, cx);
14998 }
14999
15000 pub fn diff_hunks_in_ranges<'a>(
15001 &'a self,
15002 ranges: &'a [Range<Anchor>],
15003 buffer: &'a MultiBufferSnapshot,
15004 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15005 ranges.iter().flat_map(move |range| {
15006 let end_excerpt_id = range.end.excerpt_id;
15007 let range = range.to_point(buffer);
15008 let mut peek_end = range.end;
15009 if range.end.row < buffer.max_row().0 {
15010 peek_end = Point::new(range.end.row + 1, 0);
15011 }
15012 buffer
15013 .diff_hunks_in_range(range.start..peek_end)
15014 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15015 })
15016 }
15017
15018 pub fn has_stageable_diff_hunks_in_ranges(
15019 &self,
15020 ranges: &[Range<Anchor>],
15021 snapshot: &MultiBufferSnapshot,
15022 ) -> bool {
15023 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15024 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15025 }
15026
15027 pub fn toggle_staged_selected_diff_hunks(
15028 &mut self,
15029 _: &::git::ToggleStaged,
15030 _: &mut Window,
15031 cx: &mut Context<Self>,
15032 ) {
15033 let snapshot = self.buffer.read(cx).snapshot(cx);
15034 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15035 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15036 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15037 }
15038
15039 pub fn set_render_diff_hunk_controls(
15040 &mut self,
15041 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15042 cx: &mut Context<Self>,
15043 ) {
15044 self.render_diff_hunk_controls = render_diff_hunk_controls;
15045 cx.notify();
15046 }
15047
15048 pub fn stage_and_next(
15049 &mut self,
15050 _: &::git::StageAndNext,
15051 window: &mut Window,
15052 cx: &mut Context<Self>,
15053 ) {
15054 self.do_stage_or_unstage_and_next(true, window, cx);
15055 }
15056
15057 pub fn unstage_and_next(
15058 &mut self,
15059 _: &::git::UnstageAndNext,
15060 window: &mut Window,
15061 cx: &mut Context<Self>,
15062 ) {
15063 self.do_stage_or_unstage_and_next(false, window, cx);
15064 }
15065
15066 pub fn stage_or_unstage_diff_hunks(
15067 &mut self,
15068 stage: bool,
15069 ranges: Vec<Range<Anchor>>,
15070 cx: &mut Context<Self>,
15071 ) {
15072 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15073 cx.spawn(async move |this, cx| {
15074 task.await?;
15075 this.update(cx, |this, cx| {
15076 let snapshot = this.buffer.read(cx).snapshot(cx);
15077 let chunk_by = this
15078 .diff_hunks_in_ranges(&ranges, &snapshot)
15079 .chunk_by(|hunk| hunk.buffer_id);
15080 for (buffer_id, hunks) in &chunk_by {
15081 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15082 }
15083 })
15084 })
15085 .detach_and_log_err(cx);
15086 }
15087
15088 fn save_buffers_for_ranges_if_needed(
15089 &mut self,
15090 ranges: &[Range<Anchor>],
15091 cx: &mut Context<Editor>,
15092 ) -> Task<Result<()>> {
15093 let multibuffer = self.buffer.read(cx);
15094 let snapshot = multibuffer.read(cx);
15095 let buffer_ids: HashSet<_> = ranges
15096 .iter()
15097 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15098 .collect();
15099 drop(snapshot);
15100
15101 let mut buffers = HashSet::default();
15102 for buffer_id in buffer_ids {
15103 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15104 let buffer = buffer_entity.read(cx);
15105 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15106 {
15107 buffers.insert(buffer_entity);
15108 }
15109 }
15110 }
15111
15112 if let Some(project) = &self.project {
15113 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15114 } else {
15115 Task::ready(Ok(()))
15116 }
15117 }
15118
15119 fn do_stage_or_unstage_and_next(
15120 &mut self,
15121 stage: bool,
15122 window: &mut Window,
15123 cx: &mut Context<Self>,
15124 ) {
15125 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15126
15127 if ranges.iter().any(|range| range.start != range.end) {
15128 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15129 return;
15130 }
15131
15132 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15133 let snapshot = self.snapshot(window, cx);
15134 let position = self.selections.newest::<Point>(cx).head();
15135 let mut row = snapshot
15136 .buffer_snapshot
15137 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15138 .find(|hunk| hunk.row_range.start.0 > position.row)
15139 .map(|hunk| hunk.row_range.start);
15140
15141 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15142 // Outside of the project diff editor, wrap around to the beginning.
15143 if !all_diff_hunks_expanded {
15144 row = row.or_else(|| {
15145 snapshot
15146 .buffer_snapshot
15147 .diff_hunks_in_range(Point::zero()..position)
15148 .find(|hunk| hunk.row_range.end.0 < position.row)
15149 .map(|hunk| hunk.row_range.start)
15150 });
15151 }
15152
15153 if let Some(row) = row {
15154 let destination = Point::new(row.0, 0);
15155 let autoscroll = Autoscroll::center();
15156
15157 self.unfold_ranges(&[destination..destination], false, false, cx);
15158 self.change_selections(Some(autoscroll), window, cx, |s| {
15159 s.select_ranges([destination..destination]);
15160 });
15161 }
15162 }
15163
15164 fn do_stage_or_unstage(
15165 &self,
15166 stage: bool,
15167 buffer_id: BufferId,
15168 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15169 cx: &mut App,
15170 ) -> Option<()> {
15171 let project = self.project.as_ref()?;
15172 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15173 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15174 let buffer_snapshot = buffer.read(cx).snapshot();
15175 let file_exists = buffer_snapshot
15176 .file()
15177 .is_some_and(|file| file.disk_state().exists());
15178 diff.update(cx, |diff, cx| {
15179 diff.stage_or_unstage_hunks(
15180 stage,
15181 &hunks
15182 .map(|hunk| buffer_diff::DiffHunk {
15183 buffer_range: hunk.buffer_range,
15184 diff_base_byte_range: hunk.diff_base_byte_range,
15185 secondary_status: hunk.secondary_status,
15186 range: Point::zero()..Point::zero(), // unused
15187 })
15188 .collect::<Vec<_>>(),
15189 &buffer_snapshot,
15190 file_exists,
15191 cx,
15192 )
15193 });
15194 None
15195 }
15196
15197 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15198 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15199 self.buffer
15200 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15201 }
15202
15203 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15204 self.buffer.update(cx, |buffer, cx| {
15205 let ranges = vec![Anchor::min()..Anchor::max()];
15206 if !buffer.all_diff_hunks_expanded()
15207 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15208 {
15209 buffer.collapse_diff_hunks(ranges, cx);
15210 true
15211 } else {
15212 false
15213 }
15214 })
15215 }
15216
15217 fn toggle_diff_hunks_in_ranges(
15218 &mut self,
15219 ranges: Vec<Range<Anchor>>,
15220 cx: &mut Context<Editor>,
15221 ) {
15222 self.buffer.update(cx, |buffer, cx| {
15223 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15224 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15225 })
15226 }
15227
15228 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15229 self.buffer.update(cx, |buffer, cx| {
15230 let snapshot = buffer.snapshot(cx);
15231 let excerpt_id = range.end.excerpt_id;
15232 let point_range = range.to_point(&snapshot);
15233 let expand = !buffer.single_hunk_is_expanded(range, cx);
15234 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15235 })
15236 }
15237
15238 pub(crate) fn apply_all_diff_hunks(
15239 &mut self,
15240 _: &ApplyAllDiffHunks,
15241 window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15245
15246 let buffers = self.buffer.read(cx).all_buffers();
15247 for branch_buffer in buffers {
15248 branch_buffer.update(cx, |branch_buffer, cx| {
15249 branch_buffer.merge_into_base(Vec::new(), cx);
15250 });
15251 }
15252
15253 if let Some(project) = self.project.clone() {
15254 self.save(true, project, window, cx).detach_and_log_err(cx);
15255 }
15256 }
15257
15258 pub(crate) fn apply_selected_diff_hunks(
15259 &mut self,
15260 _: &ApplyDiffHunk,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15265 let snapshot = self.snapshot(window, cx);
15266 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15267 let mut ranges_by_buffer = HashMap::default();
15268 self.transact(window, cx, |editor, _window, cx| {
15269 for hunk in hunks {
15270 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15271 ranges_by_buffer
15272 .entry(buffer.clone())
15273 .or_insert_with(Vec::new)
15274 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15275 }
15276 }
15277
15278 for (buffer, ranges) in ranges_by_buffer {
15279 buffer.update(cx, |buffer, cx| {
15280 buffer.merge_into_base(ranges, cx);
15281 });
15282 }
15283 });
15284
15285 if let Some(project) = self.project.clone() {
15286 self.save(true, project, window, cx).detach_and_log_err(cx);
15287 }
15288 }
15289
15290 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15291 if hovered != self.gutter_hovered {
15292 self.gutter_hovered = hovered;
15293 cx.notify();
15294 }
15295 }
15296
15297 pub fn insert_blocks(
15298 &mut self,
15299 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15300 autoscroll: Option<Autoscroll>,
15301 cx: &mut Context<Self>,
15302 ) -> Vec<CustomBlockId> {
15303 let blocks = self
15304 .display_map
15305 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15306 if let Some(autoscroll) = autoscroll {
15307 self.request_autoscroll(autoscroll, cx);
15308 }
15309 cx.notify();
15310 blocks
15311 }
15312
15313 pub fn resize_blocks(
15314 &mut self,
15315 heights: HashMap<CustomBlockId, u32>,
15316 autoscroll: Option<Autoscroll>,
15317 cx: &mut Context<Self>,
15318 ) {
15319 self.display_map
15320 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15321 if let Some(autoscroll) = autoscroll {
15322 self.request_autoscroll(autoscroll, cx);
15323 }
15324 cx.notify();
15325 }
15326
15327 pub fn replace_blocks(
15328 &mut self,
15329 renderers: HashMap<CustomBlockId, RenderBlock>,
15330 autoscroll: Option<Autoscroll>,
15331 cx: &mut Context<Self>,
15332 ) {
15333 self.display_map
15334 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15335 if let Some(autoscroll) = autoscroll {
15336 self.request_autoscroll(autoscroll, cx);
15337 }
15338 cx.notify();
15339 }
15340
15341 pub fn remove_blocks(
15342 &mut self,
15343 block_ids: HashSet<CustomBlockId>,
15344 autoscroll: Option<Autoscroll>,
15345 cx: &mut Context<Self>,
15346 ) {
15347 self.display_map.update(cx, |display_map, cx| {
15348 display_map.remove_blocks(block_ids, cx)
15349 });
15350 if let Some(autoscroll) = autoscroll {
15351 self.request_autoscroll(autoscroll, cx);
15352 }
15353 cx.notify();
15354 }
15355
15356 pub fn row_for_block(
15357 &self,
15358 block_id: CustomBlockId,
15359 cx: &mut Context<Self>,
15360 ) -> Option<DisplayRow> {
15361 self.display_map
15362 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15363 }
15364
15365 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15366 self.focused_block = Some(focused_block);
15367 }
15368
15369 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15370 self.focused_block.take()
15371 }
15372
15373 pub fn insert_creases(
15374 &mut self,
15375 creases: impl IntoIterator<Item = Crease<Anchor>>,
15376 cx: &mut Context<Self>,
15377 ) -> Vec<CreaseId> {
15378 self.display_map
15379 .update(cx, |map, cx| map.insert_creases(creases, cx))
15380 }
15381
15382 pub fn remove_creases(
15383 &mut self,
15384 ids: impl IntoIterator<Item = CreaseId>,
15385 cx: &mut Context<Self>,
15386 ) {
15387 self.display_map
15388 .update(cx, |map, cx| map.remove_creases(ids, cx));
15389 }
15390
15391 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15392 self.display_map
15393 .update(cx, |map, cx| map.snapshot(cx))
15394 .longest_row()
15395 }
15396
15397 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15398 self.display_map
15399 .update(cx, |map, cx| map.snapshot(cx))
15400 .max_point()
15401 }
15402
15403 pub fn text(&self, cx: &App) -> String {
15404 self.buffer.read(cx).read(cx).text()
15405 }
15406
15407 pub fn is_empty(&self, cx: &App) -> bool {
15408 self.buffer.read(cx).read(cx).is_empty()
15409 }
15410
15411 pub fn text_option(&self, cx: &App) -> Option<String> {
15412 let text = self.text(cx);
15413 let text = text.trim();
15414
15415 if text.is_empty() {
15416 return None;
15417 }
15418
15419 Some(text.to_string())
15420 }
15421
15422 pub fn set_text(
15423 &mut self,
15424 text: impl Into<Arc<str>>,
15425 window: &mut Window,
15426 cx: &mut Context<Self>,
15427 ) {
15428 self.transact(window, cx, |this, _, cx| {
15429 this.buffer
15430 .read(cx)
15431 .as_singleton()
15432 .expect("you can only call set_text on editors for singleton buffers")
15433 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15434 });
15435 }
15436
15437 pub fn display_text(&self, cx: &mut App) -> String {
15438 self.display_map
15439 .update(cx, |map, cx| map.snapshot(cx))
15440 .text()
15441 }
15442
15443 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15444 let mut wrap_guides = smallvec::smallvec![];
15445
15446 if self.show_wrap_guides == Some(false) {
15447 return wrap_guides;
15448 }
15449
15450 let settings = self.buffer.read(cx).language_settings(cx);
15451 if settings.show_wrap_guides {
15452 match self.soft_wrap_mode(cx) {
15453 SoftWrap::Column(soft_wrap) => {
15454 wrap_guides.push((soft_wrap as usize, true));
15455 }
15456 SoftWrap::Bounded(soft_wrap) => {
15457 wrap_guides.push((soft_wrap as usize, true));
15458 }
15459 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15460 }
15461 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15462 }
15463
15464 wrap_guides
15465 }
15466
15467 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15468 let settings = self.buffer.read(cx).language_settings(cx);
15469 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15470 match mode {
15471 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15472 SoftWrap::None
15473 }
15474 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15475 language_settings::SoftWrap::PreferredLineLength => {
15476 SoftWrap::Column(settings.preferred_line_length)
15477 }
15478 language_settings::SoftWrap::Bounded => {
15479 SoftWrap::Bounded(settings.preferred_line_length)
15480 }
15481 }
15482 }
15483
15484 pub fn set_soft_wrap_mode(
15485 &mut self,
15486 mode: language_settings::SoftWrap,
15487
15488 cx: &mut Context<Self>,
15489 ) {
15490 self.soft_wrap_mode_override = Some(mode);
15491 cx.notify();
15492 }
15493
15494 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15495 self.hard_wrap = hard_wrap;
15496 cx.notify();
15497 }
15498
15499 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15500 self.text_style_refinement = Some(style);
15501 }
15502
15503 /// called by the Element so we know what style we were most recently rendered with.
15504 pub(crate) fn set_style(
15505 &mut self,
15506 style: EditorStyle,
15507 window: &mut Window,
15508 cx: &mut Context<Self>,
15509 ) {
15510 let rem_size = window.rem_size();
15511 self.display_map.update(cx, |map, cx| {
15512 map.set_font(
15513 style.text.font(),
15514 style.text.font_size.to_pixels(rem_size),
15515 cx,
15516 )
15517 });
15518 self.style = Some(style);
15519 }
15520
15521 pub fn style(&self) -> Option<&EditorStyle> {
15522 self.style.as_ref()
15523 }
15524
15525 // Called by the element. This method is not designed to be called outside of the editor
15526 // element's layout code because it does not notify when rewrapping is computed synchronously.
15527 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15528 self.display_map
15529 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15530 }
15531
15532 pub fn set_soft_wrap(&mut self) {
15533 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15534 }
15535
15536 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15537 if self.soft_wrap_mode_override.is_some() {
15538 self.soft_wrap_mode_override.take();
15539 } else {
15540 let soft_wrap = match self.soft_wrap_mode(cx) {
15541 SoftWrap::GitDiff => return,
15542 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15543 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15544 language_settings::SoftWrap::None
15545 }
15546 };
15547 self.soft_wrap_mode_override = Some(soft_wrap);
15548 }
15549 cx.notify();
15550 }
15551
15552 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15553 let Some(workspace) = self.workspace() else {
15554 return;
15555 };
15556 let fs = workspace.read(cx).app_state().fs.clone();
15557 let current_show = TabBarSettings::get_global(cx).show;
15558 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15559 setting.show = Some(!current_show);
15560 });
15561 }
15562
15563 pub fn toggle_indent_guides(
15564 &mut self,
15565 _: &ToggleIndentGuides,
15566 _: &mut Window,
15567 cx: &mut Context<Self>,
15568 ) {
15569 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15570 self.buffer
15571 .read(cx)
15572 .language_settings(cx)
15573 .indent_guides
15574 .enabled
15575 });
15576 self.show_indent_guides = Some(!currently_enabled);
15577 cx.notify();
15578 }
15579
15580 fn should_show_indent_guides(&self) -> Option<bool> {
15581 self.show_indent_guides
15582 }
15583
15584 pub fn toggle_line_numbers(
15585 &mut self,
15586 _: &ToggleLineNumbers,
15587 _: &mut Window,
15588 cx: &mut Context<Self>,
15589 ) {
15590 let mut editor_settings = EditorSettings::get_global(cx).clone();
15591 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15592 EditorSettings::override_global(editor_settings, cx);
15593 }
15594
15595 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15596 if let Some(show_line_numbers) = self.show_line_numbers {
15597 return show_line_numbers;
15598 }
15599 EditorSettings::get_global(cx).gutter.line_numbers
15600 }
15601
15602 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15603 self.use_relative_line_numbers
15604 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15605 }
15606
15607 pub fn toggle_relative_line_numbers(
15608 &mut self,
15609 _: &ToggleRelativeLineNumbers,
15610 _: &mut Window,
15611 cx: &mut Context<Self>,
15612 ) {
15613 let is_relative = self.should_use_relative_line_numbers(cx);
15614 self.set_relative_line_number(Some(!is_relative), cx)
15615 }
15616
15617 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15618 self.use_relative_line_numbers = is_relative;
15619 cx.notify();
15620 }
15621
15622 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15623 self.show_gutter = show_gutter;
15624 cx.notify();
15625 }
15626
15627 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15628 self.show_scrollbars = show_scrollbars;
15629 cx.notify();
15630 }
15631
15632 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15633 self.show_line_numbers = Some(show_line_numbers);
15634 cx.notify();
15635 }
15636
15637 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15638 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15639 cx.notify();
15640 }
15641
15642 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15643 self.show_code_actions = Some(show_code_actions);
15644 cx.notify();
15645 }
15646
15647 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15648 self.show_runnables = Some(show_runnables);
15649 cx.notify();
15650 }
15651
15652 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
15653 self.show_breakpoints = Some(show_breakpoints);
15654 cx.notify();
15655 }
15656
15657 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
15658 if self.display_map.read(cx).masked != masked {
15659 self.display_map.update(cx, |map, _| map.masked = masked);
15660 }
15661 cx.notify()
15662 }
15663
15664 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
15665 self.show_wrap_guides = Some(show_wrap_guides);
15666 cx.notify();
15667 }
15668
15669 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
15670 self.show_indent_guides = Some(show_indent_guides);
15671 cx.notify();
15672 }
15673
15674 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
15675 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
15676 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
15677 if let Some(dir) = file.abs_path(cx).parent() {
15678 return Some(dir.to_owned());
15679 }
15680 }
15681
15682 if let Some(project_path) = buffer.read(cx).project_path(cx) {
15683 return Some(project_path.path.to_path_buf());
15684 }
15685 }
15686
15687 None
15688 }
15689
15690 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
15691 self.active_excerpt(cx)?
15692 .1
15693 .read(cx)
15694 .file()
15695 .and_then(|f| f.as_local())
15696 }
15697
15698 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15699 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15700 let buffer = buffer.read(cx);
15701 if let Some(project_path) = buffer.project_path(cx) {
15702 let project = self.project.as_ref()?.read(cx);
15703 project.absolute_path(&project_path, cx)
15704 } else {
15705 buffer
15706 .file()
15707 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
15708 }
15709 })
15710 }
15711
15712 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
15713 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
15714 let project_path = buffer.read(cx).project_path(cx)?;
15715 let project = self.project.as_ref()?.read(cx);
15716 let entry = project.entry_for_path(&project_path, cx)?;
15717 let path = entry.path.to_path_buf();
15718 Some(path)
15719 })
15720 }
15721
15722 pub fn reveal_in_finder(
15723 &mut self,
15724 _: &RevealInFileManager,
15725 _window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) {
15728 if let Some(target) = self.target_file(cx) {
15729 cx.reveal_path(&target.abs_path(cx));
15730 }
15731 }
15732
15733 pub fn copy_path(
15734 &mut self,
15735 _: &zed_actions::workspace::CopyPath,
15736 _window: &mut Window,
15737 cx: &mut Context<Self>,
15738 ) {
15739 if let Some(path) = self.target_file_abs_path(cx) {
15740 if let Some(path) = path.to_str() {
15741 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15742 }
15743 }
15744 }
15745
15746 pub fn copy_relative_path(
15747 &mut self,
15748 _: &zed_actions::workspace::CopyRelativePath,
15749 _window: &mut Window,
15750 cx: &mut Context<Self>,
15751 ) {
15752 if let Some(path) = self.target_file_path(cx) {
15753 if let Some(path) = path.to_str() {
15754 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
15755 }
15756 }
15757 }
15758
15759 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
15760 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
15761 buffer.read(cx).project_path(cx)
15762 } else {
15763 None
15764 }
15765 }
15766
15767 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) {
15768 let _ = maybe!({
15769 let breakpoint_store = self.breakpoint_store.as_ref()?;
15770
15771 let Some((_, _, active_position)) =
15772 breakpoint_store.read(cx).active_position().cloned()
15773 else {
15774 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15775 return None;
15776 };
15777
15778 let snapshot = self
15779 .project
15780 .as_ref()?
15781 .read(cx)
15782 .buffer_for_id(active_position.buffer_id?, cx)?
15783 .read(cx)
15784 .snapshot();
15785
15786 for (id, ExcerptRange { context, .. }) in self
15787 .buffer
15788 .read(cx)
15789 .excerpts_for_buffer(active_position.buffer_id?, cx)
15790 {
15791 if context.start.cmp(&active_position, &snapshot).is_ge()
15792 || context.end.cmp(&active_position, &snapshot).is_lt()
15793 {
15794 continue;
15795 }
15796 let snapshot = self.buffer.read(cx).snapshot(cx);
15797 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
15798
15799 self.clear_row_highlights::<DebugCurrentRowHighlight>();
15800 self.go_to_line::<DebugCurrentRowHighlight>(
15801 multibuffer_anchor,
15802 Some(cx.theme().colors().editor_debugger_active_line_background),
15803 window,
15804 cx,
15805 );
15806
15807 cx.notify();
15808 }
15809
15810 Some(())
15811 });
15812 }
15813
15814 pub fn copy_file_name_without_extension(
15815 &mut self,
15816 _: &CopyFileNameWithoutExtension,
15817 _: &mut Window,
15818 cx: &mut Context<Self>,
15819 ) {
15820 if let Some(file) = self.target_file(cx) {
15821 if let Some(file_stem) = file.path().file_stem() {
15822 if let Some(name) = file_stem.to_str() {
15823 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15824 }
15825 }
15826 }
15827 }
15828
15829 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
15830 if let Some(file) = self.target_file(cx) {
15831 if let Some(file_name) = file.path().file_name() {
15832 if let Some(name) = file_name.to_str() {
15833 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
15834 }
15835 }
15836 }
15837 }
15838
15839 pub fn toggle_git_blame(
15840 &mut self,
15841 _: &::git::Blame,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 self.show_git_blame_gutter = !self.show_git_blame_gutter;
15846
15847 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
15848 self.start_git_blame(true, window, cx);
15849 }
15850
15851 cx.notify();
15852 }
15853
15854 pub fn toggle_git_blame_inline(
15855 &mut self,
15856 _: &ToggleGitBlameInline,
15857 window: &mut Window,
15858 cx: &mut Context<Self>,
15859 ) {
15860 self.toggle_git_blame_inline_internal(true, window, cx);
15861 cx.notify();
15862 }
15863
15864 pub fn git_blame_inline_enabled(&self) -> bool {
15865 self.git_blame_inline_enabled
15866 }
15867
15868 pub fn toggle_selection_menu(
15869 &mut self,
15870 _: &ToggleSelectionMenu,
15871 _: &mut Window,
15872 cx: &mut Context<Self>,
15873 ) {
15874 self.show_selection_menu = self
15875 .show_selection_menu
15876 .map(|show_selections_menu| !show_selections_menu)
15877 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
15878
15879 cx.notify();
15880 }
15881
15882 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
15883 self.show_selection_menu
15884 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
15885 }
15886
15887 fn start_git_blame(
15888 &mut self,
15889 user_triggered: bool,
15890 window: &mut Window,
15891 cx: &mut Context<Self>,
15892 ) {
15893 if let Some(project) = self.project.as_ref() {
15894 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
15895 return;
15896 };
15897
15898 if buffer.read(cx).file().is_none() {
15899 return;
15900 }
15901
15902 let focused = self.focus_handle(cx).contains_focused(window, cx);
15903
15904 let project = project.clone();
15905 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
15906 self.blame_subscription =
15907 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
15908 self.blame = Some(blame);
15909 }
15910 }
15911
15912 fn toggle_git_blame_inline_internal(
15913 &mut self,
15914 user_triggered: bool,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 if self.git_blame_inline_enabled {
15919 self.git_blame_inline_enabled = false;
15920 self.show_git_blame_inline = false;
15921 self.show_git_blame_inline_delay_task.take();
15922 } else {
15923 self.git_blame_inline_enabled = true;
15924 self.start_git_blame_inline(user_triggered, window, cx);
15925 }
15926
15927 cx.notify();
15928 }
15929
15930 fn start_git_blame_inline(
15931 &mut self,
15932 user_triggered: bool,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 self.start_git_blame(user_triggered, window, cx);
15937
15938 if ProjectSettings::get_global(cx)
15939 .git
15940 .inline_blame_delay()
15941 .is_some()
15942 {
15943 self.start_inline_blame_timer(window, cx);
15944 } else {
15945 self.show_git_blame_inline = true
15946 }
15947 }
15948
15949 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
15950 self.blame.as_ref()
15951 }
15952
15953 pub fn show_git_blame_gutter(&self) -> bool {
15954 self.show_git_blame_gutter
15955 }
15956
15957 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
15958 self.show_git_blame_gutter && self.has_blame_entries(cx)
15959 }
15960
15961 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
15962 self.show_git_blame_inline
15963 && (self.focus_handle.is_focused(window)
15964 || self
15965 .git_blame_inline_tooltip
15966 .as_ref()
15967 .and_then(|t| t.upgrade())
15968 .is_some())
15969 && !self.newest_selection_head_on_empty_line(cx)
15970 && self.has_blame_entries(cx)
15971 }
15972
15973 fn has_blame_entries(&self, cx: &App) -> bool {
15974 self.blame()
15975 .map_or(false, |blame| blame.read(cx).has_generated_entries())
15976 }
15977
15978 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
15979 let cursor_anchor = self.selections.newest_anchor().head();
15980
15981 let snapshot = self.buffer.read(cx).snapshot(cx);
15982 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
15983
15984 snapshot.line_len(buffer_row) == 0
15985 }
15986
15987 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
15988 let buffer_and_selection = maybe!({
15989 let selection = self.selections.newest::<Point>(cx);
15990 let selection_range = selection.range();
15991
15992 let multi_buffer = self.buffer().read(cx);
15993 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15994 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
15995
15996 let (buffer, range, _) = if selection.reversed {
15997 buffer_ranges.first()
15998 } else {
15999 buffer_ranges.last()
16000 }?;
16001
16002 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16003 ..text::ToPoint::to_point(&range.end, &buffer).row;
16004 Some((
16005 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16006 selection,
16007 ))
16008 });
16009
16010 let Some((buffer, selection)) = buffer_and_selection else {
16011 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16012 };
16013
16014 let Some(project) = self.project.as_ref() else {
16015 return Task::ready(Err(anyhow!("editor does not have project")));
16016 };
16017
16018 project.update(cx, |project, cx| {
16019 project.get_permalink_to_line(&buffer, selection, cx)
16020 })
16021 }
16022
16023 pub fn copy_permalink_to_line(
16024 &mut self,
16025 _: &CopyPermalinkToLine,
16026 window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 let permalink_task = self.get_permalink_to_line(cx);
16030 let workspace = self.workspace();
16031
16032 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16033 Ok(permalink) => {
16034 cx.update(|_, cx| {
16035 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16036 })
16037 .ok();
16038 }
16039 Err(err) => {
16040 let message = format!("Failed to copy permalink: {err}");
16041
16042 Err::<(), anyhow::Error>(err).log_err();
16043
16044 if let Some(workspace) = workspace {
16045 workspace
16046 .update_in(cx, |workspace, _, cx| {
16047 struct CopyPermalinkToLine;
16048
16049 workspace.show_toast(
16050 Toast::new(
16051 NotificationId::unique::<CopyPermalinkToLine>(),
16052 message,
16053 ),
16054 cx,
16055 )
16056 })
16057 .ok();
16058 }
16059 }
16060 })
16061 .detach();
16062 }
16063
16064 pub fn copy_file_location(
16065 &mut self,
16066 _: &CopyFileLocation,
16067 _: &mut Window,
16068 cx: &mut Context<Self>,
16069 ) {
16070 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16071 if let Some(file) = self.target_file(cx) {
16072 if let Some(path) = file.path().to_str() {
16073 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16074 }
16075 }
16076 }
16077
16078 pub fn open_permalink_to_line(
16079 &mut self,
16080 _: &OpenPermalinkToLine,
16081 window: &mut Window,
16082 cx: &mut Context<Self>,
16083 ) {
16084 let permalink_task = self.get_permalink_to_line(cx);
16085 let workspace = self.workspace();
16086
16087 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16088 Ok(permalink) => {
16089 cx.update(|_, cx| {
16090 cx.open_url(permalink.as_ref());
16091 })
16092 .ok();
16093 }
16094 Err(err) => {
16095 let message = format!("Failed to open permalink: {err}");
16096
16097 Err::<(), anyhow::Error>(err).log_err();
16098
16099 if let Some(workspace) = workspace {
16100 workspace
16101 .update(cx, |workspace, cx| {
16102 struct OpenPermalinkToLine;
16103
16104 workspace.show_toast(
16105 Toast::new(
16106 NotificationId::unique::<OpenPermalinkToLine>(),
16107 message,
16108 ),
16109 cx,
16110 )
16111 })
16112 .ok();
16113 }
16114 }
16115 })
16116 .detach();
16117 }
16118
16119 pub fn insert_uuid_v4(
16120 &mut self,
16121 _: &InsertUuidV4,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 self.insert_uuid(UuidVersion::V4, window, cx);
16126 }
16127
16128 pub fn insert_uuid_v7(
16129 &mut self,
16130 _: &InsertUuidV7,
16131 window: &mut Window,
16132 cx: &mut Context<Self>,
16133 ) {
16134 self.insert_uuid(UuidVersion::V7, window, cx);
16135 }
16136
16137 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16138 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16139 self.transact(window, cx, |this, window, cx| {
16140 let edits = this
16141 .selections
16142 .all::<Point>(cx)
16143 .into_iter()
16144 .map(|selection| {
16145 let uuid = match version {
16146 UuidVersion::V4 => uuid::Uuid::new_v4(),
16147 UuidVersion::V7 => uuid::Uuid::now_v7(),
16148 };
16149
16150 (selection.range(), uuid.to_string())
16151 });
16152 this.edit(edits, cx);
16153 this.refresh_inline_completion(true, false, window, cx);
16154 });
16155 }
16156
16157 pub fn open_selections_in_multibuffer(
16158 &mut self,
16159 _: &OpenSelectionsInMultibuffer,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) {
16163 let multibuffer = self.buffer.read(cx);
16164
16165 let Some(buffer) = multibuffer.as_singleton() else {
16166 return;
16167 };
16168
16169 let Some(workspace) = self.workspace() else {
16170 return;
16171 };
16172
16173 let locations = self
16174 .selections
16175 .disjoint_anchors()
16176 .iter()
16177 .map(|range| Location {
16178 buffer: buffer.clone(),
16179 range: range.start.text_anchor..range.end.text_anchor,
16180 })
16181 .collect::<Vec<_>>();
16182
16183 let title = multibuffer.title(cx).to_string();
16184
16185 cx.spawn_in(window, async move |_, cx| {
16186 workspace.update_in(cx, |workspace, window, cx| {
16187 Self::open_locations_in_multibuffer(
16188 workspace,
16189 locations,
16190 format!("Selections for '{title}'"),
16191 false,
16192 MultibufferSelectionMode::All,
16193 window,
16194 cx,
16195 );
16196 })
16197 })
16198 .detach();
16199 }
16200
16201 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16202 /// last highlight added will be used.
16203 ///
16204 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16205 pub fn highlight_rows<T: 'static>(
16206 &mut self,
16207 range: Range<Anchor>,
16208 color: Hsla,
16209 should_autoscroll: bool,
16210 cx: &mut Context<Self>,
16211 ) {
16212 let snapshot = self.buffer().read(cx).snapshot(cx);
16213 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16214 let ix = row_highlights.binary_search_by(|highlight| {
16215 Ordering::Equal
16216 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16217 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16218 });
16219
16220 if let Err(mut ix) = ix {
16221 let index = post_inc(&mut self.highlight_order);
16222
16223 // If this range intersects with the preceding highlight, then merge it with
16224 // the preceding highlight. Otherwise insert a new highlight.
16225 let mut merged = false;
16226 if ix > 0 {
16227 let prev_highlight = &mut row_highlights[ix - 1];
16228 if prev_highlight
16229 .range
16230 .end
16231 .cmp(&range.start, &snapshot)
16232 .is_ge()
16233 {
16234 ix -= 1;
16235 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16236 prev_highlight.range.end = range.end;
16237 }
16238 merged = true;
16239 prev_highlight.index = index;
16240 prev_highlight.color = color;
16241 prev_highlight.should_autoscroll = should_autoscroll;
16242 }
16243 }
16244
16245 if !merged {
16246 row_highlights.insert(
16247 ix,
16248 RowHighlight {
16249 range: range.clone(),
16250 index,
16251 color,
16252 should_autoscroll,
16253 },
16254 );
16255 }
16256
16257 // If any of the following highlights intersect with this one, merge them.
16258 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16259 let highlight = &row_highlights[ix];
16260 if next_highlight
16261 .range
16262 .start
16263 .cmp(&highlight.range.end, &snapshot)
16264 .is_le()
16265 {
16266 if next_highlight
16267 .range
16268 .end
16269 .cmp(&highlight.range.end, &snapshot)
16270 .is_gt()
16271 {
16272 row_highlights[ix].range.end = next_highlight.range.end;
16273 }
16274 row_highlights.remove(ix + 1);
16275 } else {
16276 break;
16277 }
16278 }
16279 }
16280 }
16281
16282 /// Remove any highlighted row ranges of the given type that intersect the
16283 /// given ranges.
16284 pub fn remove_highlighted_rows<T: 'static>(
16285 &mut self,
16286 ranges_to_remove: Vec<Range<Anchor>>,
16287 cx: &mut Context<Self>,
16288 ) {
16289 let snapshot = self.buffer().read(cx).snapshot(cx);
16290 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16291 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16292 row_highlights.retain(|highlight| {
16293 while let Some(range_to_remove) = ranges_to_remove.peek() {
16294 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16295 Ordering::Less | Ordering::Equal => {
16296 ranges_to_remove.next();
16297 }
16298 Ordering::Greater => {
16299 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16300 Ordering::Less | Ordering::Equal => {
16301 return false;
16302 }
16303 Ordering::Greater => break,
16304 }
16305 }
16306 }
16307 }
16308
16309 true
16310 })
16311 }
16312
16313 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16314 pub fn clear_row_highlights<T: 'static>(&mut self) {
16315 self.highlighted_rows.remove(&TypeId::of::<T>());
16316 }
16317
16318 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16319 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16320 self.highlighted_rows
16321 .get(&TypeId::of::<T>())
16322 .map_or(&[] as &[_], |vec| vec.as_slice())
16323 .iter()
16324 .map(|highlight| (highlight.range.clone(), highlight.color))
16325 }
16326
16327 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16328 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16329 /// Allows to ignore certain kinds of highlights.
16330 pub fn highlighted_display_rows(
16331 &self,
16332 window: &mut Window,
16333 cx: &mut App,
16334 ) -> BTreeMap<DisplayRow, LineHighlight> {
16335 let snapshot = self.snapshot(window, cx);
16336 let mut used_highlight_orders = HashMap::default();
16337 self.highlighted_rows
16338 .iter()
16339 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16340 .fold(
16341 BTreeMap::<DisplayRow, LineHighlight>::new(),
16342 |mut unique_rows, highlight| {
16343 let start = highlight.range.start.to_display_point(&snapshot);
16344 let end = highlight.range.end.to_display_point(&snapshot);
16345 let start_row = start.row().0;
16346 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16347 && end.column() == 0
16348 {
16349 end.row().0.saturating_sub(1)
16350 } else {
16351 end.row().0
16352 };
16353 for row in start_row..=end_row {
16354 let used_index =
16355 used_highlight_orders.entry(row).or_insert(highlight.index);
16356 if highlight.index >= *used_index {
16357 *used_index = highlight.index;
16358 unique_rows.insert(DisplayRow(row), highlight.color.into());
16359 }
16360 }
16361 unique_rows
16362 },
16363 )
16364 }
16365
16366 pub fn highlighted_display_row_for_autoscroll(
16367 &self,
16368 snapshot: &DisplaySnapshot,
16369 ) -> Option<DisplayRow> {
16370 self.highlighted_rows
16371 .values()
16372 .flat_map(|highlighted_rows| highlighted_rows.iter())
16373 .filter_map(|highlight| {
16374 if highlight.should_autoscroll {
16375 Some(highlight.range.start.to_display_point(snapshot).row())
16376 } else {
16377 None
16378 }
16379 })
16380 .min()
16381 }
16382
16383 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16384 self.highlight_background::<SearchWithinRange>(
16385 ranges,
16386 |colors| colors.editor_document_highlight_read_background,
16387 cx,
16388 )
16389 }
16390
16391 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16392 self.breadcrumb_header = Some(new_header);
16393 }
16394
16395 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16396 self.clear_background_highlights::<SearchWithinRange>(cx);
16397 }
16398
16399 pub fn highlight_background<T: 'static>(
16400 &mut self,
16401 ranges: &[Range<Anchor>],
16402 color_fetcher: fn(&ThemeColors) -> Hsla,
16403 cx: &mut Context<Self>,
16404 ) {
16405 self.background_highlights
16406 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16407 self.scrollbar_marker_state.dirty = true;
16408 cx.notify();
16409 }
16410
16411 pub fn clear_background_highlights<T: 'static>(
16412 &mut self,
16413 cx: &mut Context<Self>,
16414 ) -> Option<BackgroundHighlight> {
16415 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16416 if !text_highlights.1.is_empty() {
16417 self.scrollbar_marker_state.dirty = true;
16418 cx.notify();
16419 }
16420 Some(text_highlights)
16421 }
16422
16423 pub fn highlight_gutter<T: 'static>(
16424 &mut self,
16425 ranges: &[Range<Anchor>],
16426 color_fetcher: fn(&App) -> Hsla,
16427 cx: &mut Context<Self>,
16428 ) {
16429 self.gutter_highlights
16430 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16431 cx.notify();
16432 }
16433
16434 pub fn clear_gutter_highlights<T: 'static>(
16435 &mut self,
16436 cx: &mut Context<Self>,
16437 ) -> Option<GutterHighlight> {
16438 cx.notify();
16439 self.gutter_highlights.remove(&TypeId::of::<T>())
16440 }
16441
16442 #[cfg(feature = "test-support")]
16443 pub fn all_text_background_highlights(
16444 &self,
16445 window: &mut Window,
16446 cx: &mut Context<Self>,
16447 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16448 let snapshot = self.snapshot(window, cx);
16449 let buffer = &snapshot.buffer_snapshot;
16450 let start = buffer.anchor_before(0);
16451 let end = buffer.anchor_after(buffer.len());
16452 let theme = cx.theme().colors();
16453 self.background_highlights_in_range(start..end, &snapshot, theme)
16454 }
16455
16456 #[cfg(feature = "test-support")]
16457 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16458 let snapshot = self.buffer().read(cx).snapshot(cx);
16459
16460 let highlights = self
16461 .background_highlights
16462 .get(&TypeId::of::<items::BufferSearchHighlights>());
16463
16464 if let Some((_color, ranges)) = highlights {
16465 ranges
16466 .iter()
16467 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16468 .collect_vec()
16469 } else {
16470 vec![]
16471 }
16472 }
16473
16474 fn document_highlights_for_position<'a>(
16475 &'a self,
16476 position: Anchor,
16477 buffer: &'a MultiBufferSnapshot,
16478 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16479 let read_highlights = self
16480 .background_highlights
16481 .get(&TypeId::of::<DocumentHighlightRead>())
16482 .map(|h| &h.1);
16483 let write_highlights = self
16484 .background_highlights
16485 .get(&TypeId::of::<DocumentHighlightWrite>())
16486 .map(|h| &h.1);
16487 let left_position = position.bias_left(buffer);
16488 let right_position = position.bias_right(buffer);
16489 read_highlights
16490 .into_iter()
16491 .chain(write_highlights)
16492 .flat_map(move |ranges| {
16493 let start_ix = match ranges.binary_search_by(|probe| {
16494 let cmp = probe.end.cmp(&left_position, buffer);
16495 if cmp.is_ge() {
16496 Ordering::Greater
16497 } else {
16498 Ordering::Less
16499 }
16500 }) {
16501 Ok(i) | Err(i) => i,
16502 };
16503
16504 ranges[start_ix..]
16505 .iter()
16506 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16507 })
16508 }
16509
16510 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16511 self.background_highlights
16512 .get(&TypeId::of::<T>())
16513 .map_or(false, |(_, highlights)| !highlights.is_empty())
16514 }
16515
16516 pub fn background_highlights_in_range(
16517 &self,
16518 search_range: Range<Anchor>,
16519 display_snapshot: &DisplaySnapshot,
16520 theme: &ThemeColors,
16521 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16522 let mut results = Vec::new();
16523 for (color_fetcher, ranges) in self.background_highlights.values() {
16524 let color = color_fetcher(theme);
16525 let start_ix = match ranges.binary_search_by(|probe| {
16526 let cmp = probe
16527 .end
16528 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16529 if cmp.is_gt() {
16530 Ordering::Greater
16531 } else {
16532 Ordering::Less
16533 }
16534 }) {
16535 Ok(i) | Err(i) => i,
16536 };
16537 for range in &ranges[start_ix..] {
16538 if range
16539 .start
16540 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16541 .is_ge()
16542 {
16543 break;
16544 }
16545
16546 let start = range.start.to_display_point(display_snapshot);
16547 let end = range.end.to_display_point(display_snapshot);
16548 results.push((start..end, color))
16549 }
16550 }
16551 results
16552 }
16553
16554 pub fn background_highlight_row_ranges<T: 'static>(
16555 &self,
16556 search_range: Range<Anchor>,
16557 display_snapshot: &DisplaySnapshot,
16558 count: usize,
16559 ) -> Vec<RangeInclusive<DisplayPoint>> {
16560 let mut results = Vec::new();
16561 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16562 return vec![];
16563 };
16564
16565 let start_ix = match ranges.binary_search_by(|probe| {
16566 let cmp = probe
16567 .end
16568 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16569 if cmp.is_gt() {
16570 Ordering::Greater
16571 } else {
16572 Ordering::Less
16573 }
16574 }) {
16575 Ok(i) | Err(i) => i,
16576 };
16577 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16578 if let (Some(start_display), Some(end_display)) = (start, end) {
16579 results.push(
16580 start_display.to_display_point(display_snapshot)
16581 ..=end_display.to_display_point(display_snapshot),
16582 );
16583 }
16584 };
16585 let mut start_row: Option<Point> = None;
16586 let mut end_row: Option<Point> = None;
16587 if ranges.len() > count {
16588 return Vec::new();
16589 }
16590 for range in &ranges[start_ix..] {
16591 if range
16592 .start
16593 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16594 .is_ge()
16595 {
16596 break;
16597 }
16598 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16599 if let Some(current_row) = &end_row {
16600 if end.row == current_row.row {
16601 continue;
16602 }
16603 }
16604 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16605 if start_row.is_none() {
16606 assert_eq!(end_row, None);
16607 start_row = Some(start);
16608 end_row = Some(end);
16609 continue;
16610 }
16611 if let Some(current_end) = end_row.as_mut() {
16612 if start.row > current_end.row + 1 {
16613 push_region(start_row, end_row);
16614 start_row = Some(start);
16615 end_row = Some(end);
16616 } else {
16617 // Merge two hunks.
16618 *current_end = end;
16619 }
16620 } else {
16621 unreachable!();
16622 }
16623 }
16624 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
16625 push_region(start_row, end_row);
16626 results
16627 }
16628
16629 pub fn gutter_highlights_in_range(
16630 &self,
16631 search_range: Range<Anchor>,
16632 display_snapshot: &DisplaySnapshot,
16633 cx: &App,
16634 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16635 let mut results = Vec::new();
16636 for (color_fetcher, ranges) in self.gutter_highlights.values() {
16637 let color = color_fetcher(cx);
16638 let start_ix = match ranges.binary_search_by(|probe| {
16639 let cmp = probe
16640 .end
16641 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16642 if cmp.is_gt() {
16643 Ordering::Greater
16644 } else {
16645 Ordering::Less
16646 }
16647 }) {
16648 Ok(i) | Err(i) => i,
16649 };
16650 for range in &ranges[start_ix..] {
16651 if range
16652 .start
16653 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16654 .is_ge()
16655 {
16656 break;
16657 }
16658
16659 let start = range.start.to_display_point(display_snapshot);
16660 let end = range.end.to_display_point(display_snapshot);
16661 results.push((start..end, color))
16662 }
16663 }
16664 results
16665 }
16666
16667 /// Get the text ranges corresponding to the redaction query
16668 pub fn redacted_ranges(
16669 &self,
16670 search_range: Range<Anchor>,
16671 display_snapshot: &DisplaySnapshot,
16672 cx: &App,
16673 ) -> Vec<Range<DisplayPoint>> {
16674 display_snapshot
16675 .buffer_snapshot
16676 .redacted_ranges(search_range, |file| {
16677 if let Some(file) = file {
16678 file.is_private()
16679 && EditorSettings::get(
16680 Some(SettingsLocation {
16681 worktree_id: file.worktree_id(cx),
16682 path: file.path().as_ref(),
16683 }),
16684 cx,
16685 )
16686 .redact_private_values
16687 } else {
16688 false
16689 }
16690 })
16691 .map(|range| {
16692 range.start.to_display_point(display_snapshot)
16693 ..range.end.to_display_point(display_snapshot)
16694 })
16695 .collect()
16696 }
16697
16698 pub fn highlight_text<T: 'static>(
16699 &mut self,
16700 ranges: Vec<Range<Anchor>>,
16701 style: HighlightStyle,
16702 cx: &mut Context<Self>,
16703 ) {
16704 self.display_map.update(cx, |map, _| {
16705 map.highlight_text(TypeId::of::<T>(), ranges, style)
16706 });
16707 cx.notify();
16708 }
16709
16710 pub(crate) fn highlight_inlays<T: 'static>(
16711 &mut self,
16712 highlights: Vec<InlayHighlight>,
16713 style: HighlightStyle,
16714 cx: &mut Context<Self>,
16715 ) {
16716 self.display_map.update(cx, |map, _| {
16717 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
16718 });
16719 cx.notify();
16720 }
16721
16722 pub fn text_highlights<'a, T: 'static>(
16723 &'a self,
16724 cx: &'a App,
16725 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
16726 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
16727 }
16728
16729 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
16730 let cleared = self
16731 .display_map
16732 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
16733 if cleared {
16734 cx.notify();
16735 }
16736 }
16737
16738 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
16739 (self.read_only(cx) || self.blink_manager.read(cx).visible())
16740 && self.focus_handle.is_focused(window)
16741 }
16742
16743 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
16744 self.show_cursor_when_unfocused = is_enabled;
16745 cx.notify();
16746 }
16747
16748 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
16749 cx.notify();
16750 }
16751
16752 fn on_buffer_event(
16753 &mut self,
16754 multibuffer: &Entity<MultiBuffer>,
16755 event: &multi_buffer::Event,
16756 window: &mut Window,
16757 cx: &mut Context<Self>,
16758 ) {
16759 match event {
16760 multi_buffer::Event::Edited {
16761 singleton_buffer_edited,
16762 edited_buffer: buffer_edited,
16763 } => {
16764 self.scrollbar_marker_state.dirty = true;
16765 self.active_indent_guides_state.dirty = true;
16766 self.refresh_active_diagnostics(cx);
16767 self.refresh_code_actions(window, cx);
16768 if self.has_active_inline_completion() {
16769 self.update_visible_inline_completion(window, cx);
16770 }
16771 if let Some(buffer) = buffer_edited {
16772 let buffer_id = buffer.read(cx).remote_id();
16773 if !self.registered_buffers.contains_key(&buffer_id) {
16774 if let Some(project) = self.project.as_ref() {
16775 project.update(cx, |project, cx| {
16776 self.registered_buffers.insert(
16777 buffer_id,
16778 project.register_buffer_with_language_servers(&buffer, cx),
16779 );
16780 })
16781 }
16782 }
16783 }
16784 cx.emit(EditorEvent::BufferEdited);
16785 cx.emit(SearchEvent::MatchesInvalidated);
16786 if *singleton_buffer_edited {
16787 if let Some(project) = &self.project {
16788 #[allow(clippy::mutable_key_type)]
16789 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
16790 multibuffer
16791 .all_buffers()
16792 .into_iter()
16793 .filter_map(|buffer| {
16794 buffer.update(cx, |buffer, cx| {
16795 let language = buffer.language()?;
16796 let should_discard = project.update(cx, |project, cx| {
16797 project.is_local()
16798 && !project.has_language_servers_for(buffer, cx)
16799 });
16800 should_discard.not().then_some(language.clone())
16801 })
16802 })
16803 .collect::<HashSet<_>>()
16804 });
16805 if !languages_affected.is_empty() {
16806 self.refresh_inlay_hints(
16807 InlayHintRefreshReason::BufferEdited(languages_affected),
16808 cx,
16809 );
16810 }
16811 }
16812 }
16813
16814 let Some(project) = &self.project else { return };
16815 let (telemetry, is_via_ssh) = {
16816 let project = project.read(cx);
16817 let telemetry = project.client().telemetry().clone();
16818 let is_via_ssh = project.is_via_ssh();
16819 (telemetry, is_via_ssh)
16820 };
16821 refresh_linked_ranges(self, window, cx);
16822 telemetry.log_edit_event("editor", is_via_ssh);
16823 }
16824 multi_buffer::Event::ExcerptsAdded {
16825 buffer,
16826 predecessor,
16827 excerpts,
16828 } => {
16829 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16830 let buffer_id = buffer.read(cx).remote_id();
16831 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
16832 if let Some(project) = &self.project {
16833 get_uncommitted_diff_for_buffer(
16834 project,
16835 [buffer.clone()],
16836 self.buffer.clone(),
16837 cx,
16838 )
16839 .detach();
16840 }
16841 }
16842 cx.emit(EditorEvent::ExcerptsAdded {
16843 buffer: buffer.clone(),
16844 predecessor: *predecessor,
16845 excerpts: excerpts.clone(),
16846 });
16847 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16848 }
16849 multi_buffer::Event::ExcerptsRemoved { ids } => {
16850 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
16851 let buffer = self.buffer.read(cx);
16852 self.registered_buffers
16853 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
16854 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16855 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
16856 }
16857 multi_buffer::Event::ExcerptsEdited {
16858 excerpt_ids,
16859 buffer_ids,
16860 } => {
16861 self.display_map.update(cx, |map, cx| {
16862 map.unfold_buffers(buffer_ids.iter().copied(), cx)
16863 });
16864 cx.emit(EditorEvent::ExcerptsEdited {
16865 ids: excerpt_ids.clone(),
16866 })
16867 }
16868 multi_buffer::Event::ExcerptsExpanded { ids } => {
16869 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
16870 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
16871 }
16872 multi_buffer::Event::Reparsed(buffer_id) => {
16873 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16874 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16875
16876 cx.emit(EditorEvent::Reparsed(*buffer_id));
16877 }
16878 multi_buffer::Event::DiffHunksToggled => {
16879 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16880 }
16881 multi_buffer::Event::LanguageChanged(buffer_id) => {
16882 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
16883 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
16884 cx.emit(EditorEvent::Reparsed(*buffer_id));
16885 cx.notify();
16886 }
16887 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
16888 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
16889 multi_buffer::Event::FileHandleChanged
16890 | multi_buffer::Event::Reloaded
16891 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
16892 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
16893 multi_buffer::Event::DiagnosticsUpdated => {
16894 self.refresh_active_diagnostics(cx);
16895 self.refresh_inline_diagnostics(true, window, cx);
16896 self.scrollbar_marker_state.dirty = true;
16897 cx.notify();
16898 }
16899 _ => {}
16900 };
16901 }
16902
16903 fn on_display_map_changed(
16904 &mut self,
16905 _: Entity<DisplayMap>,
16906 _: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 cx.notify();
16910 }
16911
16912 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16913 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
16914 self.update_edit_prediction_settings(cx);
16915 self.refresh_inline_completion(true, false, window, cx);
16916 self.refresh_inlay_hints(
16917 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
16918 self.selections.newest_anchor().head(),
16919 &self.buffer.read(cx).snapshot(cx),
16920 cx,
16921 )),
16922 cx,
16923 );
16924
16925 let old_cursor_shape = self.cursor_shape;
16926
16927 {
16928 let editor_settings = EditorSettings::get_global(cx);
16929 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
16930 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
16931 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
16932 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
16933 }
16934
16935 if old_cursor_shape != self.cursor_shape {
16936 cx.emit(EditorEvent::CursorShapeChanged);
16937 }
16938
16939 let project_settings = ProjectSettings::get_global(cx);
16940 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
16941
16942 if self.mode == EditorMode::Full {
16943 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
16944 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
16945 if self.show_inline_diagnostics != show_inline_diagnostics {
16946 self.show_inline_diagnostics = show_inline_diagnostics;
16947 self.refresh_inline_diagnostics(false, window, cx);
16948 }
16949
16950 if self.git_blame_inline_enabled != inline_blame_enabled {
16951 self.toggle_git_blame_inline_internal(false, window, cx);
16952 }
16953 }
16954
16955 cx.notify();
16956 }
16957
16958 pub fn set_searchable(&mut self, searchable: bool) {
16959 self.searchable = searchable;
16960 }
16961
16962 pub fn searchable(&self) -> bool {
16963 self.searchable
16964 }
16965
16966 fn open_proposed_changes_editor(
16967 &mut self,
16968 _: &OpenProposedChangesEditor,
16969 window: &mut Window,
16970 cx: &mut Context<Self>,
16971 ) {
16972 let Some(workspace) = self.workspace() else {
16973 cx.propagate();
16974 return;
16975 };
16976
16977 let selections = self.selections.all::<usize>(cx);
16978 let multi_buffer = self.buffer.read(cx);
16979 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16980 let mut new_selections_by_buffer = HashMap::default();
16981 for selection in selections {
16982 for (buffer, range, _) in
16983 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
16984 {
16985 let mut range = range.to_point(buffer);
16986 range.start.column = 0;
16987 range.end.column = buffer.line_len(range.end.row);
16988 new_selections_by_buffer
16989 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
16990 .or_insert(Vec::new())
16991 .push(range)
16992 }
16993 }
16994
16995 let proposed_changes_buffers = new_selections_by_buffer
16996 .into_iter()
16997 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
16998 .collect::<Vec<_>>();
16999 let proposed_changes_editor = cx.new(|cx| {
17000 ProposedChangesEditor::new(
17001 "Proposed changes",
17002 proposed_changes_buffers,
17003 self.project.clone(),
17004 window,
17005 cx,
17006 )
17007 });
17008
17009 window.defer(cx, move |window, cx| {
17010 workspace.update(cx, |workspace, cx| {
17011 workspace.active_pane().update(cx, |pane, cx| {
17012 pane.add_item(
17013 Box::new(proposed_changes_editor),
17014 true,
17015 true,
17016 None,
17017 window,
17018 cx,
17019 );
17020 });
17021 });
17022 });
17023 }
17024
17025 pub fn open_excerpts_in_split(
17026 &mut self,
17027 _: &OpenExcerptsSplit,
17028 window: &mut Window,
17029 cx: &mut Context<Self>,
17030 ) {
17031 self.open_excerpts_common(None, true, window, cx)
17032 }
17033
17034 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17035 self.open_excerpts_common(None, false, window, cx)
17036 }
17037
17038 fn open_excerpts_common(
17039 &mut self,
17040 jump_data: Option<JumpData>,
17041 split: bool,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 let Some(workspace) = self.workspace() else {
17046 cx.propagate();
17047 return;
17048 };
17049
17050 if self.buffer.read(cx).is_singleton() {
17051 cx.propagate();
17052 return;
17053 }
17054
17055 let mut new_selections_by_buffer = HashMap::default();
17056 match &jump_data {
17057 Some(JumpData::MultiBufferPoint {
17058 excerpt_id,
17059 position,
17060 anchor,
17061 line_offset_from_top,
17062 }) => {
17063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17064 if let Some(buffer) = multi_buffer_snapshot
17065 .buffer_id_for_excerpt(*excerpt_id)
17066 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17067 {
17068 let buffer_snapshot = buffer.read(cx).snapshot();
17069 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17070 language::ToPoint::to_point(anchor, &buffer_snapshot)
17071 } else {
17072 buffer_snapshot.clip_point(*position, Bias::Left)
17073 };
17074 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17075 new_selections_by_buffer.insert(
17076 buffer,
17077 (
17078 vec![jump_to_offset..jump_to_offset],
17079 Some(*line_offset_from_top),
17080 ),
17081 );
17082 }
17083 }
17084 Some(JumpData::MultiBufferRow {
17085 row,
17086 line_offset_from_top,
17087 }) => {
17088 let point = MultiBufferPoint::new(row.0, 0);
17089 if let Some((buffer, buffer_point, _)) =
17090 self.buffer.read(cx).point_to_buffer_point(point, cx)
17091 {
17092 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17093 new_selections_by_buffer
17094 .entry(buffer)
17095 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17096 .0
17097 .push(buffer_offset..buffer_offset)
17098 }
17099 }
17100 None => {
17101 let selections = self.selections.all::<usize>(cx);
17102 let multi_buffer = self.buffer.read(cx);
17103 for selection in selections {
17104 for (snapshot, range, _, anchor) in multi_buffer
17105 .snapshot(cx)
17106 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17107 {
17108 if let Some(anchor) = anchor {
17109 // selection is in a deleted hunk
17110 let Some(buffer_id) = anchor.buffer_id else {
17111 continue;
17112 };
17113 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17114 continue;
17115 };
17116 let offset = text::ToOffset::to_offset(
17117 &anchor.text_anchor,
17118 &buffer_handle.read(cx).snapshot(),
17119 );
17120 let range = offset..offset;
17121 new_selections_by_buffer
17122 .entry(buffer_handle)
17123 .or_insert((Vec::new(), None))
17124 .0
17125 .push(range)
17126 } else {
17127 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17128 else {
17129 continue;
17130 };
17131 new_selections_by_buffer
17132 .entry(buffer_handle)
17133 .or_insert((Vec::new(), None))
17134 .0
17135 .push(range)
17136 }
17137 }
17138 }
17139 }
17140 }
17141
17142 if new_selections_by_buffer.is_empty() {
17143 return;
17144 }
17145
17146 // We defer the pane interaction because we ourselves are a workspace item
17147 // and activating a new item causes the pane to call a method on us reentrantly,
17148 // which panics if we're on the stack.
17149 window.defer(cx, move |window, cx| {
17150 workspace.update(cx, |workspace, cx| {
17151 let pane = if split {
17152 workspace.adjacent_pane(window, cx)
17153 } else {
17154 workspace.active_pane().clone()
17155 };
17156
17157 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17158 let editor = buffer
17159 .read(cx)
17160 .file()
17161 .is_none()
17162 .then(|| {
17163 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17164 // so `workspace.open_project_item` will never find them, always opening a new editor.
17165 // Instead, we try to activate the existing editor in the pane first.
17166 let (editor, pane_item_index) =
17167 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17168 let editor = item.downcast::<Editor>()?;
17169 let singleton_buffer =
17170 editor.read(cx).buffer().read(cx).as_singleton()?;
17171 if singleton_buffer == buffer {
17172 Some((editor, i))
17173 } else {
17174 None
17175 }
17176 })?;
17177 pane.update(cx, |pane, cx| {
17178 pane.activate_item(pane_item_index, true, true, window, cx)
17179 });
17180 Some(editor)
17181 })
17182 .flatten()
17183 .unwrap_or_else(|| {
17184 workspace.open_project_item::<Self>(
17185 pane.clone(),
17186 buffer,
17187 true,
17188 true,
17189 window,
17190 cx,
17191 )
17192 });
17193
17194 editor.update(cx, |editor, cx| {
17195 let autoscroll = match scroll_offset {
17196 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17197 None => Autoscroll::newest(),
17198 };
17199 let nav_history = editor.nav_history.take();
17200 editor.change_selections(Some(autoscroll), window, cx, |s| {
17201 s.select_ranges(ranges);
17202 });
17203 editor.nav_history = nav_history;
17204 });
17205 }
17206 })
17207 });
17208 }
17209
17210 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17211 let snapshot = self.buffer.read(cx).read(cx);
17212 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17213 Some(
17214 ranges
17215 .iter()
17216 .map(move |range| {
17217 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17218 })
17219 .collect(),
17220 )
17221 }
17222
17223 fn selection_replacement_ranges(
17224 &self,
17225 range: Range<OffsetUtf16>,
17226 cx: &mut App,
17227 ) -> Vec<Range<OffsetUtf16>> {
17228 let selections = self.selections.all::<OffsetUtf16>(cx);
17229 let newest_selection = selections
17230 .iter()
17231 .max_by_key(|selection| selection.id)
17232 .unwrap();
17233 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17234 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17235 let snapshot = self.buffer.read(cx).read(cx);
17236 selections
17237 .into_iter()
17238 .map(|mut selection| {
17239 selection.start.0 =
17240 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17241 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17242 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17243 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17244 })
17245 .collect()
17246 }
17247
17248 fn report_editor_event(
17249 &self,
17250 event_type: &'static str,
17251 file_extension: Option<String>,
17252 cx: &App,
17253 ) {
17254 if cfg!(any(test, feature = "test-support")) {
17255 return;
17256 }
17257
17258 let Some(project) = &self.project else { return };
17259
17260 // If None, we are in a file without an extension
17261 let file = self
17262 .buffer
17263 .read(cx)
17264 .as_singleton()
17265 .and_then(|b| b.read(cx).file());
17266 let file_extension = file_extension.or(file
17267 .as_ref()
17268 .and_then(|file| Path::new(file.file_name(cx)).extension())
17269 .and_then(|e| e.to_str())
17270 .map(|a| a.to_string()));
17271
17272 let vim_mode = cx
17273 .global::<SettingsStore>()
17274 .raw_user_settings()
17275 .get("vim_mode")
17276 == Some(&serde_json::Value::Bool(true));
17277
17278 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17279 let copilot_enabled = edit_predictions_provider
17280 == language::language_settings::EditPredictionProvider::Copilot;
17281 let copilot_enabled_for_language = self
17282 .buffer
17283 .read(cx)
17284 .language_settings(cx)
17285 .show_edit_predictions;
17286
17287 let project = project.read(cx);
17288 telemetry::event!(
17289 event_type,
17290 file_extension,
17291 vim_mode,
17292 copilot_enabled,
17293 copilot_enabled_for_language,
17294 edit_predictions_provider,
17295 is_via_ssh = project.is_via_ssh(),
17296 );
17297 }
17298
17299 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17300 /// with each line being an array of {text, highlight} objects.
17301 fn copy_highlight_json(
17302 &mut self,
17303 _: &CopyHighlightJson,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 #[derive(Serialize)]
17308 struct Chunk<'a> {
17309 text: String,
17310 highlight: Option<&'a str>,
17311 }
17312
17313 let snapshot = self.buffer.read(cx).snapshot(cx);
17314 let range = self
17315 .selected_text_range(false, window, cx)
17316 .and_then(|selection| {
17317 if selection.range.is_empty() {
17318 None
17319 } else {
17320 Some(selection.range)
17321 }
17322 })
17323 .unwrap_or_else(|| 0..snapshot.len());
17324
17325 let chunks = snapshot.chunks(range, true);
17326 let mut lines = Vec::new();
17327 let mut line: VecDeque<Chunk> = VecDeque::new();
17328
17329 let Some(style) = self.style.as_ref() else {
17330 return;
17331 };
17332
17333 for chunk in chunks {
17334 let highlight = chunk
17335 .syntax_highlight_id
17336 .and_then(|id| id.name(&style.syntax));
17337 let mut chunk_lines = chunk.text.split('\n').peekable();
17338 while let Some(text) = chunk_lines.next() {
17339 let mut merged_with_last_token = false;
17340 if let Some(last_token) = line.back_mut() {
17341 if last_token.highlight == highlight {
17342 last_token.text.push_str(text);
17343 merged_with_last_token = true;
17344 }
17345 }
17346
17347 if !merged_with_last_token {
17348 line.push_back(Chunk {
17349 text: text.into(),
17350 highlight,
17351 });
17352 }
17353
17354 if chunk_lines.peek().is_some() {
17355 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17356 line.pop_front();
17357 }
17358 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17359 line.pop_back();
17360 }
17361
17362 lines.push(mem::take(&mut line));
17363 }
17364 }
17365 }
17366
17367 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17368 return;
17369 };
17370 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17371 }
17372
17373 pub fn open_context_menu(
17374 &mut self,
17375 _: &OpenContextMenu,
17376 window: &mut Window,
17377 cx: &mut Context<Self>,
17378 ) {
17379 self.request_autoscroll(Autoscroll::newest(), cx);
17380 let position = self.selections.newest_display(cx).start;
17381 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17382 }
17383
17384 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17385 &self.inlay_hint_cache
17386 }
17387
17388 pub fn replay_insert_event(
17389 &mut self,
17390 text: &str,
17391 relative_utf16_range: Option<Range<isize>>,
17392 window: &mut Window,
17393 cx: &mut Context<Self>,
17394 ) {
17395 if !self.input_enabled {
17396 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17397 return;
17398 }
17399 if let Some(relative_utf16_range) = relative_utf16_range {
17400 let selections = self.selections.all::<OffsetUtf16>(cx);
17401 self.change_selections(None, window, cx, |s| {
17402 let new_ranges = selections.into_iter().map(|range| {
17403 let start = OffsetUtf16(
17404 range
17405 .head()
17406 .0
17407 .saturating_add_signed(relative_utf16_range.start),
17408 );
17409 let end = OffsetUtf16(
17410 range
17411 .head()
17412 .0
17413 .saturating_add_signed(relative_utf16_range.end),
17414 );
17415 start..end
17416 });
17417 s.select_ranges(new_ranges);
17418 });
17419 }
17420
17421 self.handle_input(text, window, cx);
17422 }
17423
17424 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17425 let Some(provider) = self.semantics_provider.as_ref() else {
17426 return false;
17427 };
17428
17429 let mut supports = false;
17430 self.buffer().update(cx, |this, cx| {
17431 this.for_each_buffer(|buffer| {
17432 supports |= provider.supports_inlay_hints(buffer, cx);
17433 });
17434 });
17435
17436 supports
17437 }
17438
17439 pub fn is_focused(&self, window: &Window) -> bool {
17440 self.focus_handle.is_focused(window)
17441 }
17442
17443 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17444 cx.emit(EditorEvent::Focused);
17445
17446 if let Some(descendant) = self
17447 .last_focused_descendant
17448 .take()
17449 .and_then(|descendant| descendant.upgrade())
17450 {
17451 window.focus(&descendant);
17452 } else {
17453 if let Some(blame) = self.blame.as_ref() {
17454 blame.update(cx, GitBlame::focus)
17455 }
17456
17457 self.blink_manager.update(cx, BlinkManager::enable);
17458 self.show_cursor_names(window, cx);
17459 self.buffer.update(cx, |buffer, cx| {
17460 buffer.finalize_last_transaction(cx);
17461 if self.leader_peer_id.is_none() {
17462 buffer.set_active_selections(
17463 &self.selections.disjoint_anchors(),
17464 self.selections.line_mode,
17465 self.cursor_shape,
17466 cx,
17467 );
17468 }
17469 });
17470 }
17471 }
17472
17473 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17474 cx.emit(EditorEvent::FocusedIn)
17475 }
17476
17477 fn handle_focus_out(
17478 &mut self,
17479 event: FocusOutEvent,
17480 _window: &mut Window,
17481 cx: &mut Context<Self>,
17482 ) {
17483 if event.blurred != self.focus_handle {
17484 self.last_focused_descendant = Some(event.blurred);
17485 }
17486 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17487 }
17488
17489 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17490 self.blink_manager.update(cx, BlinkManager::disable);
17491 self.buffer
17492 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17493
17494 if let Some(blame) = self.blame.as_ref() {
17495 blame.update(cx, GitBlame::blur)
17496 }
17497 if !self.hover_state.focused(window, cx) {
17498 hide_hover(self, cx);
17499 }
17500 if !self
17501 .context_menu
17502 .borrow()
17503 .as_ref()
17504 .is_some_and(|context_menu| context_menu.focused(window, cx))
17505 {
17506 self.hide_context_menu(window, cx);
17507 }
17508 self.discard_inline_completion(false, cx);
17509 cx.emit(EditorEvent::Blurred);
17510 cx.notify();
17511 }
17512
17513 pub fn register_action<A: Action>(
17514 &mut self,
17515 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17516 ) -> Subscription {
17517 let id = self.next_editor_action_id.post_inc();
17518 let listener = Arc::new(listener);
17519 self.editor_actions.borrow_mut().insert(
17520 id,
17521 Box::new(move |window, _| {
17522 let listener = listener.clone();
17523 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17524 let action = action.downcast_ref().unwrap();
17525 if phase == DispatchPhase::Bubble {
17526 listener(action, window, cx)
17527 }
17528 })
17529 }),
17530 );
17531
17532 let editor_actions = self.editor_actions.clone();
17533 Subscription::new(move || {
17534 editor_actions.borrow_mut().remove(&id);
17535 })
17536 }
17537
17538 pub fn file_header_size(&self) -> u32 {
17539 FILE_HEADER_HEIGHT
17540 }
17541
17542 pub fn restore(
17543 &mut self,
17544 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17545 window: &mut Window,
17546 cx: &mut Context<Self>,
17547 ) {
17548 let workspace = self.workspace();
17549 let project = self.project.as_ref();
17550 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17551 let mut tasks = Vec::new();
17552 for (buffer_id, changes) in revert_changes {
17553 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17554 buffer.update(cx, |buffer, cx| {
17555 buffer.edit(
17556 changes
17557 .into_iter()
17558 .map(|(range, text)| (range, text.to_string())),
17559 None,
17560 cx,
17561 );
17562 });
17563
17564 if let Some(project) =
17565 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17566 {
17567 project.update(cx, |project, cx| {
17568 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17569 })
17570 }
17571 }
17572 }
17573 tasks
17574 });
17575 cx.spawn_in(window, async move |_, cx| {
17576 for (buffer, task) in save_tasks {
17577 let result = task.await;
17578 if result.is_err() {
17579 let Some(path) = buffer
17580 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17581 .ok()
17582 else {
17583 continue;
17584 };
17585 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17586 let Some(task) = cx
17587 .update_window_entity(&workspace, |workspace, window, cx| {
17588 workspace
17589 .open_path_preview(path, None, false, false, false, window, cx)
17590 })
17591 .ok()
17592 else {
17593 continue;
17594 };
17595 task.await.log_err();
17596 }
17597 }
17598 }
17599 })
17600 .detach();
17601 self.change_selections(None, window, cx, |selections| selections.refresh());
17602 }
17603
17604 pub fn to_pixel_point(
17605 &self,
17606 source: multi_buffer::Anchor,
17607 editor_snapshot: &EditorSnapshot,
17608 window: &mut Window,
17609 ) -> Option<gpui::Point<Pixels>> {
17610 let source_point = source.to_display_point(editor_snapshot);
17611 self.display_to_pixel_point(source_point, editor_snapshot, window)
17612 }
17613
17614 pub fn display_to_pixel_point(
17615 &self,
17616 source: DisplayPoint,
17617 editor_snapshot: &EditorSnapshot,
17618 window: &mut Window,
17619 ) -> Option<gpui::Point<Pixels>> {
17620 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
17621 let text_layout_details = self.text_layout_details(window);
17622 let scroll_top = text_layout_details
17623 .scroll_anchor
17624 .scroll_position(editor_snapshot)
17625 .y;
17626
17627 if source.row().as_f32() < scroll_top.floor() {
17628 return None;
17629 }
17630 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
17631 let source_y = line_height * (source.row().as_f32() - scroll_top);
17632 Some(gpui::Point::new(source_x, source_y))
17633 }
17634
17635 pub fn has_visible_completions_menu(&self) -> bool {
17636 !self.edit_prediction_preview_is_active()
17637 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
17638 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
17639 })
17640 }
17641
17642 pub fn register_addon<T: Addon>(&mut self, instance: T) {
17643 self.addons
17644 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
17645 }
17646
17647 pub fn unregister_addon<T: Addon>(&mut self) {
17648 self.addons.remove(&std::any::TypeId::of::<T>());
17649 }
17650
17651 pub fn addon<T: Addon>(&self) -> Option<&T> {
17652 let type_id = std::any::TypeId::of::<T>();
17653 self.addons
17654 .get(&type_id)
17655 .and_then(|item| item.to_any().downcast_ref::<T>())
17656 }
17657
17658 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
17659 let text_layout_details = self.text_layout_details(window);
17660 let style = &text_layout_details.editor_style;
17661 let font_id = window.text_system().resolve_font(&style.text.font());
17662 let font_size = style.text.font_size.to_pixels(window.rem_size());
17663 let line_height = style.text.line_height_in_pixels(window.rem_size());
17664 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
17665
17666 gpui::Size::new(em_width, line_height)
17667 }
17668
17669 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
17670 self.load_diff_task.clone()
17671 }
17672
17673 fn read_metadata_from_db(
17674 &mut self,
17675 item_id: u64,
17676 workspace_id: WorkspaceId,
17677 window: &mut Window,
17678 cx: &mut Context<Editor>,
17679 ) {
17680 if self.is_singleton(cx)
17681 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
17682 {
17683 let buffer_snapshot = OnceCell::new();
17684
17685 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
17686 if !folds.is_empty() {
17687 let snapshot =
17688 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17689 self.fold_ranges(
17690 folds
17691 .into_iter()
17692 .map(|(start, end)| {
17693 snapshot.clip_offset(start, Bias::Left)
17694 ..snapshot.clip_offset(end, Bias::Right)
17695 })
17696 .collect(),
17697 false,
17698 window,
17699 cx,
17700 );
17701 }
17702 }
17703
17704 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
17705 if !selections.is_empty() {
17706 let snapshot =
17707 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
17708 self.change_selections(None, window, cx, |s| {
17709 s.select_ranges(selections.into_iter().map(|(start, end)| {
17710 snapshot.clip_offset(start, Bias::Left)
17711 ..snapshot.clip_offset(end, Bias::Right)
17712 }));
17713 });
17714 }
17715 };
17716 }
17717
17718 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
17719 }
17720}
17721
17722fn insert_extra_newline_brackets(
17723 buffer: &MultiBufferSnapshot,
17724 range: Range<usize>,
17725 language: &language::LanguageScope,
17726) -> bool {
17727 let leading_whitespace_len = buffer
17728 .reversed_chars_at(range.start)
17729 .take_while(|c| c.is_whitespace() && *c != '\n')
17730 .map(|c| c.len_utf8())
17731 .sum::<usize>();
17732 let trailing_whitespace_len = buffer
17733 .chars_at(range.end)
17734 .take_while(|c| c.is_whitespace() && *c != '\n')
17735 .map(|c| c.len_utf8())
17736 .sum::<usize>();
17737 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
17738
17739 language.brackets().any(|(pair, enabled)| {
17740 let pair_start = pair.start.trim_end();
17741 let pair_end = pair.end.trim_start();
17742
17743 enabled
17744 && pair.newline
17745 && buffer.contains_str_at(range.end, pair_end)
17746 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
17747 })
17748}
17749
17750fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
17751 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
17752 [(buffer, range, _)] => (*buffer, range.clone()),
17753 _ => return false,
17754 };
17755 let pair = {
17756 let mut result: Option<BracketMatch> = None;
17757
17758 for pair in buffer
17759 .all_bracket_ranges(range.clone())
17760 .filter(move |pair| {
17761 pair.open_range.start <= range.start && pair.close_range.end >= range.end
17762 })
17763 {
17764 let len = pair.close_range.end - pair.open_range.start;
17765
17766 if let Some(existing) = &result {
17767 let existing_len = existing.close_range.end - existing.open_range.start;
17768 if len > existing_len {
17769 continue;
17770 }
17771 }
17772
17773 result = Some(pair);
17774 }
17775
17776 result
17777 };
17778 let Some(pair) = pair else {
17779 return false;
17780 };
17781 pair.newline_only
17782 && buffer
17783 .chars_for_range(pair.open_range.end..range.start)
17784 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
17785 .all(|c| c.is_whitespace() && c != '\n')
17786}
17787
17788fn get_uncommitted_diff_for_buffer(
17789 project: &Entity<Project>,
17790 buffers: impl IntoIterator<Item = Entity<Buffer>>,
17791 buffer: Entity<MultiBuffer>,
17792 cx: &mut App,
17793) -> Task<()> {
17794 let mut tasks = Vec::new();
17795 project.update(cx, |project, cx| {
17796 for buffer in buffers {
17797 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
17798 }
17799 });
17800 cx.spawn(async move |cx| {
17801 let diffs = future::join_all(tasks).await;
17802 buffer
17803 .update(cx, |buffer, cx| {
17804 for diff in diffs.into_iter().flatten() {
17805 buffer.add_diff(diff, cx);
17806 }
17807 })
17808 .ok();
17809 })
17810}
17811
17812fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
17813 let tab_size = tab_size.get() as usize;
17814 let mut width = offset;
17815
17816 for ch in text.chars() {
17817 width += if ch == '\t' {
17818 tab_size - (width % tab_size)
17819 } else {
17820 1
17821 };
17822 }
17823
17824 width - offset
17825}
17826
17827#[cfg(test)]
17828mod tests {
17829 use super::*;
17830
17831 #[test]
17832 fn test_string_size_with_expanded_tabs() {
17833 let nz = |val| NonZeroU32::new(val).unwrap();
17834 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
17835 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
17836 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
17837 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
17838 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
17839 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
17840 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
17841 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
17842 }
17843}
17844
17845/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
17846struct WordBreakingTokenizer<'a> {
17847 input: &'a str,
17848}
17849
17850impl<'a> WordBreakingTokenizer<'a> {
17851 fn new(input: &'a str) -> Self {
17852 Self { input }
17853 }
17854}
17855
17856fn is_char_ideographic(ch: char) -> bool {
17857 use unicode_script::Script::*;
17858 use unicode_script::UnicodeScript;
17859 matches!(ch.script(), Han | Tangut | Yi)
17860}
17861
17862fn is_grapheme_ideographic(text: &str) -> bool {
17863 text.chars().any(is_char_ideographic)
17864}
17865
17866fn is_grapheme_whitespace(text: &str) -> bool {
17867 text.chars().any(|x| x.is_whitespace())
17868}
17869
17870fn should_stay_with_preceding_ideograph(text: &str) -> bool {
17871 text.chars().next().map_or(false, |ch| {
17872 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
17873 })
17874}
17875
17876#[derive(PartialEq, Eq, Debug, Clone, Copy)]
17877enum WordBreakToken<'a> {
17878 Word { token: &'a str, grapheme_len: usize },
17879 InlineWhitespace { token: &'a str, grapheme_len: usize },
17880 Newline,
17881}
17882
17883impl<'a> Iterator for WordBreakingTokenizer<'a> {
17884 /// Yields a span, the count of graphemes in the token, and whether it was
17885 /// whitespace. Note that it also breaks at word boundaries.
17886 type Item = WordBreakToken<'a>;
17887
17888 fn next(&mut self) -> Option<Self::Item> {
17889 use unicode_segmentation::UnicodeSegmentation;
17890 if self.input.is_empty() {
17891 return None;
17892 }
17893
17894 let mut iter = self.input.graphemes(true).peekable();
17895 let mut offset = 0;
17896 let mut grapheme_len = 0;
17897 if let Some(first_grapheme) = iter.next() {
17898 let is_newline = first_grapheme == "\n";
17899 let is_whitespace = is_grapheme_whitespace(first_grapheme);
17900 offset += first_grapheme.len();
17901 grapheme_len += 1;
17902 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
17903 if let Some(grapheme) = iter.peek().copied() {
17904 if should_stay_with_preceding_ideograph(grapheme) {
17905 offset += grapheme.len();
17906 grapheme_len += 1;
17907 }
17908 }
17909 } else {
17910 let mut words = self.input[offset..].split_word_bound_indices().peekable();
17911 let mut next_word_bound = words.peek().copied();
17912 if next_word_bound.map_or(false, |(i, _)| i == 0) {
17913 next_word_bound = words.next();
17914 }
17915 while let Some(grapheme) = iter.peek().copied() {
17916 if next_word_bound.map_or(false, |(i, _)| i == offset) {
17917 break;
17918 };
17919 if is_grapheme_whitespace(grapheme) != is_whitespace
17920 || (grapheme == "\n") != is_newline
17921 {
17922 break;
17923 };
17924 offset += grapheme.len();
17925 grapheme_len += 1;
17926 iter.next();
17927 }
17928 }
17929 let token = &self.input[..offset];
17930 self.input = &self.input[offset..];
17931 if token == "\n" {
17932 Some(WordBreakToken::Newline)
17933 } else if is_whitespace {
17934 Some(WordBreakToken::InlineWhitespace {
17935 token,
17936 grapheme_len,
17937 })
17938 } else {
17939 Some(WordBreakToken::Word {
17940 token,
17941 grapheme_len,
17942 })
17943 }
17944 } else {
17945 None
17946 }
17947 }
17948}
17949
17950#[test]
17951fn test_word_breaking_tokenizer() {
17952 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
17953 ("", &[]),
17954 (" ", &[whitespace(" ", 2)]),
17955 ("Ʒ", &[word("Ʒ", 1)]),
17956 ("Ǽ", &[word("Ǽ", 1)]),
17957 ("⋑", &[word("⋑", 1)]),
17958 ("⋑⋑", &[word("⋑⋑", 2)]),
17959 (
17960 "原理,进而",
17961 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
17962 ),
17963 (
17964 "hello world",
17965 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
17966 ),
17967 (
17968 "hello, world",
17969 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
17970 ),
17971 (
17972 " hello world",
17973 &[
17974 whitespace(" ", 2),
17975 word("hello", 5),
17976 whitespace(" ", 1),
17977 word("world", 5),
17978 ],
17979 ),
17980 (
17981 "这是什么 \n 钢笔",
17982 &[
17983 word("这", 1),
17984 word("是", 1),
17985 word("什", 1),
17986 word("么", 1),
17987 whitespace(" ", 1),
17988 newline(),
17989 whitespace(" ", 1),
17990 word("钢", 1),
17991 word("笔", 1),
17992 ],
17993 ),
17994 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
17995 ];
17996
17997 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
17998 WordBreakToken::Word {
17999 token,
18000 grapheme_len,
18001 }
18002 }
18003
18004 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18005 WordBreakToken::InlineWhitespace {
18006 token,
18007 grapheme_len,
18008 }
18009 }
18010
18011 fn newline() -> WordBreakToken<'static> {
18012 WordBreakToken::Newline
18013 }
18014
18015 for (input, result) in tests {
18016 assert_eq!(
18017 WordBreakingTokenizer::new(input)
18018 .collect::<Vec<_>>()
18019 .as_slice(),
18020 *result,
18021 );
18022 }
18023}
18024
18025fn wrap_with_prefix(
18026 line_prefix: String,
18027 unwrapped_text: String,
18028 wrap_column: usize,
18029 tab_size: NonZeroU32,
18030 preserve_existing_whitespace: bool,
18031) -> String {
18032 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18033 let mut wrapped_text = String::new();
18034 let mut current_line = line_prefix.clone();
18035
18036 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18037 let mut current_line_len = line_prefix_len;
18038 let mut in_whitespace = false;
18039 for token in tokenizer {
18040 let have_preceding_whitespace = in_whitespace;
18041 match token {
18042 WordBreakToken::Word {
18043 token,
18044 grapheme_len,
18045 } => {
18046 in_whitespace = false;
18047 if current_line_len + grapheme_len > wrap_column
18048 && current_line_len != line_prefix_len
18049 {
18050 wrapped_text.push_str(current_line.trim_end());
18051 wrapped_text.push('\n');
18052 current_line.truncate(line_prefix.len());
18053 current_line_len = line_prefix_len;
18054 }
18055 current_line.push_str(token);
18056 current_line_len += grapheme_len;
18057 }
18058 WordBreakToken::InlineWhitespace {
18059 mut token,
18060 mut grapheme_len,
18061 } => {
18062 in_whitespace = true;
18063 if have_preceding_whitespace && !preserve_existing_whitespace {
18064 continue;
18065 }
18066 if !preserve_existing_whitespace {
18067 token = " ";
18068 grapheme_len = 1;
18069 }
18070 if current_line_len + grapheme_len > wrap_column {
18071 wrapped_text.push_str(current_line.trim_end());
18072 wrapped_text.push('\n');
18073 current_line.truncate(line_prefix.len());
18074 current_line_len = line_prefix_len;
18075 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18076 current_line.push_str(token);
18077 current_line_len += grapheme_len;
18078 }
18079 }
18080 WordBreakToken::Newline => {
18081 in_whitespace = true;
18082 if preserve_existing_whitespace {
18083 wrapped_text.push_str(current_line.trim_end());
18084 wrapped_text.push('\n');
18085 current_line.truncate(line_prefix.len());
18086 current_line_len = line_prefix_len;
18087 } else if have_preceding_whitespace {
18088 continue;
18089 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18090 {
18091 wrapped_text.push_str(current_line.trim_end());
18092 wrapped_text.push('\n');
18093 current_line.truncate(line_prefix.len());
18094 current_line_len = line_prefix_len;
18095 } else if current_line_len != line_prefix_len {
18096 current_line.push(' ');
18097 current_line_len += 1;
18098 }
18099 }
18100 }
18101 }
18102
18103 if !current_line.is_empty() {
18104 wrapped_text.push_str(¤t_line);
18105 }
18106 wrapped_text
18107}
18108
18109#[test]
18110fn test_wrap_with_prefix() {
18111 assert_eq!(
18112 wrap_with_prefix(
18113 "# ".to_string(),
18114 "abcdefg".to_string(),
18115 4,
18116 NonZeroU32::new(4).unwrap(),
18117 false,
18118 ),
18119 "# abcdefg"
18120 );
18121 assert_eq!(
18122 wrap_with_prefix(
18123 "".to_string(),
18124 "\thello world".to_string(),
18125 8,
18126 NonZeroU32::new(4).unwrap(),
18127 false,
18128 ),
18129 "hello\nworld"
18130 );
18131 assert_eq!(
18132 wrap_with_prefix(
18133 "// ".to_string(),
18134 "xx \nyy zz aa bb cc".to_string(),
18135 12,
18136 NonZeroU32::new(4).unwrap(),
18137 false,
18138 ),
18139 "// xx yy zz\n// aa bb cc"
18140 );
18141 assert_eq!(
18142 wrap_with_prefix(
18143 String::new(),
18144 "这是什么 \n 钢笔".to_string(),
18145 3,
18146 NonZeroU32::new(4).unwrap(),
18147 false,
18148 ),
18149 "这是什\n么 钢\n笔"
18150 );
18151}
18152
18153pub trait CollaborationHub {
18154 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18155 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18156 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18157}
18158
18159impl CollaborationHub for Entity<Project> {
18160 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18161 self.read(cx).collaborators()
18162 }
18163
18164 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18165 self.read(cx).user_store().read(cx).participant_indices()
18166 }
18167
18168 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18169 let this = self.read(cx);
18170 let user_ids = this.collaborators().values().map(|c| c.user_id);
18171 this.user_store().read_with(cx, |user_store, cx| {
18172 user_store.participant_names(user_ids, cx)
18173 })
18174 }
18175}
18176
18177pub trait SemanticsProvider {
18178 fn hover(
18179 &self,
18180 buffer: &Entity<Buffer>,
18181 position: text::Anchor,
18182 cx: &mut App,
18183 ) -> Option<Task<Vec<project::Hover>>>;
18184
18185 fn inlay_hints(
18186 &self,
18187 buffer_handle: Entity<Buffer>,
18188 range: Range<text::Anchor>,
18189 cx: &mut App,
18190 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18191
18192 fn resolve_inlay_hint(
18193 &self,
18194 hint: InlayHint,
18195 buffer_handle: Entity<Buffer>,
18196 server_id: LanguageServerId,
18197 cx: &mut App,
18198 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18199
18200 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18201
18202 fn document_highlights(
18203 &self,
18204 buffer: &Entity<Buffer>,
18205 position: text::Anchor,
18206 cx: &mut App,
18207 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18208
18209 fn definitions(
18210 &self,
18211 buffer: &Entity<Buffer>,
18212 position: text::Anchor,
18213 kind: GotoDefinitionKind,
18214 cx: &mut App,
18215 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18216
18217 fn range_for_rename(
18218 &self,
18219 buffer: &Entity<Buffer>,
18220 position: text::Anchor,
18221 cx: &mut App,
18222 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18223
18224 fn perform_rename(
18225 &self,
18226 buffer: &Entity<Buffer>,
18227 position: text::Anchor,
18228 new_name: String,
18229 cx: &mut App,
18230 ) -> Option<Task<Result<ProjectTransaction>>>;
18231}
18232
18233pub trait CompletionProvider {
18234 fn completions(
18235 &self,
18236 excerpt_id: ExcerptId,
18237 buffer: &Entity<Buffer>,
18238 buffer_position: text::Anchor,
18239 trigger: CompletionContext,
18240 window: &mut Window,
18241 cx: &mut Context<Editor>,
18242 ) -> Task<Result<Option<Vec<Completion>>>>;
18243
18244 fn resolve_completions(
18245 &self,
18246 buffer: Entity<Buffer>,
18247 completion_indices: Vec<usize>,
18248 completions: Rc<RefCell<Box<[Completion]>>>,
18249 cx: &mut Context<Editor>,
18250 ) -> Task<Result<bool>>;
18251
18252 fn apply_additional_edits_for_completion(
18253 &self,
18254 _buffer: Entity<Buffer>,
18255 _completions: Rc<RefCell<Box<[Completion]>>>,
18256 _completion_index: usize,
18257 _push_to_history: bool,
18258 _cx: &mut Context<Editor>,
18259 ) -> Task<Result<Option<language::Transaction>>> {
18260 Task::ready(Ok(None))
18261 }
18262
18263 fn is_completion_trigger(
18264 &self,
18265 buffer: &Entity<Buffer>,
18266 position: language::Anchor,
18267 text: &str,
18268 trigger_in_words: bool,
18269 cx: &mut Context<Editor>,
18270 ) -> bool;
18271
18272 fn sort_completions(&self) -> bool {
18273 true
18274 }
18275
18276 fn filter_completions(&self) -> bool {
18277 true
18278 }
18279}
18280
18281pub trait CodeActionProvider {
18282 fn id(&self) -> Arc<str>;
18283
18284 fn code_actions(
18285 &self,
18286 buffer: &Entity<Buffer>,
18287 range: Range<text::Anchor>,
18288 window: &mut Window,
18289 cx: &mut App,
18290 ) -> Task<Result<Vec<CodeAction>>>;
18291
18292 fn apply_code_action(
18293 &self,
18294 buffer_handle: Entity<Buffer>,
18295 action: CodeAction,
18296 excerpt_id: ExcerptId,
18297 push_to_history: bool,
18298 window: &mut Window,
18299 cx: &mut App,
18300 ) -> Task<Result<ProjectTransaction>>;
18301}
18302
18303impl CodeActionProvider for Entity<Project> {
18304 fn id(&self) -> Arc<str> {
18305 "project".into()
18306 }
18307
18308 fn code_actions(
18309 &self,
18310 buffer: &Entity<Buffer>,
18311 range: Range<text::Anchor>,
18312 _window: &mut Window,
18313 cx: &mut App,
18314 ) -> Task<Result<Vec<CodeAction>>> {
18315 self.update(cx, |project, cx| {
18316 let code_lens = project.code_lens(buffer, range.clone(), cx);
18317 let code_actions = project.code_actions(buffer, range, None, cx);
18318 cx.background_spawn(async move {
18319 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18320 Ok(code_lens
18321 .context("code lens fetch")?
18322 .into_iter()
18323 .chain(code_actions.context("code action fetch")?)
18324 .collect())
18325 })
18326 })
18327 }
18328
18329 fn apply_code_action(
18330 &self,
18331 buffer_handle: Entity<Buffer>,
18332 action: CodeAction,
18333 _excerpt_id: ExcerptId,
18334 push_to_history: bool,
18335 _window: &mut Window,
18336 cx: &mut App,
18337 ) -> Task<Result<ProjectTransaction>> {
18338 self.update(cx, |project, cx| {
18339 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18340 })
18341 }
18342}
18343
18344fn snippet_completions(
18345 project: &Project,
18346 buffer: &Entity<Buffer>,
18347 buffer_position: text::Anchor,
18348 cx: &mut App,
18349) -> Task<Result<Vec<Completion>>> {
18350 let language = buffer.read(cx).language_at(buffer_position);
18351 let language_name = language.as_ref().map(|language| language.lsp_id());
18352 let snippet_store = project.snippets().read(cx);
18353 let snippets = snippet_store.snippets_for(language_name, cx);
18354
18355 if snippets.is_empty() {
18356 return Task::ready(Ok(vec![]));
18357 }
18358 let snapshot = buffer.read(cx).text_snapshot();
18359 let chars: String = snapshot
18360 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18361 .collect();
18362
18363 let scope = language.map(|language| language.default_scope());
18364 let executor = cx.background_executor().clone();
18365
18366 cx.background_spawn(async move {
18367 let classifier = CharClassifier::new(scope).for_completion(true);
18368 let mut last_word = chars
18369 .chars()
18370 .take_while(|c| classifier.is_word(*c))
18371 .collect::<String>();
18372 last_word = last_word.chars().rev().collect();
18373
18374 if last_word.is_empty() {
18375 return Ok(vec![]);
18376 }
18377
18378 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18379 let to_lsp = |point: &text::Anchor| {
18380 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18381 point_to_lsp(end)
18382 };
18383 let lsp_end = to_lsp(&buffer_position);
18384
18385 let candidates = snippets
18386 .iter()
18387 .enumerate()
18388 .flat_map(|(ix, snippet)| {
18389 snippet
18390 .prefix
18391 .iter()
18392 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18393 })
18394 .collect::<Vec<StringMatchCandidate>>();
18395
18396 let mut matches = fuzzy::match_strings(
18397 &candidates,
18398 &last_word,
18399 last_word.chars().any(|c| c.is_uppercase()),
18400 100,
18401 &Default::default(),
18402 executor,
18403 )
18404 .await;
18405
18406 // Remove all candidates where the query's start does not match the start of any word in the candidate
18407 if let Some(query_start) = last_word.chars().next() {
18408 matches.retain(|string_match| {
18409 split_words(&string_match.string).any(|word| {
18410 // Check that the first codepoint of the word as lowercase matches the first
18411 // codepoint of the query as lowercase
18412 word.chars()
18413 .flat_map(|codepoint| codepoint.to_lowercase())
18414 .zip(query_start.to_lowercase())
18415 .all(|(word_cp, query_cp)| word_cp == query_cp)
18416 })
18417 });
18418 }
18419
18420 let matched_strings = matches
18421 .into_iter()
18422 .map(|m| m.string)
18423 .collect::<HashSet<_>>();
18424
18425 let result: Vec<Completion> = snippets
18426 .into_iter()
18427 .filter_map(|snippet| {
18428 let matching_prefix = snippet
18429 .prefix
18430 .iter()
18431 .find(|prefix| matched_strings.contains(*prefix))?;
18432 let start = as_offset - last_word.len();
18433 let start = snapshot.anchor_before(start);
18434 let range = start..buffer_position;
18435 let lsp_start = to_lsp(&start);
18436 let lsp_range = lsp::Range {
18437 start: lsp_start,
18438 end: lsp_end,
18439 };
18440 Some(Completion {
18441 old_range: range,
18442 new_text: snippet.body.clone(),
18443 source: CompletionSource::Lsp {
18444 server_id: LanguageServerId(usize::MAX),
18445 resolved: true,
18446 lsp_completion: Box::new(lsp::CompletionItem {
18447 label: snippet.prefix.first().unwrap().clone(),
18448 kind: Some(CompletionItemKind::SNIPPET),
18449 label_details: snippet.description.as_ref().map(|description| {
18450 lsp::CompletionItemLabelDetails {
18451 detail: Some(description.clone()),
18452 description: None,
18453 }
18454 }),
18455 insert_text_format: Some(InsertTextFormat::SNIPPET),
18456 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18457 lsp::InsertReplaceEdit {
18458 new_text: snippet.body.clone(),
18459 insert: lsp_range,
18460 replace: lsp_range,
18461 },
18462 )),
18463 filter_text: Some(snippet.body.clone()),
18464 sort_text: Some(char::MAX.to_string()),
18465 ..lsp::CompletionItem::default()
18466 }),
18467 lsp_defaults: None,
18468 },
18469 label: CodeLabel {
18470 text: matching_prefix.clone(),
18471 runs: Vec::new(),
18472 filter_range: 0..matching_prefix.len(),
18473 },
18474 icon_path: None,
18475 documentation: snippet
18476 .description
18477 .clone()
18478 .map(|description| CompletionDocumentation::SingleLine(description.into())),
18479 confirm: None,
18480 })
18481 })
18482 .collect();
18483
18484 Ok(result)
18485 })
18486}
18487
18488impl CompletionProvider for Entity<Project> {
18489 fn completions(
18490 &self,
18491 _excerpt_id: ExcerptId,
18492 buffer: &Entity<Buffer>,
18493 buffer_position: text::Anchor,
18494 options: CompletionContext,
18495 _window: &mut Window,
18496 cx: &mut Context<Editor>,
18497 ) -> Task<Result<Option<Vec<Completion>>>> {
18498 self.update(cx, |project, cx| {
18499 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18500 let project_completions = project.completions(buffer, buffer_position, options, cx);
18501 cx.background_spawn(async move {
18502 let snippets_completions = snippets.await?;
18503 match project_completions.await? {
18504 Some(mut completions) => {
18505 completions.extend(snippets_completions);
18506 Ok(Some(completions))
18507 }
18508 None => {
18509 if snippets_completions.is_empty() {
18510 Ok(None)
18511 } else {
18512 Ok(Some(snippets_completions))
18513 }
18514 }
18515 }
18516 })
18517 })
18518 }
18519
18520 fn resolve_completions(
18521 &self,
18522 buffer: Entity<Buffer>,
18523 completion_indices: Vec<usize>,
18524 completions: Rc<RefCell<Box<[Completion]>>>,
18525 cx: &mut Context<Editor>,
18526 ) -> Task<Result<bool>> {
18527 self.update(cx, |project, cx| {
18528 project.lsp_store().update(cx, |lsp_store, cx| {
18529 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
18530 })
18531 })
18532 }
18533
18534 fn apply_additional_edits_for_completion(
18535 &self,
18536 buffer: Entity<Buffer>,
18537 completions: Rc<RefCell<Box<[Completion]>>>,
18538 completion_index: usize,
18539 push_to_history: bool,
18540 cx: &mut Context<Editor>,
18541 ) -> Task<Result<Option<language::Transaction>>> {
18542 self.update(cx, |project, cx| {
18543 project.lsp_store().update(cx, |lsp_store, cx| {
18544 lsp_store.apply_additional_edits_for_completion(
18545 buffer,
18546 completions,
18547 completion_index,
18548 push_to_history,
18549 cx,
18550 )
18551 })
18552 })
18553 }
18554
18555 fn is_completion_trigger(
18556 &self,
18557 buffer: &Entity<Buffer>,
18558 position: language::Anchor,
18559 text: &str,
18560 trigger_in_words: bool,
18561 cx: &mut Context<Editor>,
18562 ) -> bool {
18563 let mut chars = text.chars();
18564 let char = if let Some(char) = chars.next() {
18565 char
18566 } else {
18567 return false;
18568 };
18569 if chars.next().is_some() {
18570 return false;
18571 }
18572
18573 let buffer = buffer.read(cx);
18574 let snapshot = buffer.snapshot();
18575 if !snapshot.settings_at(position, cx).show_completions_on_input {
18576 return false;
18577 }
18578 let classifier = snapshot.char_classifier_at(position).for_completion(true);
18579 if trigger_in_words && classifier.is_word(char) {
18580 return true;
18581 }
18582
18583 buffer.completion_triggers().contains(text)
18584 }
18585}
18586
18587impl SemanticsProvider for Entity<Project> {
18588 fn hover(
18589 &self,
18590 buffer: &Entity<Buffer>,
18591 position: text::Anchor,
18592 cx: &mut App,
18593 ) -> Option<Task<Vec<project::Hover>>> {
18594 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
18595 }
18596
18597 fn document_highlights(
18598 &self,
18599 buffer: &Entity<Buffer>,
18600 position: text::Anchor,
18601 cx: &mut App,
18602 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
18603 Some(self.update(cx, |project, cx| {
18604 project.document_highlights(buffer, position, cx)
18605 }))
18606 }
18607
18608 fn definitions(
18609 &self,
18610 buffer: &Entity<Buffer>,
18611 position: text::Anchor,
18612 kind: GotoDefinitionKind,
18613 cx: &mut App,
18614 ) -> Option<Task<Result<Vec<LocationLink>>>> {
18615 Some(self.update(cx, |project, cx| match kind {
18616 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
18617 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
18618 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
18619 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
18620 }))
18621 }
18622
18623 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
18624 // TODO: make this work for remote projects
18625 self.update(cx, |this, cx| {
18626 buffer.update(cx, |buffer, cx| {
18627 this.any_language_server_supports_inlay_hints(buffer, cx)
18628 })
18629 })
18630 }
18631
18632 fn inlay_hints(
18633 &self,
18634 buffer_handle: Entity<Buffer>,
18635 range: Range<text::Anchor>,
18636 cx: &mut App,
18637 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
18638 Some(self.update(cx, |project, cx| {
18639 project.inlay_hints(buffer_handle, range, cx)
18640 }))
18641 }
18642
18643 fn resolve_inlay_hint(
18644 &self,
18645 hint: InlayHint,
18646 buffer_handle: Entity<Buffer>,
18647 server_id: LanguageServerId,
18648 cx: &mut App,
18649 ) -> Option<Task<anyhow::Result<InlayHint>>> {
18650 Some(self.update(cx, |project, cx| {
18651 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
18652 }))
18653 }
18654
18655 fn range_for_rename(
18656 &self,
18657 buffer: &Entity<Buffer>,
18658 position: text::Anchor,
18659 cx: &mut App,
18660 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
18661 Some(self.update(cx, |project, cx| {
18662 let buffer = buffer.clone();
18663 let task = project.prepare_rename(buffer.clone(), position, cx);
18664 cx.spawn(async move |_, cx| {
18665 Ok(match task.await? {
18666 PrepareRenameResponse::Success(range) => Some(range),
18667 PrepareRenameResponse::InvalidPosition => None,
18668 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
18669 // Fallback on using TreeSitter info to determine identifier range
18670 buffer.update(cx, |buffer, _| {
18671 let snapshot = buffer.snapshot();
18672 let (range, kind) = snapshot.surrounding_word(position);
18673 if kind != Some(CharKind::Word) {
18674 return None;
18675 }
18676 Some(
18677 snapshot.anchor_before(range.start)
18678 ..snapshot.anchor_after(range.end),
18679 )
18680 })?
18681 }
18682 })
18683 })
18684 }))
18685 }
18686
18687 fn perform_rename(
18688 &self,
18689 buffer: &Entity<Buffer>,
18690 position: text::Anchor,
18691 new_name: String,
18692 cx: &mut App,
18693 ) -> Option<Task<Result<ProjectTransaction>>> {
18694 Some(self.update(cx, |project, cx| {
18695 project.perform_rename(buffer.clone(), position, new_name, cx)
18696 }))
18697 }
18698}
18699
18700fn inlay_hint_settings(
18701 location: Anchor,
18702 snapshot: &MultiBufferSnapshot,
18703 cx: &mut Context<Editor>,
18704) -> InlayHintSettings {
18705 let file = snapshot.file_at(location);
18706 let language = snapshot.language_at(location).map(|l| l.name());
18707 language_settings(language, file, cx).inlay_hints
18708}
18709
18710fn consume_contiguous_rows(
18711 contiguous_row_selections: &mut Vec<Selection<Point>>,
18712 selection: &Selection<Point>,
18713 display_map: &DisplaySnapshot,
18714 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
18715) -> (MultiBufferRow, MultiBufferRow) {
18716 contiguous_row_selections.push(selection.clone());
18717 let start_row = MultiBufferRow(selection.start.row);
18718 let mut end_row = ending_row(selection, display_map);
18719
18720 while let Some(next_selection) = selections.peek() {
18721 if next_selection.start.row <= end_row.0 {
18722 end_row = ending_row(next_selection, display_map);
18723 contiguous_row_selections.push(selections.next().unwrap().clone());
18724 } else {
18725 break;
18726 }
18727 }
18728 (start_row, end_row)
18729}
18730
18731fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
18732 if next_selection.end.column > 0 || next_selection.is_empty() {
18733 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
18734 } else {
18735 MultiBufferRow(next_selection.end.row)
18736 }
18737}
18738
18739impl EditorSnapshot {
18740 pub fn remote_selections_in_range<'a>(
18741 &'a self,
18742 range: &'a Range<Anchor>,
18743 collaboration_hub: &dyn CollaborationHub,
18744 cx: &'a App,
18745 ) -> impl 'a + Iterator<Item = RemoteSelection> {
18746 let participant_names = collaboration_hub.user_names(cx);
18747 let participant_indices = collaboration_hub.user_participant_indices(cx);
18748 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
18749 let collaborators_by_replica_id = collaborators_by_peer_id
18750 .iter()
18751 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
18752 .collect::<HashMap<_, _>>();
18753 self.buffer_snapshot
18754 .selections_in_range(range, false)
18755 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
18756 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
18757 let participant_index = participant_indices.get(&collaborator.user_id).copied();
18758 let user_name = participant_names.get(&collaborator.user_id).cloned();
18759 Some(RemoteSelection {
18760 replica_id,
18761 selection,
18762 cursor_shape,
18763 line_mode,
18764 participant_index,
18765 peer_id: collaborator.peer_id,
18766 user_name,
18767 })
18768 })
18769 }
18770
18771 pub fn hunks_for_ranges(
18772 &self,
18773 ranges: impl IntoIterator<Item = Range<Point>>,
18774 ) -> Vec<MultiBufferDiffHunk> {
18775 let mut hunks = Vec::new();
18776 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
18777 HashMap::default();
18778 for query_range in ranges {
18779 let query_rows =
18780 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
18781 for hunk in self.buffer_snapshot.diff_hunks_in_range(
18782 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
18783 ) {
18784 // Include deleted hunks that are adjacent to the query range, because
18785 // otherwise they would be missed.
18786 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
18787 if hunk.status().is_deleted() {
18788 intersects_range |= hunk.row_range.start == query_rows.end;
18789 intersects_range |= hunk.row_range.end == query_rows.start;
18790 }
18791 if intersects_range {
18792 if !processed_buffer_rows
18793 .entry(hunk.buffer_id)
18794 .or_default()
18795 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
18796 {
18797 continue;
18798 }
18799 hunks.push(hunk);
18800 }
18801 }
18802 }
18803
18804 hunks
18805 }
18806
18807 fn display_diff_hunks_for_rows<'a>(
18808 &'a self,
18809 display_rows: Range<DisplayRow>,
18810 folded_buffers: &'a HashSet<BufferId>,
18811 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
18812 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
18813 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
18814
18815 self.buffer_snapshot
18816 .diff_hunks_in_range(buffer_start..buffer_end)
18817 .filter_map(|hunk| {
18818 if folded_buffers.contains(&hunk.buffer_id) {
18819 return None;
18820 }
18821
18822 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
18823 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
18824
18825 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
18826 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
18827
18828 let display_hunk = if hunk_display_start.column() != 0 {
18829 DisplayDiffHunk::Folded {
18830 display_row: hunk_display_start.row(),
18831 }
18832 } else {
18833 let mut end_row = hunk_display_end.row();
18834 if hunk_display_end.column() > 0 {
18835 end_row.0 += 1;
18836 }
18837 let is_created_file = hunk.is_created_file();
18838 DisplayDiffHunk::Unfolded {
18839 status: hunk.status(),
18840 diff_base_byte_range: hunk.diff_base_byte_range,
18841 display_row_range: hunk_display_start.row()..end_row,
18842 multi_buffer_range: Anchor::range_in_buffer(
18843 hunk.excerpt_id,
18844 hunk.buffer_id,
18845 hunk.buffer_range,
18846 ),
18847 is_created_file,
18848 }
18849 };
18850
18851 Some(display_hunk)
18852 })
18853 }
18854
18855 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
18856 self.display_snapshot.buffer_snapshot.language_at(position)
18857 }
18858
18859 pub fn is_focused(&self) -> bool {
18860 self.is_focused
18861 }
18862
18863 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
18864 self.placeholder_text.as_ref()
18865 }
18866
18867 pub fn scroll_position(&self) -> gpui::Point<f32> {
18868 self.scroll_anchor.scroll_position(&self.display_snapshot)
18869 }
18870
18871 fn gutter_dimensions(
18872 &self,
18873 font_id: FontId,
18874 font_size: Pixels,
18875 max_line_number_width: Pixels,
18876 cx: &App,
18877 ) -> Option<GutterDimensions> {
18878 if !self.show_gutter {
18879 return None;
18880 }
18881
18882 let descent = cx.text_system().descent(font_id, font_size);
18883 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
18884 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
18885
18886 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
18887 matches!(
18888 ProjectSettings::get_global(cx).git.git_gutter,
18889 Some(GitGutterSetting::TrackedFiles)
18890 )
18891 });
18892 let gutter_settings = EditorSettings::get_global(cx).gutter;
18893 let show_line_numbers = self
18894 .show_line_numbers
18895 .unwrap_or(gutter_settings.line_numbers);
18896 let line_gutter_width = if show_line_numbers {
18897 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
18898 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
18899 max_line_number_width.max(min_width_for_number_on_gutter)
18900 } else {
18901 0.0.into()
18902 };
18903
18904 let show_code_actions = self
18905 .show_code_actions
18906 .unwrap_or(gutter_settings.code_actions);
18907
18908 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
18909 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
18910
18911 let git_blame_entries_width =
18912 self.git_blame_gutter_max_author_length
18913 .map(|max_author_length| {
18914 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
18915
18916 /// The number of characters to dedicate to gaps and margins.
18917 const SPACING_WIDTH: usize = 4;
18918
18919 let max_char_count = max_author_length
18920 .min(GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED)
18921 + ::git::SHORT_SHA_LENGTH
18922 + MAX_RELATIVE_TIMESTAMP.len()
18923 + SPACING_WIDTH;
18924
18925 em_advance * max_char_count
18926 });
18927
18928 let is_singleton = self.buffer_snapshot.is_singleton();
18929
18930 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
18931 left_padding += if !is_singleton {
18932 em_width * 4.0
18933 } else if show_code_actions || show_runnables || show_breakpoints {
18934 em_width * 3.0
18935 } else if show_git_gutter && show_line_numbers {
18936 em_width * 2.0
18937 } else if show_git_gutter || show_line_numbers {
18938 em_width
18939 } else {
18940 px(0.)
18941 };
18942
18943 let shows_folds = is_singleton && gutter_settings.folds;
18944
18945 let right_padding = if shows_folds && show_line_numbers {
18946 em_width * 4.0
18947 } else if shows_folds || (!is_singleton && show_line_numbers) {
18948 em_width * 3.0
18949 } else if show_line_numbers {
18950 em_width
18951 } else {
18952 px(0.)
18953 };
18954
18955 Some(GutterDimensions {
18956 left_padding,
18957 right_padding,
18958 width: line_gutter_width + left_padding + right_padding,
18959 margin: -descent,
18960 git_blame_entries_width,
18961 })
18962 }
18963
18964 pub fn render_crease_toggle(
18965 &self,
18966 buffer_row: MultiBufferRow,
18967 row_contains_cursor: bool,
18968 editor: Entity<Editor>,
18969 window: &mut Window,
18970 cx: &mut App,
18971 ) -> Option<AnyElement> {
18972 let folded = self.is_line_folded(buffer_row);
18973 let mut is_foldable = false;
18974
18975 if let Some(crease) = self
18976 .crease_snapshot
18977 .query_row(buffer_row, &self.buffer_snapshot)
18978 {
18979 is_foldable = true;
18980 match crease {
18981 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
18982 if let Some(render_toggle) = render_toggle {
18983 let toggle_callback =
18984 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
18985 if folded {
18986 editor.update(cx, |editor, cx| {
18987 editor.fold_at(&crate::FoldAt { buffer_row }, window, cx)
18988 });
18989 } else {
18990 editor.update(cx, |editor, cx| {
18991 editor.unfold_at(
18992 &crate::UnfoldAt { buffer_row },
18993 window,
18994 cx,
18995 )
18996 });
18997 }
18998 });
18999 return Some((render_toggle)(
19000 buffer_row,
19001 folded,
19002 toggle_callback,
19003 window,
19004 cx,
19005 ));
19006 }
19007 }
19008 }
19009 }
19010
19011 is_foldable |= self.starts_indent(buffer_row);
19012
19013 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19014 Some(
19015 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19016 .toggle_state(folded)
19017 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19018 if folded {
19019 this.unfold_at(&UnfoldAt { buffer_row }, window, cx);
19020 } else {
19021 this.fold_at(&FoldAt { buffer_row }, window, cx);
19022 }
19023 }))
19024 .into_any_element(),
19025 )
19026 } else {
19027 None
19028 }
19029 }
19030
19031 pub fn render_crease_trailer(
19032 &self,
19033 buffer_row: MultiBufferRow,
19034 window: &mut Window,
19035 cx: &mut App,
19036 ) -> Option<AnyElement> {
19037 let folded = self.is_line_folded(buffer_row);
19038 if let Crease::Inline { render_trailer, .. } = self
19039 .crease_snapshot
19040 .query_row(buffer_row, &self.buffer_snapshot)?
19041 {
19042 let render_trailer = render_trailer.as_ref()?;
19043 Some(render_trailer(buffer_row, folded, window, cx))
19044 } else {
19045 None
19046 }
19047 }
19048}
19049
19050impl Deref for EditorSnapshot {
19051 type Target = DisplaySnapshot;
19052
19053 fn deref(&self) -> &Self::Target {
19054 &self.display_snapshot
19055 }
19056}
19057
19058#[derive(Clone, Debug, PartialEq, Eq)]
19059pub enum EditorEvent {
19060 InputIgnored {
19061 text: Arc<str>,
19062 },
19063 InputHandled {
19064 utf16_range_to_replace: Option<Range<isize>>,
19065 text: Arc<str>,
19066 },
19067 ExcerptsAdded {
19068 buffer: Entity<Buffer>,
19069 predecessor: ExcerptId,
19070 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19071 },
19072 ExcerptsRemoved {
19073 ids: Vec<ExcerptId>,
19074 },
19075 BufferFoldToggled {
19076 ids: Vec<ExcerptId>,
19077 folded: bool,
19078 },
19079 ExcerptsEdited {
19080 ids: Vec<ExcerptId>,
19081 },
19082 ExcerptsExpanded {
19083 ids: Vec<ExcerptId>,
19084 },
19085 BufferEdited,
19086 Edited {
19087 transaction_id: clock::Lamport,
19088 },
19089 Reparsed(BufferId),
19090 Focused,
19091 FocusedIn,
19092 Blurred,
19093 DirtyChanged,
19094 Saved,
19095 TitleChanged,
19096 DiffBaseChanged,
19097 SelectionsChanged {
19098 local: bool,
19099 },
19100 ScrollPositionChanged {
19101 local: bool,
19102 autoscroll: bool,
19103 },
19104 Closed,
19105 TransactionUndone {
19106 transaction_id: clock::Lamport,
19107 },
19108 TransactionBegun {
19109 transaction_id: clock::Lamport,
19110 },
19111 Reloaded,
19112 CursorShapeChanged,
19113 PushedToNavHistory {
19114 anchor: Anchor,
19115 is_deactivate: bool,
19116 },
19117}
19118
19119impl EventEmitter<EditorEvent> for Editor {}
19120
19121impl Focusable for Editor {
19122 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19123 self.focus_handle.clone()
19124 }
19125}
19126
19127impl Render for Editor {
19128 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19129 let settings = ThemeSettings::get_global(cx);
19130
19131 let mut text_style = match self.mode {
19132 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19133 color: cx.theme().colors().editor_foreground,
19134 font_family: settings.ui_font.family.clone(),
19135 font_features: settings.ui_font.features.clone(),
19136 font_fallbacks: settings.ui_font.fallbacks.clone(),
19137 font_size: rems(0.875).into(),
19138 font_weight: settings.ui_font.weight,
19139 line_height: relative(settings.buffer_line_height.value()),
19140 ..Default::default()
19141 },
19142 EditorMode::Full => TextStyle {
19143 color: cx.theme().colors().editor_foreground,
19144 font_family: settings.buffer_font.family.clone(),
19145 font_features: settings.buffer_font.features.clone(),
19146 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19147 font_size: settings.buffer_font_size(cx).into(),
19148 font_weight: settings.buffer_font.weight,
19149 line_height: relative(settings.buffer_line_height.value()),
19150 ..Default::default()
19151 },
19152 };
19153 if let Some(text_style_refinement) = &self.text_style_refinement {
19154 text_style.refine(text_style_refinement)
19155 }
19156
19157 let background = match self.mode {
19158 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19159 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19160 EditorMode::Full => cx.theme().colors().editor_background,
19161 };
19162
19163 EditorElement::new(
19164 &cx.entity(),
19165 EditorStyle {
19166 background,
19167 local_player: cx.theme().players().local(),
19168 text: text_style,
19169 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19170 syntax: cx.theme().syntax().clone(),
19171 status: cx.theme().status().clone(),
19172 inlay_hints_style: make_inlay_hints_style(cx),
19173 inline_completion_styles: make_suggestion_styles(cx),
19174 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19175 },
19176 )
19177 }
19178}
19179
19180impl EntityInputHandler for Editor {
19181 fn text_for_range(
19182 &mut self,
19183 range_utf16: Range<usize>,
19184 adjusted_range: &mut Option<Range<usize>>,
19185 _: &mut Window,
19186 cx: &mut Context<Self>,
19187 ) -> Option<String> {
19188 let snapshot = self.buffer.read(cx).read(cx);
19189 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19190 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19191 if (start.0..end.0) != range_utf16 {
19192 adjusted_range.replace(start.0..end.0);
19193 }
19194 Some(snapshot.text_for_range(start..end).collect())
19195 }
19196
19197 fn selected_text_range(
19198 &mut self,
19199 ignore_disabled_input: bool,
19200 _: &mut Window,
19201 cx: &mut Context<Self>,
19202 ) -> Option<UTF16Selection> {
19203 // Prevent the IME menu from appearing when holding down an alphabetic key
19204 // while input is disabled.
19205 if !ignore_disabled_input && !self.input_enabled {
19206 return None;
19207 }
19208
19209 let selection = self.selections.newest::<OffsetUtf16>(cx);
19210 let range = selection.range();
19211
19212 Some(UTF16Selection {
19213 range: range.start.0..range.end.0,
19214 reversed: selection.reversed,
19215 })
19216 }
19217
19218 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19219 let snapshot = self.buffer.read(cx).read(cx);
19220 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19221 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19222 }
19223
19224 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19225 self.clear_highlights::<InputComposition>(cx);
19226 self.ime_transaction.take();
19227 }
19228
19229 fn replace_text_in_range(
19230 &mut self,
19231 range_utf16: Option<Range<usize>>,
19232 text: &str,
19233 window: &mut Window,
19234 cx: &mut Context<Self>,
19235 ) {
19236 if !self.input_enabled {
19237 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19238 return;
19239 }
19240
19241 self.transact(window, cx, |this, window, cx| {
19242 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19243 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19244 Some(this.selection_replacement_ranges(range_utf16, cx))
19245 } else {
19246 this.marked_text_ranges(cx)
19247 };
19248
19249 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19250 let newest_selection_id = this.selections.newest_anchor().id;
19251 this.selections
19252 .all::<OffsetUtf16>(cx)
19253 .iter()
19254 .zip(ranges_to_replace.iter())
19255 .find_map(|(selection, range)| {
19256 if selection.id == newest_selection_id {
19257 Some(
19258 (range.start.0 as isize - selection.head().0 as isize)
19259 ..(range.end.0 as isize - selection.head().0 as isize),
19260 )
19261 } else {
19262 None
19263 }
19264 })
19265 });
19266
19267 cx.emit(EditorEvent::InputHandled {
19268 utf16_range_to_replace: range_to_replace,
19269 text: text.into(),
19270 });
19271
19272 if let Some(new_selected_ranges) = new_selected_ranges {
19273 this.change_selections(None, window, cx, |selections| {
19274 selections.select_ranges(new_selected_ranges)
19275 });
19276 this.backspace(&Default::default(), window, cx);
19277 }
19278
19279 this.handle_input(text, window, cx);
19280 });
19281
19282 if let Some(transaction) = self.ime_transaction {
19283 self.buffer.update(cx, |buffer, cx| {
19284 buffer.group_until_transaction(transaction, cx);
19285 });
19286 }
19287
19288 self.unmark_text(window, cx);
19289 }
19290
19291 fn replace_and_mark_text_in_range(
19292 &mut self,
19293 range_utf16: Option<Range<usize>>,
19294 text: &str,
19295 new_selected_range_utf16: Option<Range<usize>>,
19296 window: &mut Window,
19297 cx: &mut Context<Self>,
19298 ) {
19299 if !self.input_enabled {
19300 return;
19301 }
19302
19303 let transaction = self.transact(window, cx, |this, window, cx| {
19304 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19305 let snapshot = this.buffer.read(cx).read(cx);
19306 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19307 for marked_range in &mut marked_ranges {
19308 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19309 marked_range.start.0 += relative_range_utf16.start;
19310 marked_range.start =
19311 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19312 marked_range.end =
19313 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19314 }
19315 }
19316 Some(marked_ranges)
19317 } else if let Some(range_utf16) = range_utf16 {
19318 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19319 Some(this.selection_replacement_ranges(range_utf16, cx))
19320 } else {
19321 None
19322 };
19323
19324 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19325 let newest_selection_id = this.selections.newest_anchor().id;
19326 this.selections
19327 .all::<OffsetUtf16>(cx)
19328 .iter()
19329 .zip(ranges_to_replace.iter())
19330 .find_map(|(selection, range)| {
19331 if selection.id == newest_selection_id {
19332 Some(
19333 (range.start.0 as isize - selection.head().0 as isize)
19334 ..(range.end.0 as isize - selection.head().0 as isize),
19335 )
19336 } else {
19337 None
19338 }
19339 })
19340 });
19341
19342 cx.emit(EditorEvent::InputHandled {
19343 utf16_range_to_replace: range_to_replace,
19344 text: text.into(),
19345 });
19346
19347 if let Some(ranges) = ranges_to_replace {
19348 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19349 }
19350
19351 let marked_ranges = {
19352 let snapshot = this.buffer.read(cx).read(cx);
19353 this.selections
19354 .disjoint_anchors()
19355 .iter()
19356 .map(|selection| {
19357 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19358 })
19359 .collect::<Vec<_>>()
19360 };
19361
19362 if text.is_empty() {
19363 this.unmark_text(window, cx);
19364 } else {
19365 this.highlight_text::<InputComposition>(
19366 marked_ranges.clone(),
19367 HighlightStyle {
19368 underline: Some(UnderlineStyle {
19369 thickness: px(1.),
19370 color: None,
19371 wavy: false,
19372 }),
19373 ..Default::default()
19374 },
19375 cx,
19376 );
19377 }
19378
19379 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19380 let use_autoclose = this.use_autoclose;
19381 let use_auto_surround = this.use_auto_surround;
19382 this.set_use_autoclose(false);
19383 this.set_use_auto_surround(false);
19384 this.handle_input(text, window, cx);
19385 this.set_use_autoclose(use_autoclose);
19386 this.set_use_auto_surround(use_auto_surround);
19387
19388 if let Some(new_selected_range) = new_selected_range_utf16 {
19389 let snapshot = this.buffer.read(cx).read(cx);
19390 let new_selected_ranges = marked_ranges
19391 .into_iter()
19392 .map(|marked_range| {
19393 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19394 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19395 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19396 snapshot.clip_offset_utf16(new_start, Bias::Left)
19397 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19398 })
19399 .collect::<Vec<_>>();
19400
19401 drop(snapshot);
19402 this.change_selections(None, window, cx, |selections| {
19403 selections.select_ranges(new_selected_ranges)
19404 });
19405 }
19406 });
19407
19408 self.ime_transaction = self.ime_transaction.or(transaction);
19409 if let Some(transaction) = self.ime_transaction {
19410 self.buffer.update(cx, |buffer, cx| {
19411 buffer.group_until_transaction(transaction, cx);
19412 });
19413 }
19414
19415 if self.text_highlights::<InputComposition>(cx).is_none() {
19416 self.ime_transaction.take();
19417 }
19418 }
19419
19420 fn bounds_for_range(
19421 &mut self,
19422 range_utf16: Range<usize>,
19423 element_bounds: gpui::Bounds<Pixels>,
19424 window: &mut Window,
19425 cx: &mut Context<Self>,
19426 ) -> Option<gpui::Bounds<Pixels>> {
19427 let text_layout_details = self.text_layout_details(window);
19428 let gpui::Size {
19429 width: em_width,
19430 height: line_height,
19431 } = self.character_size(window);
19432
19433 let snapshot = self.snapshot(window, cx);
19434 let scroll_position = snapshot.scroll_position();
19435 let scroll_left = scroll_position.x * em_width;
19436
19437 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19438 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19439 + self.gutter_dimensions.width
19440 + self.gutter_dimensions.margin;
19441 let y = line_height * (start.row().as_f32() - scroll_position.y);
19442
19443 Some(Bounds {
19444 origin: element_bounds.origin + point(x, y),
19445 size: size(em_width, line_height),
19446 })
19447 }
19448
19449 fn character_index_for_point(
19450 &mut self,
19451 point: gpui::Point<Pixels>,
19452 _window: &mut Window,
19453 _cx: &mut Context<Self>,
19454 ) -> Option<usize> {
19455 let position_map = self.last_position_map.as_ref()?;
19456 if !position_map.text_hitbox.contains(&point) {
19457 return None;
19458 }
19459 let display_point = position_map.point_for_position(point).previous_valid;
19460 let anchor = position_map
19461 .snapshot
19462 .display_point_to_anchor(display_point, Bias::Left);
19463 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19464 Some(utf16_offset.0)
19465 }
19466}
19467
19468trait SelectionExt {
19469 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19470 fn spanned_rows(
19471 &self,
19472 include_end_if_at_line_start: bool,
19473 map: &DisplaySnapshot,
19474 ) -> Range<MultiBufferRow>;
19475}
19476
19477impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19478 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19479 let start = self
19480 .start
19481 .to_point(&map.buffer_snapshot)
19482 .to_display_point(map);
19483 let end = self
19484 .end
19485 .to_point(&map.buffer_snapshot)
19486 .to_display_point(map);
19487 if self.reversed {
19488 end..start
19489 } else {
19490 start..end
19491 }
19492 }
19493
19494 fn spanned_rows(
19495 &self,
19496 include_end_if_at_line_start: bool,
19497 map: &DisplaySnapshot,
19498 ) -> Range<MultiBufferRow> {
19499 let start = self.start.to_point(&map.buffer_snapshot);
19500 let mut end = self.end.to_point(&map.buffer_snapshot);
19501 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19502 end.row -= 1;
19503 }
19504
19505 let buffer_start = map.prev_line_boundary(start).0;
19506 let buffer_end = map.next_line_boundary(end).0;
19507 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19508 }
19509}
19510
19511impl<T: InvalidationRegion> InvalidationStack<T> {
19512 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
19513 where
19514 S: Clone + ToOffset,
19515 {
19516 while let Some(region) = self.last() {
19517 let all_selections_inside_invalidation_ranges =
19518 if selections.len() == region.ranges().len() {
19519 selections
19520 .iter()
19521 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
19522 .all(|(selection, invalidation_range)| {
19523 let head = selection.head().to_offset(buffer);
19524 invalidation_range.start <= head && invalidation_range.end >= head
19525 })
19526 } else {
19527 false
19528 };
19529
19530 if all_selections_inside_invalidation_ranges {
19531 break;
19532 } else {
19533 self.pop();
19534 }
19535 }
19536 }
19537}
19538
19539impl<T> Default for InvalidationStack<T> {
19540 fn default() -> Self {
19541 Self(Default::default())
19542 }
19543}
19544
19545impl<T> Deref for InvalidationStack<T> {
19546 type Target = Vec<T>;
19547
19548 fn deref(&self) -> &Self::Target {
19549 &self.0
19550 }
19551}
19552
19553impl<T> DerefMut for InvalidationStack<T> {
19554 fn deref_mut(&mut self) -> &mut Self::Target {
19555 &mut self.0
19556 }
19557}
19558
19559impl InvalidationRegion for SnippetState {
19560 fn ranges(&self) -> &[Range<Anchor>] {
19561 &self.ranges[self.active_index]
19562 }
19563}
19564
19565pub fn diagnostic_block_renderer(
19566 diagnostic: Diagnostic,
19567 max_message_rows: Option<u8>,
19568 allow_closing: bool,
19569) -> RenderBlock {
19570 let (text_without_backticks, code_ranges) =
19571 highlight_diagnostic_message(&diagnostic, max_message_rows);
19572
19573 Arc::new(move |cx: &mut BlockContext| {
19574 let group_id: SharedString = cx.block_id.to_string().into();
19575
19576 let mut text_style = cx.window.text_style().clone();
19577 text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
19578 let theme_settings = ThemeSettings::get_global(cx);
19579 text_style.font_family = theme_settings.buffer_font.family.clone();
19580 text_style.font_style = theme_settings.buffer_font.style;
19581 text_style.font_features = theme_settings.buffer_font.features.clone();
19582 text_style.font_weight = theme_settings.buffer_font.weight;
19583
19584 let multi_line_diagnostic = diagnostic.message.contains('\n');
19585
19586 let buttons = |diagnostic: &Diagnostic| {
19587 if multi_line_diagnostic {
19588 v_flex()
19589 } else {
19590 h_flex()
19591 }
19592 .when(allow_closing, |div| {
19593 div.children(diagnostic.is_primary.then(|| {
19594 IconButton::new("close-block", IconName::XCircle)
19595 .icon_color(Color::Muted)
19596 .size(ButtonSize::Compact)
19597 .style(ButtonStyle::Transparent)
19598 .visible_on_hover(group_id.clone())
19599 .on_click(move |_click, window, cx| {
19600 window.dispatch_action(Box::new(Cancel), cx)
19601 })
19602 .tooltip(|window, cx| {
19603 Tooltip::for_action("Close Diagnostics", &Cancel, window, cx)
19604 })
19605 }))
19606 })
19607 .child(
19608 IconButton::new("copy-block", IconName::Copy)
19609 .icon_color(Color::Muted)
19610 .size(ButtonSize::Compact)
19611 .style(ButtonStyle::Transparent)
19612 .visible_on_hover(group_id.clone())
19613 .on_click({
19614 let message = diagnostic.message.clone();
19615 move |_click, _, cx| {
19616 cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
19617 }
19618 })
19619 .tooltip(Tooltip::text("Copy diagnostic message")),
19620 )
19621 };
19622
19623 let icon_size = buttons(&diagnostic).into_any_element().layout_as_root(
19624 AvailableSpace::min_size(),
19625 cx.window,
19626 cx.app,
19627 );
19628
19629 h_flex()
19630 .id(cx.block_id)
19631 .group(group_id.clone())
19632 .relative()
19633 .size_full()
19634 .block_mouse_down()
19635 .pl(cx.gutter_dimensions.width)
19636 .w(cx.max_width - cx.gutter_dimensions.full_width())
19637 .child(
19638 div()
19639 .flex()
19640 .w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
19641 .flex_shrink(),
19642 )
19643 .child(buttons(&diagnostic))
19644 .child(div().flex().flex_shrink_0().child(
19645 StyledText::new(text_without_backticks.clone()).with_default_highlights(
19646 &text_style,
19647 code_ranges.iter().map(|range| {
19648 (
19649 range.clone(),
19650 HighlightStyle {
19651 font_weight: Some(FontWeight::BOLD),
19652 ..Default::default()
19653 },
19654 )
19655 }),
19656 ),
19657 ))
19658 .into_any_element()
19659 })
19660}
19661
19662fn inline_completion_edit_text(
19663 current_snapshot: &BufferSnapshot,
19664 edits: &[(Range<Anchor>, String)],
19665 edit_preview: &EditPreview,
19666 include_deletions: bool,
19667 cx: &App,
19668) -> HighlightedText {
19669 let edits = edits
19670 .iter()
19671 .map(|(anchor, text)| {
19672 (
19673 anchor.start.text_anchor..anchor.end.text_anchor,
19674 text.clone(),
19675 )
19676 })
19677 .collect::<Vec<_>>();
19678
19679 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
19680}
19681
19682pub fn highlight_diagnostic_message(
19683 diagnostic: &Diagnostic,
19684 mut max_message_rows: Option<u8>,
19685) -> (SharedString, Vec<Range<usize>>) {
19686 let mut text_without_backticks = String::new();
19687 let mut code_ranges = Vec::new();
19688
19689 if let Some(source) = &diagnostic.source {
19690 text_without_backticks.push_str(source);
19691 code_ranges.push(0..source.len());
19692 text_without_backticks.push_str(": ");
19693 }
19694
19695 let mut prev_offset = 0;
19696 let mut in_code_block = false;
19697 let has_row_limit = max_message_rows.is_some();
19698 let mut newline_indices = diagnostic
19699 .message
19700 .match_indices('\n')
19701 .filter(|_| has_row_limit)
19702 .map(|(ix, _)| ix)
19703 .fuse()
19704 .peekable();
19705
19706 for (quote_ix, _) in diagnostic
19707 .message
19708 .match_indices('`')
19709 .chain([(diagnostic.message.len(), "")])
19710 {
19711 let mut first_newline_ix = None;
19712 let mut last_newline_ix = None;
19713 while let Some(newline_ix) = newline_indices.peek() {
19714 if *newline_ix < quote_ix {
19715 if first_newline_ix.is_none() {
19716 first_newline_ix = Some(*newline_ix);
19717 }
19718 last_newline_ix = Some(*newline_ix);
19719
19720 if let Some(rows_left) = &mut max_message_rows {
19721 if *rows_left == 0 {
19722 break;
19723 } else {
19724 *rows_left -= 1;
19725 }
19726 }
19727 let _ = newline_indices.next();
19728 } else {
19729 break;
19730 }
19731 }
19732 let prev_len = text_without_backticks.len();
19733 let new_text = &diagnostic.message[prev_offset..first_newline_ix.unwrap_or(quote_ix)];
19734 text_without_backticks.push_str(new_text);
19735 if in_code_block {
19736 code_ranges.push(prev_len..text_without_backticks.len());
19737 }
19738 prev_offset = last_newline_ix.unwrap_or(quote_ix) + 1;
19739 in_code_block = !in_code_block;
19740 if first_newline_ix.map_or(false, |newline_ix| newline_ix < quote_ix) {
19741 text_without_backticks.push_str("...");
19742 break;
19743 }
19744 }
19745
19746 (text_without_backticks.into(), code_ranges)
19747}
19748
19749fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
19750 match severity {
19751 DiagnosticSeverity::ERROR => colors.error,
19752 DiagnosticSeverity::WARNING => colors.warning,
19753 DiagnosticSeverity::INFORMATION => colors.info,
19754 DiagnosticSeverity::HINT => colors.info,
19755 _ => colors.ignored,
19756 }
19757}
19758
19759pub fn styled_runs_for_code_label<'a>(
19760 label: &'a CodeLabel,
19761 syntax_theme: &'a theme::SyntaxTheme,
19762) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
19763 let fade_out = HighlightStyle {
19764 fade_out: Some(0.35),
19765 ..Default::default()
19766 };
19767
19768 let mut prev_end = label.filter_range.end;
19769 label
19770 .runs
19771 .iter()
19772 .enumerate()
19773 .flat_map(move |(ix, (range, highlight_id))| {
19774 let style = if let Some(style) = highlight_id.style(syntax_theme) {
19775 style
19776 } else {
19777 return Default::default();
19778 };
19779 let mut muted_style = style;
19780 muted_style.highlight(fade_out);
19781
19782 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
19783 if range.start >= label.filter_range.end {
19784 if range.start > prev_end {
19785 runs.push((prev_end..range.start, fade_out));
19786 }
19787 runs.push((range.clone(), muted_style));
19788 } else if range.end <= label.filter_range.end {
19789 runs.push((range.clone(), style));
19790 } else {
19791 runs.push((range.start..label.filter_range.end, style));
19792 runs.push((label.filter_range.end..range.end, muted_style));
19793 }
19794 prev_end = cmp::max(prev_end, range.end);
19795
19796 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
19797 runs.push((prev_end..label.text.len(), fade_out));
19798 }
19799
19800 runs
19801 })
19802}
19803
19804pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
19805 let mut prev_index = 0;
19806 let mut prev_codepoint: Option<char> = None;
19807 text.char_indices()
19808 .chain([(text.len(), '\0')])
19809 .filter_map(move |(index, codepoint)| {
19810 let prev_codepoint = prev_codepoint.replace(codepoint)?;
19811 let is_boundary = index == text.len()
19812 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
19813 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
19814 if is_boundary {
19815 let chunk = &text[prev_index..index];
19816 prev_index = index;
19817 Some(chunk)
19818 } else {
19819 None
19820 }
19821 })
19822}
19823
19824pub trait RangeToAnchorExt: Sized {
19825 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
19826
19827 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
19828 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
19829 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
19830 }
19831}
19832
19833impl<T: ToOffset> RangeToAnchorExt for Range<T> {
19834 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
19835 let start_offset = self.start.to_offset(snapshot);
19836 let end_offset = self.end.to_offset(snapshot);
19837 if start_offset == end_offset {
19838 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
19839 } else {
19840 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
19841 }
19842 }
19843}
19844
19845pub trait RowExt {
19846 fn as_f32(&self) -> f32;
19847
19848 fn next_row(&self) -> Self;
19849
19850 fn previous_row(&self) -> Self;
19851
19852 fn minus(&self, other: Self) -> u32;
19853}
19854
19855impl RowExt for DisplayRow {
19856 fn as_f32(&self) -> f32 {
19857 self.0 as f32
19858 }
19859
19860 fn next_row(&self) -> Self {
19861 Self(self.0 + 1)
19862 }
19863
19864 fn previous_row(&self) -> Self {
19865 Self(self.0.saturating_sub(1))
19866 }
19867
19868 fn minus(&self, other: Self) -> u32 {
19869 self.0 - other.0
19870 }
19871}
19872
19873impl RowExt for MultiBufferRow {
19874 fn as_f32(&self) -> f32 {
19875 self.0 as f32
19876 }
19877
19878 fn next_row(&self) -> Self {
19879 Self(self.0 + 1)
19880 }
19881
19882 fn previous_row(&self) -> Self {
19883 Self(self.0.saturating_sub(1))
19884 }
19885
19886 fn minus(&self, other: Self) -> u32 {
19887 self.0 - other.0
19888 }
19889}
19890
19891trait RowRangeExt {
19892 type Row;
19893
19894 fn len(&self) -> usize;
19895
19896 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
19897}
19898
19899impl RowRangeExt for Range<MultiBufferRow> {
19900 type Row = MultiBufferRow;
19901
19902 fn len(&self) -> usize {
19903 (self.end.0 - self.start.0) as usize
19904 }
19905
19906 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
19907 (self.start.0..self.end.0).map(MultiBufferRow)
19908 }
19909}
19910
19911impl RowRangeExt for Range<DisplayRow> {
19912 type Row = DisplayRow;
19913
19914 fn len(&self) -> usize {
19915 (self.end.0 - self.start.0) as usize
19916 }
19917
19918 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
19919 (self.start.0..self.end.0).map(DisplayRow)
19920 }
19921}
19922
19923/// If select range has more than one line, we
19924/// just point the cursor to range.start.
19925fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
19926 if range.start.row == range.end.row {
19927 range
19928 } else {
19929 range.start..range.start
19930 }
19931}
19932pub struct KillRing(ClipboardItem);
19933impl Global for KillRing {}
19934
19935const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
19936
19937enum BreakpointPromptEditAction {
19938 Log,
19939 Condition,
19940 HitCondition,
19941}
19942
19943struct BreakpointPromptEditor {
19944 pub(crate) prompt: Entity<Editor>,
19945 editor: WeakEntity<Editor>,
19946 breakpoint_anchor: Anchor,
19947 breakpoint: Breakpoint,
19948 edit_action: BreakpointPromptEditAction,
19949 block_ids: HashSet<CustomBlockId>,
19950 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
19951 _subscriptions: Vec<Subscription>,
19952}
19953
19954impl BreakpointPromptEditor {
19955 const MAX_LINES: u8 = 4;
19956
19957 fn new(
19958 editor: WeakEntity<Editor>,
19959 breakpoint_anchor: Anchor,
19960 breakpoint: Breakpoint,
19961 edit_action: BreakpointPromptEditAction,
19962 window: &mut Window,
19963 cx: &mut Context<Self>,
19964 ) -> Self {
19965 let base_text = match edit_action {
19966 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
19967 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
19968 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
19969 }
19970 .map(|msg| msg.to_string())
19971 .unwrap_or_default();
19972
19973 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
19974 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19975
19976 let prompt = cx.new(|cx| {
19977 let mut prompt = Editor::new(
19978 EditorMode::AutoHeight {
19979 max_lines: Self::MAX_LINES as usize,
19980 },
19981 buffer,
19982 None,
19983 window,
19984 cx,
19985 );
19986 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
19987 prompt.set_show_cursor_when_unfocused(false, cx);
19988 prompt.set_placeholder_text(
19989 match edit_action {
19990 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
19991 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
19992 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
19993 },
19994 cx,
19995 );
19996
19997 prompt
19998 });
19999
20000 Self {
20001 prompt,
20002 editor,
20003 breakpoint_anchor,
20004 breakpoint,
20005 edit_action,
20006 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20007 block_ids: Default::default(),
20008 _subscriptions: vec![],
20009 }
20010 }
20011
20012 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20013 self.block_ids.extend(block_ids)
20014 }
20015
20016 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20017 if let Some(editor) = self.editor.upgrade() {
20018 let message = self
20019 .prompt
20020 .read(cx)
20021 .buffer
20022 .read(cx)
20023 .as_singleton()
20024 .expect("A multi buffer in breakpoint prompt isn't possible")
20025 .read(cx)
20026 .as_rope()
20027 .to_string();
20028
20029 editor.update(cx, |editor, cx| {
20030 editor.edit_breakpoint_at_anchor(
20031 self.breakpoint_anchor,
20032 self.breakpoint.clone(),
20033 match self.edit_action {
20034 BreakpointPromptEditAction::Log => {
20035 BreakpointEditAction::EditLogMessage(message.into())
20036 }
20037 BreakpointPromptEditAction::Condition => {
20038 BreakpointEditAction::EditCondition(message.into())
20039 }
20040 BreakpointPromptEditAction::HitCondition => {
20041 BreakpointEditAction::EditHitCondition(message.into())
20042 }
20043 },
20044 cx,
20045 );
20046
20047 editor.remove_blocks(self.block_ids.clone(), None, cx);
20048 cx.focus_self(window);
20049 });
20050 }
20051 }
20052
20053 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20054 self.editor
20055 .update(cx, |editor, cx| {
20056 editor.remove_blocks(self.block_ids.clone(), None, cx);
20057 window.focus(&editor.focus_handle);
20058 })
20059 .log_err();
20060 }
20061
20062 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20063 let settings = ThemeSettings::get_global(cx);
20064 let text_style = TextStyle {
20065 color: if self.prompt.read(cx).read_only(cx) {
20066 cx.theme().colors().text_disabled
20067 } else {
20068 cx.theme().colors().text
20069 },
20070 font_family: settings.buffer_font.family.clone(),
20071 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20072 font_size: settings.buffer_font_size(cx).into(),
20073 font_weight: settings.buffer_font.weight,
20074 line_height: relative(settings.buffer_line_height.value()),
20075 ..Default::default()
20076 };
20077 EditorElement::new(
20078 &self.prompt,
20079 EditorStyle {
20080 background: cx.theme().colors().editor_background,
20081 local_player: cx.theme().players().local(),
20082 text: text_style,
20083 ..Default::default()
20084 },
20085 )
20086 }
20087}
20088
20089impl Render for BreakpointPromptEditor {
20090 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20091 let gutter_dimensions = *self.gutter_dimensions.lock();
20092 h_flex()
20093 .key_context("Editor")
20094 .bg(cx.theme().colors().editor_background)
20095 .border_y_1()
20096 .border_color(cx.theme().status().info_border)
20097 .size_full()
20098 .py(window.line_height() / 2.5)
20099 .on_action(cx.listener(Self::confirm))
20100 .on_action(cx.listener(Self::cancel))
20101 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20102 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20103 }
20104}
20105
20106impl Focusable for BreakpointPromptEditor {
20107 fn focus_handle(&self, cx: &App) -> FocusHandle {
20108 self.prompt.focus_handle(cx)
20109 }
20110}
20111
20112fn all_edits_insertions_or_deletions(
20113 edits: &Vec<(Range<Anchor>, String)>,
20114 snapshot: &MultiBufferSnapshot,
20115) -> bool {
20116 let mut all_insertions = true;
20117 let mut all_deletions = true;
20118
20119 for (range, new_text) in edits.iter() {
20120 let range_is_empty = range.to_offset(&snapshot).is_empty();
20121 let text_is_empty = new_text.is_empty();
20122
20123 if range_is_empty != text_is_empty {
20124 if range_is_empty {
20125 all_deletions = false;
20126 } else {
20127 all_insertions = false;
20128 }
20129 } else {
20130 return false;
20131 }
20132
20133 if !all_insertions && !all_deletions {
20134 return false;
20135 }
20136 }
20137 all_insertions || all_deletions
20138}
20139
20140struct MissingEditPredictionKeybindingTooltip;
20141
20142impl Render for MissingEditPredictionKeybindingTooltip {
20143 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20144 ui::tooltip_container(window, cx, |container, _, cx| {
20145 container
20146 .flex_shrink_0()
20147 .max_w_80()
20148 .min_h(rems_from_px(124.))
20149 .justify_between()
20150 .child(
20151 v_flex()
20152 .flex_1()
20153 .text_ui_sm(cx)
20154 .child(Label::new("Conflict with Accept Keybinding"))
20155 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20156 )
20157 .child(
20158 h_flex()
20159 .pb_1()
20160 .gap_1()
20161 .items_end()
20162 .w_full()
20163 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20164 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20165 }))
20166 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20167 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20168 })),
20169 )
20170 })
20171 }
20172}
20173
20174#[derive(Debug, Clone, Copy, PartialEq)]
20175pub struct LineHighlight {
20176 pub background: Background,
20177 pub border: Option<gpui::Hsla>,
20178}
20179
20180impl From<Hsla> for LineHighlight {
20181 fn from(hsla: Hsla) -> Self {
20182 Self {
20183 background: hsla.into(),
20184 border: None,
20185 }
20186 }
20187}
20188
20189impl From<Background> for LineHighlight {
20190 fn from(background: Background) -> Self {
20191 Self {
20192 background,
20193 border: None,
20194 }
20195 }
20196}
20197
20198fn render_diff_hunk_controls(
20199 row: u32,
20200 status: &DiffHunkStatus,
20201 hunk_range: Range<Anchor>,
20202 is_created_file: bool,
20203 line_height: Pixels,
20204 editor: &Entity<Editor>,
20205 _window: &mut Window,
20206 cx: &mut App,
20207) -> AnyElement {
20208 h_flex()
20209 .h(line_height)
20210 .mr_1()
20211 .gap_1()
20212 .px_0p5()
20213 .pb_1()
20214 .border_x_1()
20215 .border_b_1()
20216 .border_color(cx.theme().colors().border_variant)
20217 .rounded_b_lg()
20218 .bg(cx.theme().colors().editor_background)
20219 .gap_1()
20220 .occlude()
20221 .shadow_md()
20222 .child(if status.has_secondary_hunk() {
20223 Button::new(("stage", row as u64), "Stage")
20224 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20225 .tooltip({
20226 let focus_handle = editor.focus_handle(cx);
20227 move |window, cx| {
20228 Tooltip::for_action_in(
20229 "Stage Hunk",
20230 &::git::ToggleStaged,
20231 &focus_handle,
20232 window,
20233 cx,
20234 )
20235 }
20236 })
20237 .on_click({
20238 let editor = editor.clone();
20239 move |_event, _window, cx| {
20240 editor.update(cx, |editor, cx| {
20241 editor.stage_or_unstage_diff_hunks(
20242 true,
20243 vec![hunk_range.start..hunk_range.start],
20244 cx,
20245 );
20246 });
20247 }
20248 })
20249 } else {
20250 Button::new(("unstage", row as u64), "Unstage")
20251 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20252 .tooltip({
20253 let focus_handle = editor.focus_handle(cx);
20254 move |window, cx| {
20255 Tooltip::for_action_in(
20256 "Unstage Hunk",
20257 &::git::ToggleStaged,
20258 &focus_handle,
20259 window,
20260 cx,
20261 )
20262 }
20263 })
20264 .on_click({
20265 let editor = editor.clone();
20266 move |_event, _window, cx| {
20267 editor.update(cx, |editor, cx| {
20268 editor.stage_or_unstage_diff_hunks(
20269 false,
20270 vec![hunk_range.start..hunk_range.start],
20271 cx,
20272 );
20273 });
20274 }
20275 })
20276 })
20277 .child(
20278 Button::new("restore", "Restore")
20279 .tooltip({
20280 let focus_handle = editor.focus_handle(cx);
20281 move |window, cx| {
20282 Tooltip::for_action_in(
20283 "Restore Hunk",
20284 &::git::Restore,
20285 &focus_handle,
20286 window,
20287 cx,
20288 )
20289 }
20290 })
20291 .on_click({
20292 let editor = editor.clone();
20293 move |_event, window, cx| {
20294 editor.update(cx, |editor, cx| {
20295 let snapshot = editor.snapshot(window, cx);
20296 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20297 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20298 });
20299 }
20300 })
20301 .disabled(is_created_file),
20302 )
20303 .when(
20304 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20305 |el| {
20306 el.child(
20307 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20308 .shape(IconButtonShape::Square)
20309 .icon_size(IconSize::Small)
20310 // .disabled(!has_multiple_hunks)
20311 .tooltip({
20312 let focus_handle = editor.focus_handle(cx);
20313 move |window, cx| {
20314 Tooltip::for_action_in(
20315 "Next Hunk",
20316 &GoToHunk,
20317 &focus_handle,
20318 window,
20319 cx,
20320 )
20321 }
20322 })
20323 .on_click({
20324 let editor = editor.clone();
20325 move |_event, window, cx| {
20326 editor.update(cx, |editor, cx| {
20327 let snapshot = editor.snapshot(window, cx);
20328 let position =
20329 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20330 editor.go_to_hunk_before_or_after_position(
20331 &snapshot,
20332 position,
20333 Direction::Next,
20334 window,
20335 cx,
20336 );
20337 editor.expand_selected_diff_hunks(cx);
20338 });
20339 }
20340 }),
20341 )
20342 .child(
20343 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20344 .shape(IconButtonShape::Square)
20345 .icon_size(IconSize::Small)
20346 // .disabled(!has_multiple_hunks)
20347 .tooltip({
20348 let focus_handle = editor.focus_handle(cx);
20349 move |window, cx| {
20350 Tooltip::for_action_in(
20351 "Previous Hunk",
20352 &GoToPreviousHunk,
20353 &focus_handle,
20354 window,
20355 cx,
20356 )
20357 }
20358 })
20359 .on_click({
20360 let editor = editor.clone();
20361 move |_event, window, cx| {
20362 editor.update(cx, |editor, cx| {
20363 let snapshot = editor.snapshot(window, cx);
20364 let point =
20365 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20366 editor.go_to_hunk_before_or_after_position(
20367 &snapshot,
20368 point,
20369 Direction::Prev,
20370 window,
20371 cx,
20372 );
20373 editor.expand_selected_diff_hunks(cx);
20374 });
20375 }
20376 }),
20377 )
20378 },
20379 )
20380 .into_any_element()
20381}