1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::ReplicaId;
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{Debugger, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::Restore;
82use code_context_menus::{
83 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
84 CompletionsMenu, ContextMenuOrigin,
85};
86use git::blame::{GitBlame, GlobalBlameRenderer};
87use gpui::{
88 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
89 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
90 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
91 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
92 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
93 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
94 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
95 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
96};
97use highlight_matching_bracket::refresh_matching_bracket_highlights;
98use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
99pub use hover_popover::hover_markdown_style;
100use hover_popover::{HoverState, hide_hover};
101use indent_guides::ActiveIndentGuidesState;
102use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
103pub use inline_completion::Direction;
104use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
105pub use items::MAX_TAB_TITLE_LEN;
106use itertools::Itertools;
107use language::{
108 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
109 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
110 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
111 TransactionId, TreeSitterOptions, WordsQuery,
112 language_settings::{
113 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
114 all_language_settings, language_settings,
115 },
116 point_from_lsp, text_diff_with_options,
117};
118use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
119use linked_editing_ranges::refresh_linked_ranges;
120use mouse_context_menu::MouseContextMenu;
121use persistence::DB;
122use project::{
123 ProjectPath,
124 debugger::breakpoint_store::{
125 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
126 },
127};
128
129pub use git::blame::BlameRenderer;
130pub use proposed_changes_editor::{
131 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
132};
133use smallvec::smallvec;
134use std::{cell::OnceCell, iter::Peekable};
135use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
136
137pub use lsp::CompletionContext;
138use lsp::{
139 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
140 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
141};
142
143use language::BufferSnapshot;
144pub use lsp_ext::lsp_tasks;
145use movement::TextLayoutDetails;
146pub use multi_buffer::{
147 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
148 RowInfo, ToOffset, ToPoint,
149};
150use multi_buffer::{
151 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
152 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
153};
154use parking_lot::Mutex;
155use project::{
156 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
157 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
158 TaskSourceKind,
159 debugger::breakpoint_store::Breakpoint,
160 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
161 project_settings::{GitGutterSetting, ProjectSettings},
162};
163use rand::prelude::*;
164use rpc::{ErrorExt, proto::*};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
166use selections_collection::{
167 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
168};
169use serde::{Deserialize, Serialize};
170use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
171use smallvec::SmallVec;
172use snippet::Snippet;
173use std::sync::Arc;
174use std::{
175 any::TypeId,
176 borrow::Cow,
177 cell::RefCell,
178 cmp::{self, Ordering, Reverse},
179 mem,
180 num::NonZeroU32,
181 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 time::{Duration, Instant},
185};
186pub use sum_tree::Bias;
187use sum_tree::TreeMap;
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Key, Tooltip, h_flex, prelude::*,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemHandle, PreviewTabsSettings},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::hover_links::{find_url, find_url_from_range};
208use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
209
210pub const FILE_HEADER_HEIGHT: u32 = 2;
211pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
212pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
213const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
214const MAX_LINE_LEN: usize = 1024;
215const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
216const MAX_SELECTION_HISTORY_LEN: usize = 1024;
217pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
218#[doc(hidden)]
219pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
220const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
221
222pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
223pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
224pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
225
226pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
227pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
228pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
229
230pub type RenderDiffHunkControlsFn = Arc<
231 dyn Fn(
232 u32,
233 &DiffHunkStatus,
234 Range<Anchor>,
235 bool,
236 Pixels,
237 &Entity<Editor>,
238 &mut Window,
239 &mut App,
240 ) -> AnyElement,
241>;
242
243const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
244 alt: true,
245 shift: true,
246 control: false,
247 platform: false,
248 function: false,
249};
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
252pub enum InlayId {
253 InlineCompletion(usize),
254 Hint(usize),
255}
256
257impl InlayId {
258 fn id(&self) -> usize {
259 match self {
260 Self::InlineCompletion(id) => *id,
261 Self::Hint(id) => *id,
262 }
263 }
264}
265
266pub enum DebugCurrentRowHighlight {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270enum SelectedTextHighlight {}
271
272pub enum ConflictsOuter {}
273pub enum ConflictsOurs {}
274pub enum ConflictsTheirs {}
275pub enum ConflictsOursMarker {}
276pub enum ConflictsTheirsMarker {}
277
278#[derive(Debug, Copy, Clone, PartialEq, Eq)]
279pub enum Navigated {
280 Yes,
281 No,
282}
283
284impl Navigated {
285 pub fn from_bool(yes: bool) -> Navigated {
286 if yes { Navigated::Yes } else { Navigated::No }
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Eq)]
291enum DisplayDiffHunk {
292 Folded {
293 display_row: DisplayRow,
294 },
295 Unfolded {
296 is_created_file: bool,
297 diff_base_byte_range: Range<usize>,
298 display_row_range: Range<DisplayRow>,
299 multi_buffer_range: Range<Anchor>,
300 status: DiffHunkStatus,
301 },
302}
303
304pub enum HideMouseCursorOrigin {
305 TypingAction,
306 MovementAction,
307}
308
309pub fn init_settings(cx: &mut App) {
310 EditorSettings::register(cx);
311}
312
313pub fn init(cx: &mut App) {
314 init_settings(cx);
315
316 cx.set_global(GlobalBlameRenderer(Arc::new(())));
317
318 workspace::register_project_item::<Editor>(cx);
319 workspace::FollowableViewRegistry::register::<Editor>(cx);
320 workspace::register_serializable_item::<Editor>(cx);
321
322 cx.observe_new(
323 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
324 workspace.register_action(Editor::new_file);
325 workspace.register_action(Editor::new_file_vertical);
326 workspace.register_action(Editor::new_file_horizontal);
327 workspace.register_action(Editor::cancel_language_server_work);
328 },
329 )
330 .detach();
331
332 cx.on_action(move |_: &workspace::NewFile, cx| {
333 let app_state = workspace::AppState::global(cx);
334 if let Some(app_state) = app_state.upgrade() {
335 workspace::open_new(
336 Default::default(),
337 app_state,
338 cx,
339 |workspace, window, cx| {
340 Editor::new_file(workspace, &Default::default(), window, cx)
341 },
342 )
343 .detach();
344 }
345 });
346 cx.on_action(move |_: &workspace::NewWindow, cx| {
347 let app_state = workspace::AppState::global(cx);
348 if let Some(app_state) = app_state.upgrade() {
349 workspace::open_new(
350 Default::default(),
351 app_state,
352 cx,
353 |workspace, window, cx| {
354 cx.activate(true);
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 });
361}
362
363pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
364 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
365}
366
367pub trait DiagnosticRenderer {
368 fn render_group(
369 &self,
370 diagnostic_group: Vec<DiagnosticEntry<Point>>,
371 buffer_id: BufferId,
372 snapshot: EditorSnapshot,
373 editor: WeakEntity<Editor>,
374 cx: &mut App,
375 ) -> Vec<BlockProperties<Anchor>>;
376}
377
378pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
379
380impl gpui::Global for GlobalDiagnosticRenderer {}
381pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
382 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
383}
384
385pub struct SearchWithinRange;
386
387trait InvalidationRegion {
388 fn ranges(&self) -> &[Range<Anchor>];
389}
390
391#[derive(Clone, Debug, PartialEq)]
392pub enum SelectPhase {
393 Begin {
394 position: DisplayPoint,
395 add: bool,
396 click_count: usize,
397 },
398 BeginColumnar {
399 position: DisplayPoint,
400 reset: bool,
401 goal_column: u32,
402 },
403 Extend {
404 position: DisplayPoint,
405 click_count: usize,
406 },
407 Update {
408 position: DisplayPoint,
409 goal_column: u32,
410 scroll_delta: gpui::Point<f32>,
411 },
412 End,
413}
414
415#[derive(Clone, Debug)]
416pub enum SelectMode {
417 Character,
418 Word(Range<Anchor>),
419 Line(Range<Anchor>),
420 All,
421}
422
423#[derive(Copy, Clone, PartialEq, Eq, Debug)]
424pub enum EditorMode {
425 SingleLine {
426 auto_width: bool,
427 },
428 AutoHeight {
429 max_lines: usize,
430 },
431 Full {
432 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
433 scale_ui_elements_with_buffer_font_size: bool,
434 /// When set to `true`, the editor will render a background for the active line.
435 show_active_line_background: bool,
436 /// When set to `true`, the editor's height will be determined by its content.
437 sized_by_content: bool,
438 },
439}
440
441impl EditorMode {
442 pub fn full() -> Self {
443 Self::Full {
444 scale_ui_elements_with_buffer_font_size: true,
445 show_active_line_background: true,
446 sized_by_content: false,
447 }
448 }
449
450 pub fn is_full(&self) -> bool {
451 matches!(self, Self::Full { .. })
452 }
453}
454
455#[derive(Copy, Clone, Debug)]
456pub enum SoftWrap {
457 /// Prefer not to wrap at all.
458 ///
459 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
460 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
461 GitDiff,
462 /// Prefer a single line generally, unless an overly long line is encountered.
463 None,
464 /// Soft wrap lines that exceed the editor width.
465 EditorWidth,
466 /// Soft wrap lines at the preferred line length.
467 Column(u32),
468 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
469 Bounded(u32),
470}
471
472#[derive(Clone)]
473pub struct EditorStyle {
474 pub background: Hsla,
475 pub local_player: PlayerColor,
476 pub text: TextStyle,
477 pub scrollbar_width: Pixels,
478 pub syntax: Arc<SyntaxTheme>,
479 pub status: StatusColors,
480 pub inlay_hints_style: HighlightStyle,
481 pub inline_completion_styles: InlineCompletionStyles,
482 pub unnecessary_code_fade: f32,
483}
484
485impl Default for EditorStyle {
486 fn default() -> Self {
487 Self {
488 background: Hsla::default(),
489 local_player: PlayerColor::default(),
490 text: TextStyle::default(),
491 scrollbar_width: Pixels::default(),
492 syntax: Default::default(),
493 // HACK: Status colors don't have a real default.
494 // We should look into removing the status colors from the editor
495 // style and retrieve them directly from the theme.
496 status: StatusColors::dark(),
497 inlay_hints_style: HighlightStyle::default(),
498 inline_completion_styles: InlineCompletionStyles {
499 insertion: HighlightStyle::default(),
500 whitespace: HighlightStyle::default(),
501 },
502 unnecessary_code_fade: Default::default(),
503 }
504 }
505}
506
507pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
508 let show_background = language_settings::language_settings(None, None, cx)
509 .inlay_hints
510 .show_background;
511
512 HighlightStyle {
513 color: Some(cx.theme().status().hint),
514 background_color: show_background.then(|| cx.theme().status().hint_background),
515 ..HighlightStyle::default()
516 }
517}
518
519pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
520 InlineCompletionStyles {
521 insertion: HighlightStyle {
522 color: Some(cx.theme().status().predictive),
523 ..HighlightStyle::default()
524 },
525 whitespace: HighlightStyle {
526 background_color: Some(cx.theme().status().created_background),
527 ..HighlightStyle::default()
528 },
529 }
530}
531
532type CompletionId = usize;
533
534pub(crate) enum EditDisplayMode {
535 TabAccept,
536 DiffPopover,
537 Inline,
538}
539
540enum InlineCompletion {
541 Edit {
542 edits: Vec<(Range<Anchor>, String)>,
543 edit_preview: Option<EditPreview>,
544 display_mode: EditDisplayMode,
545 snapshot: BufferSnapshot,
546 },
547 Move {
548 target: Anchor,
549 snapshot: BufferSnapshot,
550 },
551}
552
553struct InlineCompletionState {
554 inlay_ids: Vec<InlayId>,
555 completion: InlineCompletion,
556 completion_id: Option<SharedString>,
557 invalidation_range: Range<Anchor>,
558}
559
560enum EditPredictionSettings {
561 Disabled,
562 Enabled {
563 show_in_menu: bool,
564 preview_requires_modifier: bool,
565 },
566}
567
568enum InlineCompletionHighlight {}
569
570#[derive(Debug, Clone)]
571struct InlineDiagnostic {
572 message: SharedString,
573 group_id: usize,
574 is_primary: bool,
575 start: Point,
576 severity: DiagnosticSeverity,
577}
578
579pub enum MenuInlineCompletionsPolicy {
580 Never,
581 ByProvider,
582}
583
584pub enum EditPredictionPreview {
585 /// Modifier is not pressed
586 Inactive { released_too_fast: bool },
587 /// Modifier pressed
588 Active {
589 since: Instant,
590 previous_scroll_position: Option<ScrollAnchor>,
591 },
592}
593
594impl EditPredictionPreview {
595 pub fn released_too_fast(&self) -> bool {
596 match self {
597 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
598 EditPredictionPreview::Active { .. } => false,
599 }
600 }
601
602 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
603 if let EditPredictionPreview::Active {
604 previous_scroll_position,
605 ..
606 } = self
607 {
608 *previous_scroll_position = scroll_position;
609 }
610 }
611}
612
613pub struct ContextMenuOptions {
614 pub min_entries_visible: usize,
615 pub max_entries_visible: usize,
616 pub placement: Option<ContextMenuPlacement>,
617}
618
619#[derive(Debug, Clone, PartialEq, Eq)]
620pub enum ContextMenuPlacement {
621 Above,
622 Below,
623}
624
625#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
626struct EditorActionId(usize);
627
628impl EditorActionId {
629 pub fn post_inc(&mut self) -> Self {
630 let answer = self.0;
631
632 *self = Self(answer + 1);
633
634 Self(answer)
635 }
636}
637
638// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
639// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
640
641type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
642type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
643
644#[derive(Default)]
645struct ScrollbarMarkerState {
646 scrollbar_size: Size<Pixels>,
647 dirty: bool,
648 markers: Arc<[PaintQuad]>,
649 pending_refresh: Option<Task<Result<()>>>,
650}
651
652impl ScrollbarMarkerState {
653 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
654 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
655 }
656}
657
658#[derive(Clone, Debug)]
659struct RunnableTasks {
660 templates: Vec<(TaskSourceKind, TaskTemplate)>,
661 offset: multi_buffer::Anchor,
662 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
663 column: u32,
664 // Values of all named captures, including those starting with '_'
665 extra_variables: HashMap<String, String>,
666 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
667 context_range: Range<BufferOffset>,
668}
669
670impl RunnableTasks {
671 fn resolve<'a>(
672 &'a self,
673 cx: &'a task::TaskContext,
674 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
675 self.templates.iter().filter_map(|(kind, template)| {
676 template
677 .resolve_task(&kind.to_id_base(), cx)
678 .map(|task| (kind.clone(), task))
679 })
680 }
681}
682
683#[derive(Clone)]
684struct ResolvedTasks {
685 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
686 position: Anchor,
687}
688
689#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
690struct BufferOffset(usize);
691
692// Addons allow storing per-editor state in other crates (e.g. Vim)
693pub trait Addon: 'static {
694 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
695
696 fn render_buffer_header_controls(
697 &self,
698 _: &ExcerptInfo,
699 _: &Window,
700 _: &App,
701 ) -> Option<AnyElement> {
702 None
703 }
704
705 fn to_any(&self) -> &dyn std::any::Any;
706
707 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
708 None
709 }
710}
711
712/// A set of caret positions, registered when the editor was edited.
713pub struct ChangeList {
714 changes: Vec<Vec<Anchor>>,
715 /// Currently "selected" change.
716 position: Option<usize>,
717}
718
719impl ChangeList {
720 pub fn new() -> Self {
721 Self {
722 changes: Vec::new(),
723 position: None,
724 }
725 }
726
727 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
728 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
729 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
730 if self.changes.is_empty() {
731 return None;
732 }
733
734 let prev = self.position.unwrap_or(self.changes.len());
735 let next = if direction == Direction::Prev {
736 prev.saturating_sub(count)
737 } else {
738 (prev + count).min(self.changes.len() - 1)
739 };
740 self.position = Some(next);
741 self.changes.get(next).map(|anchors| anchors.as_slice())
742 }
743
744 /// Adds a new change to the list, resetting the change list position.
745 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
746 self.position.take();
747 if pop_state {
748 self.changes.pop();
749 }
750 self.changes.push(new_positions.clone());
751 }
752
753 pub fn last(&self) -> Option<&[Anchor]> {
754 self.changes.last().map(|anchors| anchors.as_slice())
755 }
756}
757
758/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
759///
760/// See the [module level documentation](self) for more information.
761pub struct Editor {
762 focus_handle: FocusHandle,
763 last_focused_descendant: Option<WeakFocusHandle>,
764 /// The text buffer being edited
765 buffer: Entity<MultiBuffer>,
766 /// Map of how text in the buffer should be displayed.
767 /// Handles soft wraps, folds, fake inlay text insertions, etc.
768 pub display_map: Entity<DisplayMap>,
769 pub selections: SelectionsCollection,
770 pub scroll_manager: ScrollManager,
771 /// When inline assist editors are linked, they all render cursors because
772 /// typing enters text into each of them, even the ones that aren't focused.
773 pub(crate) show_cursor_when_unfocused: bool,
774 columnar_selection_tail: Option<Anchor>,
775 add_selections_state: Option<AddSelectionsState>,
776 select_next_state: Option<SelectNextState>,
777 select_prev_state: Option<SelectNextState>,
778 selection_history: SelectionHistory,
779 autoclose_regions: Vec<AutocloseRegion>,
780 snippet_stack: InvalidationStack<SnippetState>,
781 select_syntax_node_history: SelectSyntaxNodeHistory,
782 ime_transaction: Option<TransactionId>,
783 active_diagnostics: ActiveDiagnostic,
784 show_inline_diagnostics: bool,
785 inline_diagnostics_update: Task<()>,
786 inline_diagnostics_enabled: bool,
787 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
788 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
789 hard_wrap: Option<usize>,
790
791 // TODO: make this a access method
792 pub project: Option<Entity<Project>>,
793 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
794 completion_provider: Option<Box<dyn CompletionProvider>>,
795 collaboration_hub: Option<Box<dyn CollaborationHub>>,
796 blink_manager: Entity<BlinkManager>,
797 show_cursor_names: bool,
798 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
799 pub show_local_selections: bool,
800 mode: EditorMode,
801 show_breadcrumbs: bool,
802 show_gutter: bool,
803 show_scrollbars: bool,
804 disable_scrolling: bool,
805 disable_expand_excerpt_buttons: bool,
806 show_line_numbers: Option<bool>,
807 use_relative_line_numbers: Option<bool>,
808 show_git_diff_gutter: Option<bool>,
809 show_code_actions: Option<bool>,
810 show_runnables: Option<bool>,
811 show_breakpoints: Option<bool>,
812 show_wrap_guides: Option<bool>,
813 show_indent_guides: Option<bool>,
814 placeholder_text: Option<Arc<str>>,
815 highlight_order: usize,
816 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
817 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
818 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
819 scrollbar_marker_state: ScrollbarMarkerState,
820 active_indent_guides_state: ActiveIndentGuidesState,
821 nav_history: Option<ItemNavHistory>,
822 context_menu: RefCell<Option<CodeContextMenu>>,
823 context_menu_options: Option<ContextMenuOptions>,
824 mouse_context_menu: Option<MouseContextMenu>,
825 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
826 signature_help_state: SignatureHelpState,
827 auto_signature_help: Option<bool>,
828 find_all_references_task_sources: Vec<Anchor>,
829 next_completion_id: CompletionId,
830 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
831 code_actions_task: Option<Task<Result<()>>>,
832 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
833 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
834 document_highlights_task: Option<Task<()>>,
835 linked_editing_range_task: Option<Task<Option<()>>>,
836 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
837 pending_rename: Option<RenameState>,
838 searchable: bool,
839 cursor_shape: CursorShape,
840 current_line_highlight: Option<CurrentLineHighlight>,
841 collapse_matches: bool,
842 autoindent_mode: Option<AutoindentMode>,
843 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
844 input_enabled: bool,
845 use_modal_editing: bool,
846 read_only: bool,
847 leader_peer_id: Option<PeerId>,
848 remote_id: Option<ViewId>,
849 hover_state: HoverState,
850 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
851 gutter_hovered: bool,
852 hovered_link_state: Option<HoveredLinkState>,
853 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
854 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
855 active_inline_completion: Option<InlineCompletionState>,
856 /// Used to prevent flickering as the user types while the menu is open
857 stale_inline_completion_in_menu: Option<InlineCompletionState>,
858 edit_prediction_settings: EditPredictionSettings,
859 inline_completions_hidden_for_vim_mode: bool,
860 show_inline_completions_override: Option<bool>,
861 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
862 edit_prediction_preview: EditPredictionPreview,
863 edit_prediction_indent_conflict: bool,
864 edit_prediction_requires_modifier_in_indent_conflict: bool,
865 inlay_hint_cache: InlayHintCache,
866 next_inlay_id: usize,
867 _subscriptions: Vec<Subscription>,
868 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
869 gutter_dimensions: GutterDimensions,
870 style: Option<EditorStyle>,
871 text_style_refinement: Option<TextStyleRefinement>,
872 next_editor_action_id: EditorActionId,
873 editor_actions:
874 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
875 use_autoclose: bool,
876 use_auto_surround: bool,
877 auto_replace_emoji_shortcode: bool,
878 jsx_tag_auto_close_enabled_in_any_buffer: bool,
879 show_git_blame_gutter: bool,
880 show_git_blame_inline: bool,
881 show_git_blame_inline_delay_task: Option<Task<()>>,
882 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
883 git_blame_inline_enabled: bool,
884 render_diff_hunk_controls: RenderDiffHunkControlsFn,
885 serialize_dirty_buffers: bool,
886 show_selection_menu: Option<bool>,
887 blame: Option<Entity<GitBlame>>,
888 blame_subscription: Option<Subscription>,
889 custom_context_menu: Option<
890 Box<
891 dyn 'static
892 + Fn(
893 &mut Self,
894 DisplayPoint,
895 &mut Window,
896 &mut Context<Self>,
897 ) -> Option<Entity<ui::ContextMenu>>,
898 >,
899 >,
900 last_bounds: Option<Bounds<Pixels>>,
901 last_position_map: Option<Rc<PositionMap>>,
902 expect_bounds_change: Option<Bounds<Pixels>>,
903 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
904 tasks_update_task: Option<Task<()>>,
905 breakpoint_store: Option<Entity<BreakpointStore>>,
906 /// Allow's a user to create a breakpoint by selecting this indicator
907 /// It should be None while a user is not hovering over the gutter
908 /// Otherwise it represents the point that the breakpoint will be shown
909 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
910 in_project_search: bool,
911 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
912 breadcrumb_header: Option<String>,
913 focused_block: Option<FocusedBlock>,
914 next_scroll_position: NextScrollCursorCenterTopBottom,
915 addons: HashMap<TypeId, Box<dyn Addon>>,
916 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
917 load_diff_task: Option<Shared<Task<()>>>,
918 selection_mark_mode: bool,
919 toggle_fold_multiple_buffers: Task<()>,
920 _scroll_cursor_center_top_bottom_task: Task<()>,
921 serialize_selections: Task<()>,
922 serialize_folds: Task<()>,
923 mouse_cursor_hidden: bool,
924 hide_mouse_mode: HideMouseMode,
925 pub change_list: ChangeList,
926}
927
928#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
929enum NextScrollCursorCenterTopBottom {
930 #[default]
931 Center,
932 Top,
933 Bottom,
934}
935
936impl NextScrollCursorCenterTopBottom {
937 fn next(&self) -> Self {
938 match self {
939 Self::Center => Self::Top,
940 Self::Top => Self::Bottom,
941 Self::Bottom => Self::Center,
942 }
943 }
944}
945
946#[derive(Clone)]
947pub struct EditorSnapshot {
948 pub mode: EditorMode,
949 show_gutter: bool,
950 show_line_numbers: Option<bool>,
951 show_git_diff_gutter: Option<bool>,
952 show_code_actions: Option<bool>,
953 show_runnables: Option<bool>,
954 show_breakpoints: Option<bool>,
955 git_blame_gutter_max_author_length: Option<usize>,
956 pub display_snapshot: DisplaySnapshot,
957 pub placeholder_text: Option<Arc<str>>,
958 is_focused: bool,
959 scroll_anchor: ScrollAnchor,
960 ongoing_scroll: OngoingScroll,
961 current_line_highlight: CurrentLineHighlight,
962 gutter_hovered: bool,
963}
964
965#[derive(Default, Debug, Clone, Copy)]
966pub struct GutterDimensions {
967 pub left_padding: Pixels,
968 pub right_padding: Pixels,
969 pub width: Pixels,
970 pub margin: Pixels,
971 pub git_blame_entries_width: Option<Pixels>,
972}
973
974impl GutterDimensions {
975 /// The full width of the space taken up by the gutter.
976 pub fn full_width(&self) -> Pixels {
977 self.margin + self.width
978 }
979
980 /// The width of the space reserved for the fold indicators,
981 /// use alongside 'justify_end' and `gutter_width` to
982 /// right align content with the line numbers
983 pub fn fold_area_width(&self) -> Pixels {
984 self.margin + self.right_padding
985 }
986}
987
988#[derive(Debug)]
989pub struct RemoteSelection {
990 pub replica_id: ReplicaId,
991 pub selection: Selection<Anchor>,
992 pub cursor_shape: CursorShape,
993 pub peer_id: PeerId,
994 pub line_mode: bool,
995 pub participant_index: Option<ParticipantIndex>,
996 pub user_name: Option<SharedString>,
997}
998
999#[derive(Clone, Debug)]
1000struct SelectionHistoryEntry {
1001 selections: Arc<[Selection<Anchor>]>,
1002 select_next_state: Option<SelectNextState>,
1003 select_prev_state: Option<SelectNextState>,
1004 add_selections_state: Option<AddSelectionsState>,
1005}
1006
1007enum SelectionHistoryMode {
1008 Normal,
1009 Undoing,
1010 Redoing,
1011}
1012
1013#[derive(Clone, PartialEq, Eq, Hash)]
1014struct HoveredCursor {
1015 replica_id: u16,
1016 selection_id: usize,
1017}
1018
1019impl Default for SelectionHistoryMode {
1020 fn default() -> Self {
1021 Self::Normal
1022 }
1023}
1024
1025#[derive(Default)]
1026struct SelectionHistory {
1027 #[allow(clippy::type_complexity)]
1028 selections_by_transaction:
1029 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1030 mode: SelectionHistoryMode,
1031 undo_stack: VecDeque<SelectionHistoryEntry>,
1032 redo_stack: VecDeque<SelectionHistoryEntry>,
1033}
1034
1035impl SelectionHistory {
1036 fn insert_transaction(
1037 &mut self,
1038 transaction_id: TransactionId,
1039 selections: Arc<[Selection<Anchor>]>,
1040 ) {
1041 self.selections_by_transaction
1042 .insert(transaction_id, (selections, None));
1043 }
1044
1045 #[allow(clippy::type_complexity)]
1046 fn transaction(
1047 &self,
1048 transaction_id: TransactionId,
1049 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1050 self.selections_by_transaction.get(&transaction_id)
1051 }
1052
1053 #[allow(clippy::type_complexity)]
1054 fn transaction_mut(
1055 &mut self,
1056 transaction_id: TransactionId,
1057 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1058 self.selections_by_transaction.get_mut(&transaction_id)
1059 }
1060
1061 fn push(&mut self, entry: SelectionHistoryEntry) {
1062 if !entry.selections.is_empty() {
1063 match self.mode {
1064 SelectionHistoryMode::Normal => {
1065 self.push_undo(entry);
1066 self.redo_stack.clear();
1067 }
1068 SelectionHistoryMode::Undoing => self.push_redo(entry),
1069 SelectionHistoryMode::Redoing => self.push_undo(entry),
1070 }
1071 }
1072 }
1073
1074 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1075 if self
1076 .undo_stack
1077 .back()
1078 .map_or(true, |e| e.selections != entry.selections)
1079 {
1080 self.undo_stack.push_back(entry);
1081 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1082 self.undo_stack.pop_front();
1083 }
1084 }
1085 }
1086
1087 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1088 if self
1089 .redo_stack
1090 .back()
1091 .map_or(true, |e| e.selections != entry.selections)
1092 {
1093 self.redo_stack.push_back(entry);
1094 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1095 self.redo_stack.pop_front();
1096 }
1097 }
1098 }
1099}
1100
1101#[derive(Clone, Copy)]
1102pub struct RowHighlightOptions {
1103 pub autoscroll: bool,
1104 pub include_gutter: bool,
1105}
1106
1107impl Default for RowHighlightOptions {
1108 fn default() -> Self {
1109 Self {
1110 autoscroll: Default::default(),
1111 include_gutter: true,
1112 }
1113 }
1114}
1115
1116struct RowHighlight {
1117 index: usize,
1118 range: Range<Anchor>,
1119 color: Hsla,
1120 options: RowHighlightOptions,
1121 type_id: TypeId,
1122}
1123
1124#[derive(Clone, Debug)]
1125struct AddSelectionsState {
1126 above: bool,
1127 stack: Vec<usize>,
1128}
1129
1130#[derive(Clone)]
1131struct SelectNextState {
1132 query: AhoCorasick,
1133 wordwise: bool,
1134 done: bool,
1135}
1136
1137impl std::fmt::Debug for SelectNextState {
1138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1139 f.debug_struct(std::any::type_name::<Self>())
1140 .field("wordwise", &self.wordwise)
1141 .field("done", &self.done)
1142 .finish()
1143 }
1144}
1145
1146#[derive(Debug)]
1147struct AutocloseRegion {
1148 selection_id: usize,
1149 range: Range<Anchor>,
1150 pair: BracketPair,
1151}
1152
1153#[derive(Debug)]
1154struct SnippetState {
1155 ranges: Vec<Vec<Range<Anchor>>>,
1156 active_index: usize,
1157 choices: Vec<Option<Vec<String>>>,
1158}
1159
1160#[doc(hidden)]
1161pub struct RenameState {
1162 pub range: Range<Anchor>,
1163 pub old_name: Arc<str>,
1164 pub editor: Entity<Editor>,
1165 block_id: CustomBlockId,
1166}
1167
1168struct InvalidationStack<T>(Vec<T>);
1169
1170struct RegisteredInlineCompletionProvider {
1171 provider: Arc<dyn InlineCompletionProviderHandle>,
1172 _subscription: Subscription,
1173}
1174
1175#[derive(Debug, PartialEq, Eq)]
1176pub struct ActiveDiagnosticGroup {
1177 pub active_range: Range<Anchor>,
1178 pub active_message: String,
1179 pub group_id: usize,
1180 pub blocks: HashSet<CustomBlockId>,
1181}
1182
1183#[derive(Debug, PartialEq, Eq)]
1184#[allow(clippy::large_enum_variant)]
1185pub(crate) enum ActiveDiagnostic {
1186 None,
1187 All,
1188 Group(ActiveDiagnosticGroup),
1189}
1190
1191#[derive(Serialize, Deserialize, Clone, Debug)]
1192pub struct ClipboardSelection {
1193 /// The number of bytes in this selection.
1194 pub len: usize,
1195 /// Whether this was a full-line selection.
1196 pub is_entire_line: bool,
1197 /// The indentation of the first line when this content was originally copied.
1198 pub first_line_indent: u32,
1199}
1200
1201// selections, scroll behavior, was newest selection reversed
1202type SelectSyntaxNodeHistoryState = (
1203 Box<[Selection<usize>]>,
1204 SelectSyntaxNodeScrollBehavior,
1205 bool,
1206);
1207
1208#[derive(Default)]
1209struct SelectSyntaxNodeHistory {
1210 stack: Vec<SelectSyntaxNodeHistoryState>,
1211 // disable temporarily to allow changing selections without losing the stack
1212 pub disable_clearing: bool,
1213}
1214
1215impl SelectSyntaxNodeHistory {
1216 pub fn try_clear(&mut self) {
1217 if !self.disable_clearing {
1218 self.stack.clear();
1219 }
1220 }
1221
1222 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1223 self.stack.push(selection);
1224 }
1225
1226 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1227 self.stack.pop()
1228 }
1229}
1230
1231enum SelectSyntaxNodeScrollBehavior {
1232 CursorTop,
1233 FitSelection,
1234 CursorBottom,
1235}
1236
1237#[derive(Debug)]
1238pub(crate) struct NavigationData {
1239 cursor_anchor: Anchor,
1240 cursor_position: Point,
1241 scroll_anchor: ScrollAnchor,
1242 scroll_top_row: u32,
1243}
1244
1245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1246pub enum GotoDefinitionKind {
1247 Symbol,
1248 Declaration,
1249 Type,
1250 Implementation,
1251}
1252
1253#[derive(Debug, Clone)]
1254enum InlayHintRefreshReason {
1255 ModifiersChanged(bool),
1256 Toggle(bool),
1257 SettingsChange(InlayHintSettings),
1258 NewLinesShown,
1259 BufferEdited(HashSet<Arc<Language>>),
1260 RefreshRequested,
1261 ExcerptsRemoved(Vec<ExcerptId>),
1262}
1263
1264impl InlayHintRefreshReason {
1265 fn description(&self) -> &'static str {
1266 match self {
1267 Self::ModifiersChanged(_) => "modifiers changed",
1268 Self::Toggle(_) => "toggle",
1269 Self::SettingsChange(_) => "settings change",
1270 Self::NewLinesShown => "new lines shown",
1271 Self::BufferEdited(_) => "buffer edited",
1272 Self::RefreshRequested => "refresh requested",
1273 Self::ExcerptsRemoved(_) => "excerpts removed",
1274 }
1275 }
1276}
1277
1278pub enum FormatTarget {
1279 Buffers,
1280 Ranges(Vec<Range<MultiBufferPoint>>),
1281}
1282
1283pub(crate) struct FocusedBlock {
1284 id: BlockId,
1285 focus_handle: WeakFocusHandle,
1286}
1287
1288#[derive(Clone)]
1289enum JumpData {
1290 MultiBufferRow {
1291 row: MultiBufferRow,
1292 line_offset_from_top: u32,
1293 },
1294 MultiBufferPoint {
1295 excerpt_id: ExcerptId,
1296 position: Point,
1297 anchor: text::Anchor,
1298 line_offset_from_top: u32,
1299 },
1300}
1301
1302pub enum MultibufferSelectionMode {
1303 First,
1304 All,
1305}
1306
1307#[derive(Clone, Copy, Debug, Default)]
1308pub struct RewrapOptions {
1309 pub override_language_settings: bool,
1310 pub preserve_existing_whitespace: bool,
1311}
1312
1313impl Editor {
1314 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1315 let buffer = cx.new(|cx| Buffer::local("", cx));
1316 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1317 Self::new(
1318 EditorMode::SingleLine { auto_width: false },
1319 buffer,
1320 None,
1321 window,
1322 cx,
1323 )
1324 }
1325
1326 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1327 let buffer = cx.new(|cx| Buffer::local("", cx));
1328 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1329 Self::new(EditorMode::full(), buffer, None, window, cx)
1330 }
1331
1332 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1333 let buffer = cx.new(|cx| Buffer::local("", cx));
1334 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1335 Self::new(
1336 EditorMode::SingleLine { auto_width: true },
1337 buffer,
1338 None,
1339 window,
1340 cx,
1341 )
1342 }
1343
1344 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1345 let buffer = cx.new(|cx| Buffer::local("", cx));
1346 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1347 Self::new(
1348 EditorMode::AutoHeight { max_lines },
1349 buffer,
1350 None,
1351 window,
1352 cx,
1353 )
1354 }
1355
1356 pub fn for_buffer(
1357 buffer: Entity<Buffer>,
1358 project: Option<Entity<Project>>,
1359 window: &mut Window,
1360 cx: &mut Context<Self>,
1361 ) -> Self {
1362 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1363 Self::new(EditorMode::full(), buffer, project, window, cx)
1364 }
1365
1366 pub fn for_multibuffer(
1367 buffer: Entity<MultiBuffer>,
1368 project: Option<Entity<Project>>,
1369 window: &mut Window,
1370 cx: &mut Context<Self>,
1371 ) -> Self {
1372 Self::new(EditorMode::full(), buffer, project, window, cx)
1373 }
1374
1375 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1376 let mut clone = Self::new(
1377 self.mode,
1378 self.buffer.clone(),
1379 self.project.clone(),
1380 window,
1381 cx,
1382 );
1383 self.display_map.update(cx, |display_map, cx| {
1384 let snapshot = display_map.snapshot(cx);
1385 clone.display_map.update(cx, |display_map, cx| {
1386 display_map.set_state(&snapshot, cx);
1387 });
1388 });
1389 clone.folds_did_change(cx);
1390 clone.selections.clone_state(&self.selections);
1391 clone.scroll_manager.clone_state(&self.scroll_manager);
1392 clone.searchable = self.searchable;
1393 clone.read_only = self.read_only;
1394 clone
1395 }
1396
1397 pub fn new(
1398 mode: EditorMode,
1399 buffer: Entity<MultiBuffer>,
1400 project: Option<Entity<Project>>,
1401 window: &mut Window,
1402 cx: &mut Context<Self>,
1403 ) -> Self {
1404 let style = window.text_style();
1405 let font_size = style.font_size.to_pixels(window.rem_size());
1406 let editor = cx.entity().downgrade();
1407 let fold_placeholder = FoldPlaceholder {
1408 constrain_width: true,
1409 render: Arc::new(move |fold_id, fold_range, cx| {
1410 let editor = editor.clone();
1411 div()
1412 .id(fold_id)
1413 .bg(cx.theme().colors().ghost_element_background)
1414 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1415 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1416 .rounded_xs()
1417 .size_full()
1418 .cursor_pointer()
1419 .child("⋯")
1420 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1421 .on_click(move |_, _window, cx| {
1422 editor
1423 .update(cx, |editor, cx| {
1424 editor.unfold_ranges(
1425 &[fold_range.start..fold_range.end],
1426 true,
1427 false,
1428 cx,
1429 );
1430 cx.stop_propagation();
1431 })
1432 .ok();
1433 })
1434 .into_any()
1435 }),
1436 merge_adjacent: true,
1437 ..Default::default()
1438 };
1439 let display_map = cx.new(|cx| {
1440 DisplayMap::new(
1441 buffer.clone(),
1442 style.font(),
1443 font_size,
1444 None,
1445 FILE_HEADER_HEIGHT,
1446 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1447 fold_placeholder,
1448 cx,
1449 )
1450 });
1451
1452 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1453
1454 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1455
1456 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1457 .then(|| language_settings::SoftWrap::None);
1458
1459 let mut project_subscriptions = Vec::new();
1460 if mode.is_full() {
1461 if let Some(project) = project.as_ref() {
1462 project_subscriptions.push(cx.subscribe_in(
1463 project,
1464 window,
1465 |editor, _, event, window, cx| match event {
1466 project::Event::RefreshCodeLens => {
1467 // we always query lens with actions, without storing them, always refreshing them
1468 }
1469 project::Event::RefreshInlayHints => {
1470 editor
1471 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1472 }
1473 project::Event::SnippetEdit(id, snippet_edits) => {
1474 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1475 let focus_handle = editor.focus_handle(cx);
1476 if focus_handle.is_focused(window) {
1477 let snapshot = buffer.read(cx).snapshot();
1478 for (range, snippet) in snippet_edits {
1479 let editor_range =
1480 language::range_from_lsp(*range).to_offset(&snapshot);
1481 editor
1482 .insert_snippet(
1483 &[editor_range],
1484 snippet.clone(),
1485 window,
1486 cx,
1487 )
1488 .ok();
1489 }
1490 }
1491 }
1492 }
1493 _ => {}
1494 },
1495 ));
1496 if let Some(task_inventory) = project
1497 .read(cx)
1498 .task_store()
1499 .read(cx)
1500 .task_inventory()
1501 .cloned()
1502 {
1503 project_subscriptions.push(cx.observe_in(
1504 &task_inventory,
1505 window,
1506 |editor, _, window, cx| {
1507 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1508 },
1509 ));
1510 };
1511
1512 project_subscriptions.push(cx.subscribe_in(
1513 &project.read(cx).breakpoint_store(),
1514 window,
1515 |editor, _, event, window, cx| match event {
1516 BreakpointStoreEvent::ActiveDebugLineChanged => {
1517 if editor.go_to_active_debug_line(window, cx) {
1518 cx.stop_propagation();
1519 }
1520 }
1521 _ => {}
1522 },
1523 ));
1524 }
1525 }
1526
1527 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1528
1529 let inlay_hint_settings =
1530 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1531 let focus_handle = cx.focus_handle();
1532 cx.on_focus(&focus_handle, window, Self::handle_focus)
1533 .detach();
1534 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1535 .detach();
1536 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1537 .detach();
1538 cx.on_blur(&focus_handle, window, Self::handle_blur)
1539 .detach();
1540
1541 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1542 Some(false)
1543 } else {
1544 None
1545 };
1546
1547 let breakpoint_store = match (mode, project.as_ref()) {
1548 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1549 _ => None,
1550 };
1551
1552 let mut code_action_providers = Vec::new();
1553 let mut load_uncommitted_diff = None;
1554 if let Some(project) = project.clone() {
1555 load_uncommitted_diff = Some(
1556 get_uncommitted_diff_for_buffer(
1557 &project,
1558 buffer.read(cx).all_buffers(),
1559 buffer.clone(),
1560 cx,
1561 )
1562 .shared(),
1563 );
1564 code_action_providers.push(Rc::new(project) as Rc<_>);
1565 }
1566
1567 let mut this = Self {
1568 focus_handle,
1569 show_cursor_when_unfocused: false,
1570 last_focused_descendant: None,
1571 buffer: buffer.clone(),
1572 display_map: display_map.clone(),
1573 selections,
1574 scroll_manager: ScrollManager::new(cx),
1575 columnar_selection_tail: None,
1576 add_selections_state: None,
1577 select_next_state: None,
1578 select_prev_state: None,
1579 selection_history: Default::default(),
1580 autoclose_regions: Default::default(),
1581 snippet_stack: Default::default(),
1582 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1583 ime_transaction: Default::default(),
1584 active_diagnostics: ActiveDiagnostic::None,
1585 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1586 inline_diagnostics_update: Task::ready(()),
1587 inline_diagnostics: Vec::new(),
1588 soft_wrap_mode_override,
1589 hard_wrap: None,
1590 completion_provider: project.clone().map(|project| Box::new(project) as _),
1591 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1592 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1593 project,
1594 blink_manager: blink_manager.clone(),
1595 show_local_selections: true,
1596 show_scrollbars: true,
1597 disable_scrolling: true,
1598 mode,
1599 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1600 show_gutter: mode.is_full(),
1601 show_line_numbers: None,
1602 use_relative_line_numbers: None,
1603 disable_expand_excerpt_buttons: false,
1604 show_git_diff_gutter: None,
1605 show_code_actions: None,
1606 show_runnables: None,
1607 show_breakpoints: None,
1608 show_wrap_guides: None,
1609 show_indent_guides,
1610 placeholder_text: None,
1611 highlight_order: 0,
1612 highlighted_rows: HashMap::default(),
1613 background_highlights: Default::default(),
1614 gutter_highlights: TreeMap::default(),
1615 scrollbar_marker_state: ScrollbarMarkerState::default(),
1616 active_indent_guides_state: ActiveIndentGuidesState::default(),
1617 nav_history: None,
1618 context_menu: RefCell::new(None),
1619 context_menu_options: None,
1620 mouse_context_menu: None,
1621 completion_tasks: Default::default(),
1622 signature_help_state: SignatureHelpState::default(),
1623 auto_signature_help: None,
1624 find_all_references_task_sources: Vec::new(),
1625 next_completion_id: 0,
1626 next_inlay_id: 0,
1627 code_action_providers,
1628 available_code_actions: Default::default(),
1629 code_actions_task: Default::default(),
1630 quick_selection_highlight_task: Default::default(),
1631 debounced_selection_highlight_task: Default::default(),
1632 document_highlights_task: Default::default(),
1633 linked_editing_range_task: Default::default(),
1634 pending_rename: Default::default(),
1635 searchable: true,
1636 cursor_shape: EditorSettings::get_global(cx)
1637 .cursor_shape
1638 .unwrap_or_default(),
1639 current_line_highlight: None,
1640 autoindent_mode: Some(AutoindentMode::EachLine),
1641 collapse_matches: false,
1642 workspace: None,
1643 input_enabled: true,
1644 use_modal_editing: mode.is_full(),
1645 read_only: false,
1646 use_autoclose: true,
1647 use_auto_surround: true,
1648 auto_replace_emoji_shortcode: false,
1649 jsx_tag_auto_close_enabled_in_any_buffer: false,
1650 leader_peer_id: None,
1651 remote_id: None,
1652 hover_state: Default::default(),
1653 pending_mouse_down: None,
1654 hovered_link_state: Default::default(),
1655 edit_prediction_provider: None,
1656 active_inline_completion: None,
1657 stale_inline_completion_in_menu: None,
1658 edit_prediction_preview: EditPredictionPreview::Inactive {
1659 released_too_fast: false,
1660 },
1661 inline_diagnostics_enabled: mode.is_full(),
1662 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1663
1664 gutter_hovered: false,
1665 pixel_position_of_newest_cursor: None,
1666 last_bounds: None,
1667 last_position_map: None,
1668 expect_bounds_change: None,
1669 gutter_dimensions: GutterDimensions::default(),
1670 style: None,
1671 show_cursor_names: false,
1672 hovered_cursors: Default::default(),
1673 next_editor_action_id: EditorActionId::default(),
1674 editor_actions: Rc::default(),
1675 inline_completions_hidden_for_vim_mode: false,
1676 show_inline_completions_override: None,
1677 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1678 edit_prediction_settings: EditPredictionSettings::Disabled,
1679 edit_prediction_indent_conflict: false,
1680 edit_prediction_requires_modifier_in_indent_conflict: true,
1681 custom_context_menu: None,
1682 show_git_blame_gutter: false,
1683 show_git_blame_inline: false,
1684 show_selection_menu: None,
1685 show_git_blame_inline_delay_task: None,
1686 git_blame_inline_tooltip: None,
1687 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1688 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1689 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1690 .session
1691 .restore_unsaved_buffers,
1692 blame: None,
1693 blame_subscription: None,
1694 tasks: Default::default(),
1695
1696 breakpoint_store,
1697 gutter_breakpoint_indicator: (None, None),
1698 _subscriptions: vec![
1699 cx.observe(&buffer, Self::on_buffer_changed),
1700 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1701 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1702 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1703 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1704 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1705 cx.observe_window_activation(window, |editor, window, cx| {
1706 let active = window.is_window_active();
1707 editor.blink_manager.update(cx, |blink_manager, cx| {
1708 if active {
1709 blink_manager.enable(cx);
1710 } else {
1711 blink_manager.disable(cx);
1712 }
1713 });
1714 }),
1715 ],
1716 tasks_update_task: None,
1717 linked_edit_ranges: Default::default(),
1718 in_project_search: false,
1719 previous_search_ranges: None,
1720 breadcrumb_header: None,
1721 focused_block: None,
1722 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1723 addons: HashMap::default(),
1724 registered_buffers: HashMap::default(),
1725 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1726 selection_mark_mode: false,
1727 toggle_fold_multiple_buffers: Task::ready(()),
1728 serialize_selections: Task::ready(()),
1729 serialize_folds: Task::ready(()),
1730 text_style_refinement: None,
1731 load_diff_task: load_uncommitted_diff,
1732 mouse_cursor_hidden: false,
1733 hide_mouse_mode: EditorSettings::get_global(cx)
1734 .hide_mouse
1735 .unwrap_or_default(),
1736 change_list: ChangeList::new(),
1737 };
1738 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1739 this._subscriptions
1740 .push(cx.observe(breakpoints, |_, _, cx| {
1741 cx.notify();
1742 }));
1743 }
1744 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1745 this._subscriptions.extend(project_subscriptions);
1746
1747 this._subscriptions.push(cx.subscribe_in(
1748 &cx.entity(),
1749 window,
1750 |editor, _, e: &EditorEvent, window, cx| match e {
1751 EditorEvent::ScrollPositionChanged { local, .. } => {
1752 if *local {
1753 let new_anchor = editor.scroll_manager.anchor();
1754 let snapshot = editor.snapshot(window, cx);
1755 editor.update_restoration_data(cx, move |data| {
1756 data.scroll_position = (
1757 new_anchor.top_row(&snapshot.buffer_snapshot),
1758 new_anchor.offset,
1759 );
1760 });
1761 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1762 }
1763 }
1764 EditorEvent::Edited { .. } => {
1765 if !vim_enabled(cx) {
1766 let (map, selections) = editor.selections.all_adjusted_display(cx);
1767 let pop_state = editor
1768 .change_list
1769 .last()
1770 .map(|previous| {
1771 previous.len() == selections.len()
1772 && previous.iter().enumerate().all(|(ix, p)| {
1773 p.to_display_point(&map).row()
1774 == selections[ix].head().row()
1775 })
1776 })
1777 .unwrap_or(false);
1778 let new_positions = selections
1779 .into_iter()
1780 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1781 .collect();
1782 editor
1783 .change_list
1784 .push_to_change_list(pop_state, new_positions);
1785 }
1786 }
1787 _ => (),
1788 },
1789 ));
1790
1791 this.end_selection(window, cx);
1792 this.scroll_manager.show_scrollbars(window, cx);
1793 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1794
1795 if mode.is_full() {
1796 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1797 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1798
1799 if this.git_blame_inline_enabled {
1800 this.git_blame_inline_enabled = true;
1801 this.start_git_blame_inline(false, window, cx);
1802 }
1803
1804 this.go_to_active_debug_line(window, cx);
1805
1806 if let Some(buffer) = buffer.read(cx).as_singleton() {
1807 if let Some(project) = this.project.as_ref() {
1808 let handle = project.update(cx, |project, cx| {
1809 project.register_buffer_with_language_servers(&buffer, cx)
1810 });
1811 this.registered_buffers
1812 .insert(buffer.read(cx).remote_id(), handle);
1813 }
1814 }
1815 }
1816
1817 this.report_editor_event("Editor Opened", None, cx);
1818 this
1819 }
1820
1821 pub fn deploy_mouse_context_menu(
1822 &mut self,
1823 position: gpui::Point<Pixels>,
1824 context_menu: Entity<ContextMenu>,
1825 window: &mut Window,
1826 cx: &mut Context<Self>,
1827 ) {
1828 self.mouse_context_menu = Some(MouseContextMenu::new(
1829 self,
1830 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1831 context_menu,
1832 window,
1833 cx,
1834 ));
1835 }
1836
1837 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1838 self.mouse_context_menu
1839 .as_ref()
1840 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1841 }
1842
1843 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1844 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1845 }
1846
1847 fn key_context_internal(
1848 &self,
1849 has_active_edit_prediction: bool,
1850 window: &Window,
1851 cx: &App,
1852 ) -> KeyContext {
1853 let mut key_context = KeyContext::new_with_defaults();
1854 key_context.add("Editor");
1855 let mode = match self.mode {
1856 EditorMode::SingleLine { .. } => "single_line",
1857 EditorMode::AutoHeight { .. } => "auto_height",
1858 EditorMode::Full { .. } => "full",
1859 };
1860
1861 if EditorSettings::jupyter_enabled(cx) {
1862 key_context.add("jupyter");
1863 }
1864
1865 key_context.set("mode", mode);
1866 if self.pending_rename.is_some() {
1867 key_context.add("renaming");
1868 }
1869
1870 match self.context_menu.borrow().as_ref() {
1871 Some(CodeContextMenu::Completions(_)) => {
1872 key_context.add("menu");
1873 key_context.add("showing_completions");
1874 }
1875 Some(CodeContextMenu::CodeActions(_)) => {
1876 key_context.add("menu");
1877 key_context.add("showing_code_actions")
1878 }
1879 None => {}
1880 }
1881
1882 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1883 if !self.focus_handle(cx).contains_focused(window, cx)
1884 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1885 {
1886 for addon in self.addons.values() {
1887 addon.extend_key_context(&mut key_context, cx)
1888 }
1889 }
1890
1891 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1892 if let Some(extension) = singleton_buffer
1893 .read(cx)
1894 .file()
1895 .and_then(|file| file.path().extension()?.to_str())
1896 {
1897 key_context.set("extension", extension.to_string());
1898 }
1899 } else {
1900 key_context.add("multibuffer");
1901 }
1902
1903 if has_active_edit_prediction {
1904 if self.edit_prediction_in_conflict() {
1905 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1906 } else {
1907 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1908 key_context.add("copilot_suggestion");
1909 }
1910 }
1911
1912 if self.selection_mark_mode {
1913 key_context.add("selection_mode");
1914 }
1915
1916 key_context
1917 }
1918
1919 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1920 self.mouse_cursor_hidden = match origin {
1921 HideMouseCursorOrigin::TypingAction => {
1922 matches!(
1923 self.hide_mouse_mode,
1924 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1925 )
1926 }
1927 HideMouseCursorOrigin::MovementAction => {
1928 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1929 }
1930 };
1931 }
1932
1933 pub fn edit_prediction_in_conflict(&self) -> bool {
1934 if !self.show_edit_predictions_in_menu() {
1935 return false;
1936 }
1937
1938 let showing_completions = self
1939 .context_menu
1940 .borrow()
1941 .as_ref()
1942 .map_or(false, |context| {
1943 matches!(context, CodeContextMenu::Completions(_))
1944 });
1945
1946 showing_completions
1947 || self.edit_prediction_requires_modifier()
1948 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1949 // bindings to insert tab characters.
1950 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1951 }
1952
1953 pub fn accept_edit_prediction_keybind(
1954 &self,
1955 window: &Window,
1956 cx: &App,
1957 ) -> AcceptEditPredictionBinding {
1958 let key_context = self.key_context_internal(true, window, cx);
1959 let in_conflict = self.edit_prediction_in_conflict();
1960
1961 AcceptEditPredictionBinding(
1962 window
1963 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1964 .into_iter()
1965 .filter(|binding| {
1966 !in_conflict
1967 || binding
1968 .keystrokes()
1969 .first()
1970 .map_or(false, |keystroke| keystroke.modifiers.modified())
1971 })
1972 .rev()
1973 .min_by_key(|binding| {
1974 binding
1975 .keystrokes()
1976 .first()
1977 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1978 }),
1979 )
1980 }
1981
1982 pub fn new_file(
1983 workspace: &mut Workspace,
1984 _: &workspace::NewFile,
1985 window: &mut Window,
1986 cx: &mut Context<Workspace>,
1987 ) {
1988 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1989 "Failed to create buffer",
1990 window,
1991 cx,
1992 |e, _, _| match e.error_code() {
1993 ErrorCode::RemoteUpgradeRequired => Some(format!(
1994 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1995 e.error_tag("required").unwrap_or("the latest version")
1996 )),
1997 _ => None,
1998 },
1999 );
2000 }
2001
2002 pub fn new_in_workspace(
2003 workspace: &mut Workspace,
2004 window: &mut Window,
2005 cx: &mut Context<Workspace>,
2006 ) -> Task<Result<Entity<Editor>>> {
2007 let project = workspace.project().clone();
2008 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2009
2010 cx.spawn_in(window, async move |workspace, cx| {
2011 let buffer = create.await?;
2012 workspace.update_in(cx, |workspace, window, cx| {
2013 let editor =
2014 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2015 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2016 editor
2017 })
2018 })
2019 }
2020
2021 fn new_file_vertical(
2022 workspace: &mut Workspace,
2023 _: &workspace::NewFileSplitVertical,
2024 window: &mut Window,
2025 cx: &mut Context<Workspace>,
2026 ) {
2027 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2028 }
2029
2030 fn new_file_horizontal(
2031 workspace: &mut Workspace,
2032 _: &workspace::NewFileSplitHorizontal,
2033 window: &mut Window,
2034 cx: &mut Context<Workspace>,
2035 ) {
2036 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2037 }
2038
2039 fn new_file_in_direction(
2040 workspace: &mut Workspace,
2041 direction: SplitDirection,
2042 window: &mut Window,
2043 cx: &mut Context<Workspace>,
2044 ) {
2045 let project = workspace.project().clone();
2046 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2047
2048 cx.spawn_in(window, async move |workspace, cx| {
2049 let buffer = create.await?;
2050 workspace.update_in(cx, move |workspace, window, cx| {
2051 workspace.split_item(
2052 direction,
2053 Box::new(
2054 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2055 ),
2056 window,
2057 cx,
2058 )
2059 })?;
2060 anyhow::Ok(())
2061 })
2062 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2063 match e.error_code() {
2064 ErrorCode::RemoteUpgradeRequired => Some(format!(
2065 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2066 e.error_tag("required").unwrap_or("the latest version")
2067 )),
2068 _ => None,
2069 }
2070 });
2071 }
2072
2073 pub fn leader_peer_id(&self) -> Option<PeerId> {
2074 self.leader_peer_id
2075 }
2076
2077 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2078 &self.buffer
2079 }
2080
2081 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2082 self.workspace.as_ref()?.0.upgrade()
2083 }
2084
2085 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2086 self.buffer().read(cx).title(cx)
2087 }
2088
2089 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2090 let git_blame_gutter_max_author_length = self
2091 .render_git_blame_gutter(cx)
2092 .then(|| {
2093 if let Some(blame) = self.blame.as_ref() {
2094 let max_author_length =
2095 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2096 Some(max_author_length)
2097 } else {
2098 None
2099 }
2100 })
2101 .flatten();
2102
2103 EditorSnapshot {
2104 mode: self.mode,
2105 show_gutter: self.show_gutter,
2106 show_line_numbers: self.show_line_numbers,
2107 show_git_diff_gutter: self.show_git_diff_gutter,
2108 show_code_actions: self.show_code_actions,
2109 show_runnables: self.show_runnables,
2110 show_breakpoints: self.show_breakpoints,
2111 git_blame_gutter_max_author_length,
2112 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2113 scroll_anchor: self.scroll_manager.anchor(),
2114 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2115 placeholder_text: self.placeholder_text.clone(),
2116 is_focused: self.focus_handle.is_focused(window),
2117 current_line_highlight: self
2118 .current_line_highlight
2119 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2120 gutter_hovered: self.gutter_hovered,
2121 }
2122 }
2123
2124 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2125 self.buffer.read(cx).language_at(point, cx)
2126 }
2127
2128 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2129 self.buffer.read(cx).read(cx).file_at(point).cloned()
2130 }
2131
2132 pub fn active_excerpt(
2133 &self,
2134 cx: &App,
2135 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2136 self.buffer
2137 .read(cx)
2138 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2139 }
2140
2141 pub fn mode(&self) -> EditorMode {
2142 self.mode
2143 }
2144
2145 pub fn set_mode(&mut self, mode: EditorMode) {
2146 self.mode = mode;
2147 }
2148
2149 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2150 self.collaboration_hub.as_deref()
2151 }
2152
2153 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2154 self.collaboration_hub = Some(hub);
2155 }
2156
2157 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2158 self.in_project_search = in_project_search;
2159 }
2160
2161 pub fn set_custom_context_menu(
2162 &mut self,
2163 f: impl 'static
2164 + Fn(
2165 &mut Self,
2166 DisplayPoint,
2167 &mut Window,
2168 &mut Context<Self>,
2169 ) -> Option<Entity<ui::ContextMenu>>,
2170 ) {
2171 self.custom_context_menu = Some(Box::new(f))
2172 }
2173
2174 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2175 self.completion_provider = provider;
2176 }
2177
2178 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2179 self.semantics_provider.clone()
2180 }
2181
2182 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2183 self.semantics_provider = provider;
2184 }
2185
2186 pub fn set_edit_prediction_provider<T>(
2187 &mut self,
2188 provider: Option<Entity<T>>,
2189 window: &mut Window,
2190 cx: &mut Context<Self>,
2191 ) where
2192 T: EditPredictionProvider,
2193 {
2194 self.edit_prediction_provider =
2195 provider.map(|provider| RegisteredInlineCompletionProvider {
2196 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2197 if this.focus_handle.is_focused(window) {
2198 this.update_visible_inline_completion(window, cx);
2199 }
2200 }),
2201 provider: Arc::new(provider),
2202 });
2203 self.update_edit_prediction_settings(cx);
2204 self.refresh_inline_completion(false, false, window, cx);
2205 }
2206
2207 pub fn placeholder_text(&self) -> Option<&str> {
2208 self.placeholder_text.as_deref()
2209 }
2210
2211 pub fn set_placeholder_text(
2212 &mut self,
2213 placeholder_text: impl Into<Arc<str>>,
2214 cx: &mut Context<Self>,
2215 ) {
2216 let placeholder_text = Some(placeholder_text.into());
2217 if self.placeholder_text != placeholder_text {
2218 self.placeholder_text = placeholder_text;
2219 cx.notify();
2220 }
2221 }
2222
2223 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2224 self.cursor_shape = cursor_shape;
2225
2226 // Disrupt blink for immediate user feedback that the cursor shape has changed
2227 self.blink_manager.update(cx, BlinkManager::show_cursor);
2228
2229 cx.notify();
2230 }
2231
2232 pub fn set_current_line_highlight(
2233 &mut self,
2234 current_line_highlight: Option<CurrentLineHighlight>,
2235 ) {
2236 self.current_line_highlight = current_line_highlight;
2237 }
2238
2239 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2240 self.collapse_matches = collapse_matches;
2241 }
2242
2243 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2244 let buffers = self.buffer.read(cx).all_buffers();
2245 let Some(project) = self.project.as_ref() else {
2246 return;
2247 };
2248 project.update(cx, |project, cx| {
2249 for buffer in buffers {
2250 self.registered_buffers
2251 .entry(buffer.read(cx).remote_id())
2252 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2253 }
2254 })
2255 }
2256
2257 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2258 if self.collapse_matches {
2259 return range.start..range.start;
2260 }
2261 range.clone()
2262 }
2263
2264 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2265 if self.display_map.read(cx).clip_at_line_ends != clip {
2266 self.display_map
2267 .update(cx, |map, _| map.clip_at_line_ends = clip);
2268 }
2269 }
2270
2271 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2272 self.input_enabled = input_enabled;
2273 }
2274
2275 pub fn set_inline_completions_hidden_for_vim_mode(
2276 &mut self,
2277 hidden: bool,
2278 window: &mut Window,
2279 cx: &mut Context<Self>,
2280 ) {
2281 if hidden != self.inline_completions_hidden_for_vim_mode {
2282 self.inline_completions_hidden_for_vim_mode = hidden;
2283 if hidden {
2284 self.update_visible_inline_completion(window, cx);
2285 } else {
2286 self.refresh_inline_completion(true, false, window, cx);
2287 }
2288 }
2289 }
2290
2291 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2292 self.menu_inline_completions_policy = value;
2293 }
2294
2295 pub fn set_autoindent(&mut self, autoindent: bool) {
2296 if autoindent {
2297 self.autoindent_mode = Some(AutoindentMode::EachLine);
2298 } else {
2299 self.autoindent_mode = None;
2300 }
2301 }
2302
2303 pub fn read_only(&self, cx: &App) -> bool {
2304 self.read_only || self.buffer.read(cx).read_only()
2305 }
2306
2307 pub fn set_read_only(&mut self, read_only: bool) {
2308 self.read_only = read_only;
2309 }
2310
2311 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2312 self.use_autoclose = autoclose;
2313 }
2314
2315 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2316 self.use_auto_surround = auto_surround;
2317 }
2318
2319 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2320 self.auto_replace_emoji_shortcode = auto_replace;
2321 }
2322
2323 pub fn toggle_edit_predictions(
2324 &mut self,
2325 _: &ToggleEditPrediction,
2326 window: &mut Window,
2327 cx: &mut Context<Self>,
2328 ) {
2329 if self.show_inline_completions_override.is_some() {
2330 self.set_show_edit_predictions(None, window, cx);
2331 } else {
2332 let show_edit_predictions = !self.edit_predictions_enabled();
2333 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2334 }
2335 }
2336
2337 pub fn set_show_edit_predictions(
2338 &mut self,
2339 show_edit_predictions: Option<bool>,
2340 window: &mut Window,
2341 cx: &mut Context<Self>,
2342 ) {
2343 self.show_inline_completions_override = show_edit_predictions;
2344 self.update_edit_prediction_settings(cx);
2345
2346 if let Some(false) = show_edit_predictions {
2347 self.discard_inline_completion(false, cx);
2348 } else {
2349 self.refresh_inline_completion(false, true, window, cx);
2350 }
2351 }
2352
2353 fn inline_completions_disabled_in_scope(
2354 &self,
2355 buffer: &Entity<Buffer>,
2356 buffer_position: language::Anchor,
2357 cx: &App,
2358 ) -> bool {
2359 let snapshot = buffer.read(cx).snapshot();
2360 let settings = snapshot.settings_at(buffer_position, cx);
2361
2362 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2363 return false;
2364 };
2365
2366 scope.override_name().map_or(false, |scope_name| {
2367 settings
2368 .edit_predictions_disabled_in
2369 .iter()
2370 .any(|s| s == scope_name)
2371 })
2372 }
2373
2374 pub fn set_use_modal_editing(&mut self, to: bool) {
2375 self.use_modal_editing = to;
2376 }
2377
2378 pub fn use_modal_editing(&self) -> bool {
2379 self.use_modal_editing
2380 }
2381
2382 fn selections_did_change(
2383 &mut self,
2384 local: bool,
2385 old_cursor_position: &Anchor,
2386 show_completions: bool,
2387 window: &mut Window,
2388 cx: &mut Context<Self>,
2389 ) {
2390 window.invalidate_character_coordinates();
2391
2392 // Copy selections to primary selection buffer
2393 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2394 if local {
2395 let selections = self.selections.all::<usize>(cx);
2396 let buffer_handle = self.buffer.read(cx).read(cx);
2397
2398 let mut text = String::new();
2399 for (index, selection) in selections.iter().enumerate() {
2400 let text_for_selection = buffer_handle
2401 .text_for_range(selection.start..selection.end)
2402 .collect::<String>();
2403
2404 text.push_str(&text_for_selection);
2405 if index != selections.len() - 1 {
2406 text.push('\n');
2407 }
2408 }
2409
2410 if !text.is_empty() {
2411 cx.write_to_primary(ClipboardItem::new_string(text));
2412 }
2413 }
2414
2415 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2416 self.buffer.update(cx, |buffer, cx| {
2417 buffer.set_active_selections(
2418 &self.selections.disjoint_anchors(),
2419 self.selections.line_mode,
2420 self.cursor_shape,
2421 cx,
2422 )
2423 });
2424 }
2425 let display_map = self
2426 .display_map
2427 .update(cx, |display_map, cx| display_map.snapshot(cx));
2428 let buffer = &display_map.buffer_snapshot;
2429 self.add_selections_state = None;
2430 self.select_next_state = None;
2431 self.select_prev_state = None;
2432 self.select_syntax_node_history.try_clear();
2433 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2434 self.snippet_stack
2435 .invalidate(&self.selections.disjoint_anchors(), buffer);
2436 self.take_rename(false, window, cx);
2437
2438 let new_cursor_position = self.selections.newest_anchor().head();
2439
2440 self.push_to_nav_history(
2441 *old_cursor_position,
2442 Some(new_cursor_position.to_point(buffer)),
2443 false,
2444 cx,
2445 );
2446
2447 if local {
2448 let new_cursor_position = self.selections.newest_anchor().head();
2449 let mut context_menu = self.context_menu.borrow_mut();
2450 let completion_menu = match context_menu.as_ref() {
2451 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2452 _ => {
2453 *context_menu = None;
2454 None
2455 }
2456 };
2457 if let Some(buffer_id) = new_cursor_position.buffer_id {
2458 if !self.registered_buffers.contains_key(&buffer_id) {
2459 if let Some(project) = self.project.as_ref() {
2460 project.update(cx, |project, cx| {
2461 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2462 return;
2463 };
2464 self.registered_buffers.insert(
2465 buffer_id,
2466 project.register_buffer_with_language_servers(&buffer, cx),
2467 );
2468 })
2469 }
2470 }
2471 }
2472
2473 if let Some(completion_menu) = completion_menu {
2474 let cursor_position = new_cursor_position.to_offset(buffer);
2475 let (word_range, kind) =
2476 buffer.surrounding_word(completion_menu.initial_position, true);
2477 if kind == Some(CharKind::Word)
2478 && word_range.to_inclusive().contains(&cursor_position)
2479 {
2480 let mut completion_menu = completion_menu.clone();
2481 drop(context_menu);
2482
2483 let query = Self::completion_query(buffer, cursor_position);
2484 cx.spawn(async move |this, cx| {
2485 completion_menu
2486 .filter(query.as_deref(), cx.background_executor().clone())
2487 .await;
2488
2489 this.update(cx, |this, cx| {
2490 let mut context_menu = this.context_menu.borrow_mut();
2491 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2492 else {
2493 return;
2494 };
2495
2496 if menu.id > completion_menu.id {
2497 return;
2498 }
2499
2500 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2501 drop(context_menu);
2502 cx.notify();
2503 })
2504 })
2505 .detach();
2506
2507 if show_completions {
2508 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2509 }
2510 } else {
2511 drop(context_menu);
2512 self.hide_context_menu(window, cx);
2513 }
2514 } else {
2515 drop(context_menu);
2516 }
2517
2518 hide_hover(self, cx);
2519
2520 if old_cursor_position.to_display_point(&display_map).row()
2521 != new_cursor_position.to_display_point(&display_map).row()
2522 {
2523 self.available_code_actions.take();
2524 }
2525 self.refresh_code_actions(window, cx);
2526 self.refresh_document_highlights(cx);
2527 self.refresh_selected_text_highlights(window, cx);
2528 refresh_matching_bracket_highlights(self, window, cx);
2529 self.update_visible_inline_completion(window, cx);
2530 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2531 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2532 if self.git_blame_inline_enabled {
2533 self.start_inline_blame_timer(window, cx);
2534 }
2535 }
2536
2537 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2538 cx.emit(EditorEvent::SelectionsChanged { local });
2539
2540 let selections = &self.selections.disjoint;
2541 if selections.len() == 1 {
2542 cx.emit(SearchEvent::ActiveMatchChanged)
2543 }
2544 if local {
2545 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2546 let inmemory_selections = selections
2547 .iter()
2548 .map(|s| {
2549 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2550 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2551 })
2552 .collect();
2553 self.update_restoration_data(cx, |data| {
2554 data.selections = inmemory_selections;
2555 });
2556
2557 if WorkspaceSettings::get(None, cx).restore_on_startup
2558 != RestoreOnStartupBehavior::None
2559 {
2560 if let Some(workspace_id) =
2561 self.workspace.as_ref().and_then(|workspace| workspace.1)
2562 {
2563 let snapshot = self.buffer().read(cx).snapshot(cx);
2564 let selections = selections.clone();
2565 let background_executor = cx.background_executor().clone();
2566 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2567 self.serialize_selections = cx.background_spawn(async move {
2568 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2569 let db_selections = selections
2570 .iter()
2571 .map(|selection| {
2572 (
2573 selection.start.to_offset(&snapshot),
2574 selection.end.to_offset(&snapshot),
2575 )
2576 })
2577 .collect();
2578
2579 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2580 .await
2581 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2582 .log_err();
2583 });
2584 }
2585 }
2586 }
2587 }
2588
2589 cx.notify();
2590 }
2591
2592 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2593 use text::ToOffset as _;
2594 use text::ToPoint as _;
2595
2596 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2597 return;
2598 }
2599
2600 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2601 return;
2602 };
2603
2604 let snapshot = singleton.read(cx).snapshot();
2605 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2606 let display_snapshot = display_map.snapshot(cx);
2607
2608 display_snapshot
2609 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2610 .map(|fold| {
2611 fold.range.start.text_anchor.to_point(&snapshot)
2612 ..fold.range.end.text_anchor.to_point(&snapshot)
2613 })
2614 .collect()
2615 });
2616 self.update_restoration_data(cx, |data| {
2617 data.folds = inmemory_folds;
2618 });
2619
2620 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2621 return;
2622 };
2623 let background_executor = cx.background_executor().clone();
2624 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2625 let db_folds = self.display_map.update(cx, |display_map, cx| {
2626 display_map
2627 .snapshot(cx)
2628 .folds_in_range(0..snapshot.len())
2629 .map(|fold| {
2630 (
2631 fold.range.start.text_anchor.to_offset(&snapshot),
2632 fold.range.end.text_anchor.to_offset(&snapshot),
2633 )
2634 })
2635 .collect()
2636 });
2637 self.serialize_folds = cx.background_spawn(async move {
2638 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2639 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2640 .await
2641 .with_context(|| {
2642 format!(
2643 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2644 )
2645 })
2646 .log_err();
2647 });
2648 }
2649
2650 pub fn sync_selections(
2651 &mut self,
2652 other: Entity<Editor>,
2653 cx: &mut Context<Self>,
2654 ) -> gpui::Subscription {
2655 let other_selections = other.read(cx).selections.disjoint.to_vec();
2656 self.selections.change_with(cx, |selections| {
2657 selections.select_anchors(other_selections);
2658 });
2659
2660 let other_subscription =
2661 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2662 EditorEvent::SelectionsChanged { local: true } => {
2663 let other_selections = other.read(cx).selections.disjoint.to_vec();
2664 if other_selections.is_empty() {
2665 return;
2666 }
2667 this.selections.change_with(cx, |selections| {
2668 selections.select_anchors(other_selections);
2669 });
2670 }
2671 _ => {}
2672 });
2673
2674 let this_subscription =
2675 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2676 EditorEvent::SelectionsChanged { local: true } => {
2677 let these_selections = this.selections.disjoint.to_vec();
2678 if these_selections.is_empty() {
2679 return;
2680 }
2681 other.update(cx, |other_editor, cx| {
2682 other_editor.selections.change_with(cx, |selections| {
2683 selections.select_anchors(these_selections);
2684 })
2685 });
2686 }
2687 _ => {}
2688 });
2689
2690 Subscription::join(other_subscription, this_subscription)
2691 }
2692
2693 pub fn change_selections<R>(
2694 &mut self,
2695 autoscroll: Option<Autoscroll>,
2696 window: &mut Window,
2697 cx: &mut Context<Self>,
2698 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2699 ) -> R {
2700 self.change_selections_inner(autoscroll, true, window, cx, change)
2701 }
2702
2703 fn change_selections_inner<R>(
2704 &mut self,
2705 autoscroll: Option<Autoscroll>,
2706 request_completions: bool,
2707 window: &mut Window,
2708 cx: &mut Context<Self>,
2709 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2710 ) -> R {
2711 let old_cursor_position = self.selections.newest_anchor().head();
2712 self.push_to_selection_history();
2713
2714 let (changed, result) = self.selections.change_with(cx, change);
2715
2716 if changed {
2717 if let Some(autoscroll) = autoscroll {
2718 self.request_autoscroll(autoscroll, cx);
2719 }
2720 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2721
2722 if self.should_open_signature_help_automatically(
2723 &old_cursor_position,
2724 self.signature_help_state.backspace_pressed(),
2725 cx,
2726 ) {
2727 self.show_signature_help(&ShowSignatureHelp, window, cx);
2728 }
2729 self.signature_help_state.set_backspace_pressed(false);
2730 }
2731
2732 result
2733 }
2734
2735 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2736 where
2737 I: IntoIterator<Item = (Range<S>, T)>,
2738 S: ToOffset,
2739 T: Into<Arc<str>>,
2740 {
2741 if self.read_only(cx) {
2742 return;
2743 }
2744
2745 self.buffer
2746 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2747 }
2748
2749 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2750 where
2751 I: IntoIterator<Item = (Range<S>, T)>,
2752 S: ToOffset,
2753 T: Into<Arc<str>>,
2754 {
2755 if self.read_only(cx) {
2756 return;
2757 }
2758
2759 self.buffer.update(cx, |buffer, cx| {
2760 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2761 });
2762 }
2763
2764 pub fn edit_with_block_indent<I, S, T>(
2765 &mut self,
2766 edits: I,
2767 original_indent_columns: Vec<Option<u32>>,
2768 cx: &mut Context<Self>,
2769 ) where
2770 I: IntoIterator<Item = (Range<S>, T)>,
2771 S: ToOffset,
2772 T: Into<Arc<str>>,
2773 {
2774 if self.read_only(cx) {
2775 return;
2776 }
2777
2778 self.buffer.update(cx, |buffer, cx| {
2779 buffer.edit(
2780 edits,
2781 Some(AutoindentMode::Block {
2782 original_indent_columns,
2783 }),
2784 cx,
2785 )
2786 });
2787 }
2788
2789 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2790 self.hide_context_menu(window, cx);
2791
2792 match phase {
2793 SelectPhase::Begin {
2794 position,
2795 add,
2796 click_count,
2797 } => self.begin_selection(position, add, click_count, window, cx),
2798 SelectPhase::BeginColumnar {
2799 position,
2800 goal_column,
2801 reset,
2802 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2803 SelectPhase::Extend {
2804 position,
2805 click_count,
2806 } => self.extend_selection(position, click_count, window, cx),
2807 SelectPhase::Update {
2808 position,
2809 goal_column,
2810 scroll_delta,
2811 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2812 SelectPhase::End => self.end_selection(window, cx),
2813 }
2814 }
2815
2816 fn extend_selection(
2817 &mut self,
2818 position: DisplayPoint,
2819 click_count: usize,
2820 window: &mut Window,
2821 cx: &mut Context<Self>,
2822 ) {
2823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2824 let tail = self.selections.newest::<usize>(cx).tail();
2825 self.begin_selection(position, false, click_count, window, cx);
2826
2827 let position = position.to_offset(&display_map, Bias::Left);
2828 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2829
2830 let mut pending_selection = self
2831 .selections
2832 .pending_anchor()
2833 .expect("extend_selection not called with pending selection");
2834 if position >= tail {
2835 pending_selection.start = tail_anchor;
2836 } else {
2837 pending_selection.end = tail_anchor;
2838 pending_selection.reversed = true;
2839 }
2840
2841 let mut pending_mode = self.selections.pending_mode().unwrap();
2842 match &mut pending_mode {
2843 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2844 _ => {}
2845 }
2846
2847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2848 s.set_pending(pending_selection, pending_mode)
2849 });
2850 }
2851
2852 fn begin_selection(
2853 &mut self,
2854 position: DisplayPoint,
2855 add: bool,
2856 click_count: usize,
2857 window: &mut Window,
2858 cx: &mut Context<Self>,
2859 ) {
2860 if !self.focus_handle.is_focused(window) {
2861 self.last_focused_descendant = None;
2862 window.focus(&self.focus_handle);
2863 }
2864
2865 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2866 let buffer = &display_map.buffer_snapshot;
2867 let newest_selection = self.selections.newest_anchor().clone();
2868 let position = display_map.clip_point(position, Bias::Left);
2869
2870 let start;
2871 let end;
2872 let mode;
2873 let mut auto_scroll;
2874 match click_count {
2875 1 => {
2876 start = buffer.anchor_before(position.to_point(&display_map));
2877 end = start;
2878 mode = SelectMode::Character;
2879 auto_scroll = true;
2880 }
2881 2 => {
2882 let range = movement::surrounding_word(&display_map, position);
2883 start = buffer.anchor_before(range.start.to_point(&display_map));
2884 end = buffer.anchor_before(range.end.to_point(&display_map));
2885 mode = SelectMode::Word(start..end);
2886 auto_scroll = true;
2887 }
2888 3 => {
2889 let position = display_map
2890 .clip_point(position, Bias::Left)
2891 .to_point(&display_map);
2892 let line_start = display_map.prev_line_boundary(position).0;
2893 let next_line_start = buffer.clip_point(
2894 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2895 Bias::Left,
2896 );
2897 start = buffer.anchor_before(line_start);
2898 end = buffer.anchor_before(next_line_start);
2899 mode = SelectMode::Line(start..end);
2900 auto_scroll = true;
2901 }
2902 _ => {
2903 start = buffer.anchor_before(0);
2904 end = buffer.anchor_before(buffer.len());
2905 mode = SelectMode::All;
2906 auto_scroll = false;
2907 }
2908 }
2909 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2910
2911 let point_to_delete: Option<usize> = {
2912 let selected_points: Vec<Selection<Point>> =
2913 self.selections.disjoint_in_range(start..end, cx);
2914
2915 if !add || click_count > 1 {
2916 None
2917 } else if !selected_points.is_empty() {
2918 Some(selected_points[0].id)
2919 } else {
2920 let clicked_point_already_selected =
2921 self.selections.disjoint.iter().find(|selection| {
2922 selection.start.to_point(buffer) == start.to_point(buffer)
2923 || selection.end.to_point(buffer) == end.to_point(buffer)
2924 });
2925
2926 clicked_point_already_selected.map(|selection| selection.id)
2927 }
2928 };
2929
2930 let selections_count = self.selections.count();
2931
2932 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2933 if let Some(point_to_delete) = point_to_delete {
2934 s.delete(point_to_delete);
2935
2936 if selections_count == 1 {
2937 s.set_pending_anchor_range(start..end, mode);
2938 }
2939 } else {
2940 if !add {
2941 s.clear_disjoint();
2942 } else if click_count > 1 {
2943 s.delete(newest_selection.id)
2944 }
2945
2946 s.set_pending_anchor_range(start..end, mode);
2947 }
2948 });
2949 }
2950
2951 fn begin_columnar_selection(
2952 &mut self,
2953 position: DisplayPoint,
2954 goal_column: u32,
2955 reset: bool,
2956 window: &mut Window,
2957 cx: &mut Context<Self>,
2958 ) {
2959 if !self.focus_handle.is_focused(window) {
2960 self.last_focused_descendant = None;
2961 window.focus(&self.focus_handle);
2962 }
2963
2964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2965
2966 if reset {
2967 let pointer_position = display_map
2968 .buffer_snapshot
2969 .anchor_before(position.to_point(&display_map));
2970
2971 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2972 s.clear_disjoint();
2973 s.set_pending_anchor_range(
2974 pointer_position..pointer_position,
2975 SelectMode::Character,
2976 );
2977 });
2978 }
2979
2980 let tail = self.selections.newest::<Point>(cx).tail();
2981 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2982
2983 if !reset {
2984 self.select_columns(
2985 tail.to_display_point(&display_map),
2986 position,
2987 goal_column,
2988 &display_map,
2989 window,
2990 cx,
2991 );
2992 }
2993 }
2994
2995 fn update_selection(
2996 &mut self,
2997 position: DisplayPoint,
2998 goal_column: u32,
2999 scroll_delta: gpui::Point<f32>,
3000 window: &mut Window,
3001 cx: &mut Context<Self>,
3002 ) {
3003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3004
3005 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3006 let tail = tail.to_display_point(&display_map);
3007 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3008 } else if let Some(mut pending) = self.selections.pending_anchor() {
3009 let buffer = self.buffer.read(cx).snapshot(cx);
3010 let head;
3011 let tail;
3012 let mode = self.selections.pending_mode().unwrap();
3013 match &mode {
3014 SelectMode::Character => {
3015 head = position.to_point(&display_map);
3016 tail = pending.tail().to_point(&buffer);
3017 }
3018 SelectMode::Word(original_range) => {
3019 let original_display_range = original_range.start.to_display_point(&display_map)
3020 ..original_range.end.to_display_point(&display_map);
3021 let original_buffer_range = original_display_range.start.to_point(&display_map)
3022 ..original_display_range.end.to_point(&display_map);
3023 if movement::is_inside_word(&display_map, position)
3024 || original_display_range.contains(&position)
3025 {
3026 let word_range = movement::surrounding_word(&display_map, position);
3027 if word_range.start < original_display_range.start {
3028 head = word_range.start.to_point(&display_map);
3029 } else {
3030 head = word_range.end.to_point(&display_map);
3031 }
3032 } else {
3033 head = position.to_point(&display_map);
3034 }
3035
3036 if head <= original_buffer_range.start {
3037 tail = original_buffer_range.end;
3038 } else {
3039 tail = original_buffer_range.start;
3040 }
3041 }
3042 SelectMode::Line(original_range) => {
3043 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3044
3045 let position = display_map
3046 .clip_point(position, Bias::Left)
3047 .to_point(&display_map);
3048 let line_start = display_map.prev_line_boundary(position).0;
3049 let next_line_start = buffer.clip_point(
3050 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3051 Bias::Left,
3052 );
3053
3054 if line_start < original_range.start {
3055 head = line_start
3056 } else {
3057 head = next_line_start
3058 }
3059
3060 if head <= original_range.start {
3061 tail = original_range.end;
3062 } else {
3063 tail = original_range.start;
3064 }
3065 }
3066 SelectMode::All => {
3067 return;
3068 }
3069 };
3070
3071 if head < tail {
3072 pending.start = buffer.anchor_before(head);
3073 pending.end = buffer.anchor_before(tail);
3074 pending.reversed = true;
3075 } else {
3076 pending.start = buffer.anchor_before(tail);
3077 pending.end = buffer.anchor_before(head);
3078 pending.reversed = false;
3079 }
3080
3081 self.change_selections(None, window, cx, |s| {
3082 s.set_pending(pending, mode);
3083 });
3084 } else {
3085 log::error!("update_selection dispatched with no pending selection");
3086 return;
3087 }
3088
3089 self.apply_scroll_delta(scroll_delta, window, cx);
3090 cx.notify();
3091 }
3092
3093 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3094 self.columnar_selection_tail.take();
3095 if self.selections.pending_anchor().is_some() {
3096 let selections = self.selections.all::<usize>(cx);
3097 self.change_selections(None, window, cx, |s| {
3098 s.select(selections);
3099 s.clear_pending();
3100 });
3101 }
3102 }
3103
3104 fn select_columns(
3105 &mut self,
3106 tail: DisplayPoint,
3107 head: DisplayPoint,
3108 goal_column: u32,
3109 display_map: &DisplaySnapshot,
3110 window: &mut Window,
3111 cx: &mut Context<Self>,
3112 ) {
3113 let start_row = cmp::min(tail.row(), head.row());
3114 let end_row = cmp::max(tail.row(), head.row());
3115 let start_column = cmp::min(tail.column(), goal_column);
3116 let end_column = cmp::max(tail.column(), goal_column);
3117 let reversed = start_column < tail.column();
3118
3119 let selection_ranges = (start_row.0..=end_row.0)
3120 .map(DisplayRow)
3121 .filter_map(|row| {
3122 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3123 let start = display_map
3124 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3125 .to_point(display_map);
3126 let end = display_map
3127 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3128 .to_point(display_map);
3129 if reversed {
3130 Some(end..start)
3131 } else {
3132 Some(start..end)
3133 }
3134 } else {
3135 None
3136 }
3137 })
3138 .collect::<Vec<_>>();
3139
3140 self.change_selections(None, window, cx, |s| {
3141 s.select_ranges(selection_ranges);
3142 });
3143 cx.notify();
3144 }
3145
3146 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3147 self.selections
3148 .all_adjusted(cx)
3149 .iter()
3150 .any(|selection| !selection.is_empty())
3151 }
3152
3153 pub fn has_pending_nonempty_selection(&self) -> bool {
3154 let pending_nonempty_selection = match self.selections.pending_anchor() {
3155 Some(Selection { start, end, .. }) => start != end,
3156 None => false,
3157 };
3158
3159 pending_nonempty_selection
3160 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3161 }
3162
3163 pub fn has_pending_selection(&self) -> bool {
3164 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3165 }
3166
3167 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3168 self.selection_mark_mode = false;
3169
3170 if self.clear_expanded_diff_hunks(cx) {
3171 cx.notify();
3172 return;
3173 }
3174 if self.dismiss_menus_and_popups(true, window, cx) {
3175 return;
3176 }
3177
3178 if self.mode.is_full()
3179 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3180 {
3181 return;
3182 }
3183
3184 cx.propagate();
3185 }
3186
3187 pub fn dismiss_menus_and_popups(
3188 &mut self,
3189 is_user_requested: bool,
3190 window: &mut Window,
3191 cx: &mut Context<Self>,
3192 ) -> bool {
3193 if self.take_rename(false, window, cx).is_some() {
3194 return true;
3195 }
3196
3197 if hide_hover(self, cx) {
3198 return true;
3199 }
3200
3201 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3202 return true;
3203 }
3204
3205 if self.hide_context_menu(window, cx).is_some() {
3206 return true;
3207 }
3208
3209 if self.mouse_context_menu.take().is_some() {
3210 return true;
3211 }
3212
3213 if is_user_requested && self.discard_inline_completion(true, cx) {
3214 return true;
3215 }
3216
3217 if self.snippet_stack.pop().is_some() {
3218 return true;
3219 }
3220
3221 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3222 self.dismiss_diagnostics(cx);
3223 return true;
3224 }
3225
3226 false
3227 }
3228
3229 fn linked_editing_ranges_for(
3230 &self,
3231 selection: Range<text::Anchor>,
3232 cx: &App,
3233 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3234 if self.linked_edit_ranges.is_empty() {
3235 return None;
3236 }
3237 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3238 selection.end.buffer_id.and_then(|end_buffer_id| {
3239 if selection.start.buffer_id != Some(end_buffer_id) {
3240 return None;
3241 }
3242 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3243 let snapshot = buffer.read(cx).snapshot();
3244 self.linked_edit_ranges
3245 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3246 .map(|ranges| (ranges, snapshot, buffer))
3247 })?;
3248 use text::ToOffset as TO;
3249 // find offset from the start of current range to current cursor position
3250 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3251
3252 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3253 let start_difference = start_offset - start_byte_offset;
3254 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3255 let end_difference = end_offset - start_byte_offset;
3256 // Current range has associated linked ranges.
3257 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3258 for range in linked_ranges.iter() {
3259 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3260 let end_offset = start_offset + end_difference;
3261 let start_offset = start_offset + start_difference;
3262 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3263 continue;
3264 }
3265 if self.selections.disjoint_anchor_ranges().any(|s| {
3266 if s.start.buffer_id != selection.start.buffer_id
3267 || s.end.buffer_id != selection.end.buffer_id
3268 {
3269 return false;
3270 }
3271 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3272 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3273 }) {
3274 continue;
3275 }
3276 let start = buffer_snapshot.anchor_after(start_offset);
3277 let end = buffer_snapshot.anchor_after(end_offset);
3278 linked_edits
3279 .entry(buffer.clone())
3280 .or_default()
3281 .push(start..end);
3282 }
3283 Some(linked_edits)
3284 }
3285
3286 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3287 let text: Arc<str> = text.into();
3288
3289 if self.read_only(cx) {
3290 return;
3291 }
3292
3293 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3294
3295 let selections = self.selections.all_adjusted(cx);
3296 let mut bracket_inserted = false;
3297 let mut edits = Vec::new();
3298 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3299 let mut new_selections = Vec::with_capacity(selections.len());
3300 let mut new_autoclose_regions = Vec::new();
3301 let snapshot = self.buffer.read(cx).read(cx);
3302 let mut clear_linked_edit_ranges = false;
3303
3304 for (selection, autoclose_region) in
3305 self.selections_with_autoclose_regions(selections, &snapshot)
3306 {
3307 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3308 // Determine if the inserted text matches the opening or closing
3309 // bracket of any of this language's bracket pairs.
3310 let mut bracket_pair = None;
3311 let mut is_bracket_pair_start = false;
3312 let mut is_bracket_pair_end = false;
3313 if !text.is_empty() {
3314 let mut bracket_pair_matching_end = None;
3315 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3316 // and they are removing the character that triggered IME popup.
3317 for (pair, enabled) in scope.brackets() {
3318 if !pair.close && !pair.surround {
3319 continue;
3320 }
3321
3322 if enabled && pair.start.ends_with(text.as_ref()) {
3323 let prefix_len = pair.start.len() - text.len();
3324 let preceding_text_matches_prefix = prefix_len == 0
3325 || (selection.start.column >= (prefix_len as u32)
3326 && snapshot.contains_str_at(
3327 Point::new(
3328 selection.start.row,
3329 selection.start.column - (prefix_len as u32),
3330 ),
3331 &pair.start[..prefix_len],
3332 ));
3333 if preceding_text_matches_prefix {
3334 bracket_pair = Some(pair.clone());
3335 is_bracket_pair_start = true;
3336 break;
3337 }
3338 }
3339 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3340 {
3341 // take first bracket pair matching end, but don't break in case a later bracket
3342 // pair matches start
3343 bracket_pair_matching_end = Some(pair.clone());
3344 }
3345 }
3346 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3347 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3348 is_bracket_pair_end = true;
3349 }
3350 }
3351
3352 if let Some(bracket_pair) = bracket_pair {
3353 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3354 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3355 let auto_surround =
3356 self.use_auto_surround && snapshot_settings.use_auto_surround;
3357 if selection.is_empty() {
3358 if is_bracket_pair_start {
3359 // If the inserted text is a suffix of an opening bracket and the
3360 // selection is preceded by the rest of the opening bracket, then
3361 // insert the closing bracket.
3362 let following_text_allows_autoclose = snapshot
3363 .chars_at(selection.start)
3364 .next()
3365 .map_or(true, |c| scope.should_autoclose_before(c));
3366
3367 let preceding_text_allows_autoclose = selection.start.column == 0
3368 || snapshot.reversed_chars_at(selection.start).next().map_or(
3369 true,
3370 |c| {
3371 bracket_pair.start != bracket_pair.end
3372 || !snapshot
3373 .char_classifier_at(selection.start)
3374 .is_word(c)
3375 },
3376 );
3377
3378 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3379 && bracket_pair.start.len() == 1
3380 {
3381 let target = bracket_pair.start.chars().next().unwrap();
3382 let current_line_count = snapshot
3383 .reversed_chars_at(selection.start)
3384 .take_while(|&c| c != '\n')
3385 .filter(|&c| c == target)
3386 .count();
3387 current_line_count % 2 == 1
3388 } else {
3389 false
3390 };
3391
3392 if autoclose
3393 && bracket_pair.close
3394 && following_text_allows_autoclose
3395 && preceding_text_allows_autoclose
3396 && !is_closing_quote
3397 {
3398 let anchor = snapshot.anchor_before(selection.end);
3399 new_selections.push((selection.map(|_| anchor), text.len()));
3400 new_autoclose_regions.push((
3401 anchor,
3402 text.len(),
3403 selection.id,
3404 bracket_pair.clone(),
3405 ));
3406 edits.push((
3407 selection.range(),
3408 format!("{}{}", text, bracket_pair.end).into(),
3409 ));
3410 bracket_inserted = true;
3411 continue;
3412 }
3413 }
3414
3415 if let Some(region) = autoclose_region {
3416 // If the selection is followed by an auto-inserted closing bracket,
3417 // then don't insert that closing bracket again; just move the selection
3418 // past the closing bracket.
3419 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3420 && text.as_ref() == region.pair.end.as_str();
3421 if should_skip {
3422 let anchor = snapshot.anchor_after(selection.end);
3423 new_selections
3424 .push((selection.map(|_| anchor), region.pair.end.len()));
3425 continue;
3426 }
3427 }
3428
3429 let always_treat_brackets_as_autoclosed = snapshot
3430 .language_settings_at(selection.start, cx)
3431 .always_treat_brackets_as_autoclosed;
3432 if always_treat_brackets_as_autoclosed
3433 && is_bracket_pair_end
3434 && snapshot.contains_str_at(selection.end, text.as_ref())
3435 {
3436 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3437 // and the inserted text is a closing bracket and the selection is followed
3438 // by the closing bracket then move the selection past the closing bracket.
3439 let anchor = snapshot.anchor_after(selection.end);
3440 new_selections.push((selection.map(|_| anchor), text.len()));
3441 continue;
3442 }
3443 }
3444 // If an opening bracket is 1 character long and is typed while
3445 // text is selected, then surround that text with the bracket pair.
3446 else if auto_surround
3447 && bracket_pair.surround
3448 && is_bracket_pair_start
3449 && bracket_pair.start.chars().count() == 1
3450 {
3451 edits.push((selection.start..selection.start, text.clone()));
3452 edits.push((
3453 selection.end..selection.end,
3454 bracket_pair.end.as_str().into(),
3455 ));
3456 bracket_inserted = true;
3457 new_selections.push((
3458 Selection {
3459 id: selection.id,
3460 start: snapshot.anchor_after(selection.start),
3461 end: snapshot.anchor_before(selection.end),
3462 reversed: selection.reversed,
3463 goal: selection.goal,
3464 },
3465 0,
3466 ));
3467 continue;
3468 }
3469 }
3470 }
3471
3472 if self.auto_replace_emoji_shortcode
3473 && selection.is_empty()
3474 && text.as_ref().ends_with(':')
3475 {
3476 if let Some(possible_emoji_short_code) =
3477 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3478 {
3479 if !possible_emoji_short_code.is_empty() {
3480 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3481 let emoji_shortcode_start = Point::new(
3482 selection.start.row,
3483 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3484 );
3485
3486 // Remove shortcode from buffer
3487 edits.push((
3488 emoji_shortcode_start..selection.start,
3489 "".to_string().into(),
3490 ));
3491 new_selections.push((
3492 Selection {
3493 id: selection.id,
3494 start: snapshot.anchor_after(emoji_shortcode_start),
3495 end: snapshot.anchor_before(selection.start),
3496 reversed: selection.reversed,
3497 goal: selection.goal,
3498 },
3499 0,
3500 ));
3501
3502 // Insert emoji
3503 let selection_start_anchor = snapshot.anchor_after(selection.start);
3504 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3505 edits.push((selection.start..selection.end, emoji.to_string().into()));
3506
3507 continue;
3508 }
3509 }
3510 }
3511 }
3512
3513 // If not handling any auto-close operation, then just replace the selected
3514 // text with the given input and move the selection to the end of the
3515 // newly inserted text.
3516 let anchor = snapshot.anchor_after(selection.end);
3517 if !self.linked_edit_ranges.is_empty() {
3518 let start_anchor = snapshot.anchor_before(selection.start);
3519
3520 let is_word_char = text.chars().next().map_or(true, |char| {
3521 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3522 classifier.is_word(char)
3523 });
3524
3525 if is_word_char {
3526 if let Some(ranges) = self
3527 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3528 {
3529 for (buffer, edits) in ranges {
3530 linked_edits
3531 .entry(buffer.clone())
3532 .or_default()
3533 .extend(edits.into_iter().map(|range| (range, text.clone())));
3534 }
3535 }
3536 } else {
3537 clear_linked_edit_ranges = true;
3538 }
3539 }
3540
3541 new_selections.push((selection.map(|_| anchor), 0));
3542 edits.push((selection.start..selection.end, text.clone()));
3543 }
3544
3545 drop(snapshot);
3546
3547 self.transact(window, cx, |this, window, cx| {
3548 if clear_linked_edit_ranges {
3549 this.linked_edit_ranges.clear();
3550 }
3551 let initial_buffer_versions =
3552 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3553
3554 this.buffer.update(cx, |buffer, cx| {
3555 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3556 });
3557 for (buffer, edits) in linked_edits {
3558 buffer.update(cx, |buffer, cx| {
3559 let snapshot = buffer.snapshot();
3560 let edits = edits
3561 .into_iter()
3562 .map(|(range, text)| {
3563 use text::ToPoint as TP;
3564 let end_point = TP::to_point(&range.end, &snapshot);
3565 let start_point = TP::to_point(&range.start, &snapshot);
3566 (start_point..end_point, text)
3567 })
3568 .sorted_by_key(|(range, _)| range.start);
3569 buffer.edit(edits, None, cx);
3570 })
3571 }
3572 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3573 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3574 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3575 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3576 .zip(new_selection_deltas)
3577 .map(|(selection, delta)| Selection {
3578 id: selection.id,
3579 start: selection.start + delta,
3580 end: selection.end + delta,
3581 reversed: selection.reversed,
3582 goal: SelectionGoal::None,
3583 })
3584 .collect::<Vec<_>>();
3585
3586 let mut i = 0;
3587 for (position, delta, selection_id, pair) in new_autoclose_regions {
3588 let position = position.to_offset(&map.buffer_snapshot) + delta;
3589 let start = map.buffer_snapshot.anchor_before(position);
3590 let end = map.buffer_snapshot.anchor_after(position);
3591 while let Some(existing_state) = this.autoclose_regions.get(i) {
3592 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3593 Ordering::Less => i += 1,
3594 Ordering::Greater => break,
3595 Ordering::Equal => {
3596 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3597 Ordering::Less => i += 1,
3598 Ordering::Equal => break,
3599 Ordering::Greater => break,
3600 }
3601 }
3602 }
3603 }
3604 this.autoclose_regions.insert(
3605 i,
3606 AutocloseRegion {
3607 selection_id,
3608 range: start..end,
3609 pair,
3610 },
3611 );
3612 }
3613
3614 let had_active_inline_completion = this.has_active_inline_completion();
3615 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3616 s.select(new_selections)
3617 });
3618
3619 if !bracket_inserted {
3620 if let Some(on_type_format_task) =
3621 this.trigger_on_type_formatting(text.to_string(), window, cx)
3622 {
3623 on_type_format_task.detach_and_log_err(cx);
3624 }
3625 }
3626
3627 let editor_settings = EditorSettings::get_global(cx);
3628 if bracket_inserted
3629 && (editor_settings.auto_signature_help
3630 || editor_settings.show_signature_help_after_edits)
3631 {
3632 this.show_signature_help(&ShowSignatureHelp, window, cx);
3633 }
3634
3635 let trigger_in_words =
3636 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3637 if this.hard_wrap.is_some() {
3638 let latest: Range<Point> = this.selections.newest(cx).range();
3639 if latest.is_empty()
3640 && this
3641 .buffer()
3642 .read(cx)
3643 .snapshot(cx)
3644 .line_len(MultiBufferRow(latest.start.row))
3645 == latest.start.column
3646 {
3647 this.rewrap_impl(
3648 RewrapOptions {
3649 override_language_settings: true,
3650 preserve_existing_whitespace: true,
3651 },
3652 cx,
3653 )
3654 }
3655 }
3656 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3657 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3658 this.refresh_inline_completion(true, false, window, cx);
3659 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3660 });
3661 }
3662
3663 fn find_possible_emoji_shortcode_at_position(
3664 snapshot: &MultiBufferSnapshot,
3665 position: Point,
3666 ) -> Option<String> {
3667 let mut chars = Vec::new();
3668 let mut found_colon = false;
3669 for char in snapshot.reversed_chars_at(position).take(100) {
3670 // Found a possible emoji shortcode in the middle of the buffer
3671 if found_colon {
3672 if char.is_whitespace() {
3673 chars.reverse();
3674 return Some(chars.iter().collect());
3675 }
3676 // If the previous character is not a whitespace, we are in the middle of a word
3677 // and we only want to complete the shortcode if the word is made up of other emojis
3678 let mut containing_word = String::new();
3679 for ch in snapshot
3680 .reversed_chars_at(position)
3681 .skip(chars.len() + 1)
3682 .take(100)
3683 {
3684 if ch.is_whitespace() {
3685 break;
3686 }
3687 containing_word.push(ch);
3688 }
3689 let containing_word = containing_word.chars().rev().collect::<String>();
3690 if util::word_consists_of_emojis(containing_word.as_str()) {
3691 chars.reverse();
3692 return Some(chars.iter().collect());
3693 }
3694 }
3695
3696 if char.is_whitespace() || !char.is_ascii() {
3697 return None;
3698 }
3699 if char == ':' {
3700 found_colon = true;
3701 } else {
3702 chars.push(char);
3703 }
3704 }
3705 // Found a possible emoji shortcode at the beginning of the buffer
3706 chars.reverse();
3707 Some(chars.iter().collect())
3708 }
3709
3710 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3712 self.transact(window, cx, |this, window, cx| {
3713 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3714 let selections = this.selections.all::<usize>(cx);
3715 let multi_buffer = this.buffer.read(cx);
3716 let buffer = multi_buffer.snapshot(cx);
3717 selections
3718 .iter()
3719 .map(|selection| {
3720 let start_point = selection.start.to_point(&buffer);
3721 let mut indent =
3722 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3723 indent.len = cmp::min(indent.len, start_point.column);
3724 let start = selection.start;
3725 let end = selection.end;
3726 let selection_is_empty = start == end;
3727 let language_scope = buffer.language_scope_at(start);
3728 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3729 &language_scope
3730 {
3731 let insert_extra_newline =
3732 insert_extra_newline_brackets(&buffer, start..end, language)
3733 || insert_extra_newline_tree_sitter(&buffer, start..end);
3734
3735 // Comment extension on newline is allowed only for cursor selections
3736 let comment_delimiter = maybe!({
3737 if !selection_is_empty {
3738 return None;
3739 }
3740
3741 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3742 return None;
3743 }
3744
3745 let delimiters = language.line_comment_prefixes();
3746 let max_len_of_delimiter =
3747 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3748 let (snapshot, range) =
3749 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3750
3751 let mut index_of_first_non_whitespace = 0;
3752 let comment_candidate = snapshot
3753 .chars_for_range(range)
3754 .skip_while(|c| {
3755 let should_skip = c.is_whitespace();
3756 if should_skip {
3757 index_of_first_non_whitespace += 1;
3758 }
3759 should_skip
3760 })
3761 .take(max_len_of_delimiter)
3762 .collect::<String>();
3763 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3764 comment_candidate.starts_with(comment_prefix.as_ref())
3765 })?;
3766 let cursor_is_placed_after_comment_marker =
3767 index_of_first_non_whitespace + comment_prefix.len()
3768 <= start_point.column as usize;
3769 if cursor_is_placed_after_comment_marker {
3770 Some(comment_prefix.clone())
3771 } else {
3772 None
3773 }
3774 });
3775 (comment_delimiter, insert_extra_newline)
3776 } else {
3777 (None, false)
3778 };
3779
3780 let capacity_for_delimiter = comment_delimiter
3781 .as_deref()
3782 .map(str::len)
3783 .unwrap_or_default();
3784 let mut new_text =
3785 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3786 new_text.push('\n');
3787 new_text.extend(indent.chars());
3788 if let Some(delimiter) = &comment_delimiter {
3789 new_text.push_str(delimiter);
3790 }
3791 if insert_extra_newline {
3792 new_text = new_text.repeat(2);
3793 }
3794
3795 let anchor = buffer.anchor_after(end);
3796 let new_selection = selection.map(|_| anchor);
3797 (
3798 (start..end, new_text),
3799 (insert_extra_newline, new_selection),
3800 )
3801 })
3802 .unzip()
3803 };
3804
3805 this.edit_with_autoindent(edits, cx);
3806 let buffer = this.buffer.read(cx).snapshot(cx);
3807 let new_selections = selection_fixup_info
3808 .into_iter()
3809 .map(|(extra_newline_inserted, new_selection)| {
3810 let mut cursor = new_selection.end.to_point(&buffer);
3811 if extra_newline_inserted {
3812 cursor.row -= 1;
3813 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3814 }
3815 new_selection.map(|_| cursor)
3816 })
3817 .collect();
3818
3819 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3820 s.select(new_selections)
3821 });
3822 this.refresh_inline_completion(true, false, window, cx);
3823 });
3824 }
3825
3826 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3827 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3828
3829 let buffer = self.buffer.read(cx);
3830 let snapshot = buffer.snapshot(cx);
3831
3832 let mut edits = Vec::new();
3833 let mut rows = Vec::new();
3834
3835 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3836 let cursor = selection.head();
3837 let row = cursor.row;
3838
3839 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3840
3841 let newline = "\n".to_string();
3842 edits.push((start_of_line..start_of_line, newline));
3843
3844 rows.push(row + rows_inserted as u32);
3845 }
3846
3847 self.transact(window, cx, |editor, window, cx| {
3848 editor.edit(edits, cx);
3849
3850 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3851 let mut index = 0;
3852 s.move_cursors_with(|map, _, _| {
3853 let row = rows[index];
3854 index += 1;
3855
3856 let point = Point::new(row, 0);
3857 let boundary = map.next_line_boundary(point).1;
3858 let clipped = map.clip_point(boundary, Bias::Left);
3859
3860 (clipped, SelectionGoal::None)
3861 });
3862 });
3863
3864 let mut indent_edits = Vec::new();
3865 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3866 for row in rows {
3867 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3868 for (row, indent) in indents {
3869 if indent.len == 0 {
3870 continue;
3871 }
3872
3873 let text = match indent.kind {
3874 IndentKind::Space => " ".repeat(indent.len as usize),
3875 IndentKind::Tab => "\t".repeat(indent.len as usize),
3876 };
3877 let point = Point::new(row.0, 0);
3878 indent_edits.push((point..point, text));
3879 }
3880 }
3881 editor.edit(indent_edits, cx);
3882 });
3883 }
3884
3885 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3886 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3887
3888 let buffer = self.buffer.read(cx);
3889 let snapshot = buffer.snapshot(cx);
3890
3891 let mut edits = Vec::new();
3892 let mut rows = Vec::new();
3893 let mut rows_inserted = 0;
3894
3895 for selection in self.selections.all_adjusted(cx) {
3896 let cursor = selection.head();
3897 let row = cursor.row;
3898
3899 let point = Point::new(row + 1, 0);
3900 let start_of_line = snapshot.clip_point(point, Bias::Left);
3901
3902 let newline = "\n".to_string();
3903 edits.push((start_of_line..start_of_line, newline));
3904
3905 rows_inserted += 1;
3906 rows.push(row + rows_inserted);
3907 }
3908
3909 self.transact(window, cx, |editor, window, cx| {
3910 editor.edit(edits, cx);
3911
3912 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3913 let mut index = 0;
3914 s.move_cursors_with(|map, _, _| {
3915 let row = rows[index];
3916 index += 1;
3917
3918 let point = Point::new(row, 0);
3919 let boundary = map.next_line_boundary(point).1;
3920 let clipped = map.clip_point(boundary, Bias::Left);
3921
3922 (clipped, SelectionGoal::None)
3923 });
3924 });
3925
3926 let mut indent_edits = Vec::new();
3927 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3928 for row in rows {
3929 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3930 for (row, indent) in indents {
3931 if indent.len == 0 {
3932 continue;
3933 }
3934
3935 let text = match indent.kind {
3936 IndentKind::Space => " ".repeat(indent.len as usize),
3937 IndentKind::Tab => "\t".repeat(indent.len as usize),
3938 };
3939 let point = Point::new(row.0, 0);
3940 indent_edits.push((point..point, text));
3941 }
3942 }
3943 editor.edit(indent_edits, cx);
3944 });
3945 }
3946
3947 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3948 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3949 original_indent_columns: Vec::new(),
3950 });
3951 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3952 }
3953
3954 fn insert_with_autoindent_mode(
3955 &mut self,
3956 text: &str,
3957 autoindent_mode: Option<AutoindentMode>,
3958 window: &mut Window,
3959 cx: &mut Context<Self>,
3960 ) {
3961 if self.read_only(cx) {
3962 return;
3963 }
3964
3965 let text: Arc<str> = text.into();
3966 self.transact(window, cx, |this, window, cx| {
3967 let old_selections = this.selections.all_adjusted(cx);
3968 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3969 let anchors = {
3970 let snapshot = buffer.read(cx);
3971 old_selections
3972 .iter()
3973 .map(|s| {
3974 let anchor = snapshot.anchor_after(s.head());
3975 s.map(|_| anchor)
3976 })
3977 .collect::<Vec<_>>()
3978 };
3979 buffer.edit(
3980 old_selections
3981 .iter()
3982 .map(|s| (s.start..s.end, text.clone())),
3983 autoindent_mode,
3984 cx,
3985 );
3986 anchors
3987 });
3988
3989 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3990 s.select_anchors(selection_anchors);
3991 });
3992
3993 cx.notify();
3994 });
3995 }
3996
3997 fn trigger_completion_on_input(
3998 &mut self,
3999 text: &str,
4000 trigger_in_words: bool,
4001 window: &mut Window,
4002 cx: &mut Context<Self>,
4003 ) {
4004 let ignore_completion_provider = self
4005 .context_menu
4006 .borrow()
4007 .as_ref()
4008 .map(|menu| match menu {
4009 CodeContextMenu::Completions(completions_menu) => {
4010 completions_menu.ignore_completion_provider
4011 }
4012 CodeContextMenu::CodeActions(_) => false,
4013 })
4014 .unwrap_or(false);
4015
4016 if ignore_completion_provider {
4017 self.show_word_completions(&ShowWordCompletions, window, cx);
4018 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4019 self.show_completions(
4020 &ShowCompletions {
4021 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4022 },
4023 window,
4024 cx,
4025 );
4026 } else {
4027 self.hide_context_menu(window, cx);
4028 }
4029 }
4030
4031 fn is_completion_trigger(
4032 &self,
4033 text: &str,
4034 trigger_in_words: bool,
4035 cx: &mut Context<Self>,
4036 ) -> bool {
4037 let position = self.selections.newest_anchor().head();
4038 let multibuffer = self.buffer.read(cx);
4039 let Some(buffer) = position
4040 .buffer_id
4041 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4042 else {
4043 return false;
4044 };
4045
4046 if let Some(completion_provider) = &self.completion_provider {
4047 completion_provider.is_completion_trigger(
4048 &buffer,
4049 position.text_anchor,
4050 text,
4051 trigger_in_words,
4052 cx,
4053 )
4054 } else {
4055 false
4056 }
4057 }
4058
4059 /// If any empty selections is touching the start of its innermost containing autoclose
4060 /// region, expand it to select the brackets.
4061 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4062 let selections = self.selections.all::<usize>(cx);
4063 let buffer = self.buffer.read(cx).read(cx);
4064 let new_selections = self
4065 .selections_with_autoclose_regions(selections, &buffer)
4066 .map(|(mut selection, region)| {
4067 if !selection.is_empty() {
4068 return selection;
4069 }
4070
4071 if let Some(region) = region {
4072 let mut range = region.range.to_offset(&buffer);
4073 if selection.start == range.start && range.start >= region.pair.start.len() {
4074 range.start -= region.pair.start.len();
4075 if buffer.contains_str_at(range.start, ®ion.pair.start)
4076 && buffer.contains_str_at(range.end, ®ion.pair.end)
4077 {
4078 range.end += region.pair.end.len();
4079 selection.start = range.start;
4080 selection.end = range.end;
4081
4082 return selection;
4083 }
4084 }
4085 }
4086
4087 let always_treat_brackets_as_autoclosed = buffer
4088 .language_settings_at(selection.start, cx)
4089 .always_treat_brackets_as_autoclosed;
4090
4091 if !always_treat_brackets_as_autoclosed {
4092 return selection;
4093 }
4094
4095 if let Some(scope) = buffer.language_scope_at(selection.start) {
4096 for (pair, enabled) in scope.brackets() {
4097 if !enabled || !pair.close {
4098 continue;
4099 }
4100
4101 if buffer.contains_str_at(selection.start, &pair.end) {
4102 let pair_start_len = pair.start.len();
4103 if buffer.contains_str_at(
4104 selection.start.saturating_sub(pair_start_len),
4105 &pair.start,
4106 ) {
4107 selection.start -= pair_start_len;
4108 selection.end += pair.end.len();
4109
4110 return selection;
4111 }
4112 }
4113 }
4114 }
4115
4116 selection
4117 })
4118 .collect();
4119
4120 drop(buffer);
4121 self.change_selections(None, window, cx, |selections| {
4122 selections.select(new_selections)
4123 });
4124 }
4125
4126 /// Iterate the given selections, and for each one, find the smallest surrounding
4127 /// autoclose region. This uses the ordering of the selections and the autoclose
4128 /// regions to avoid repeated comparisons.
4129 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4130 &'a self,
4131 selections: impl IntoIterator<Item = Selection<D>>,
4132 buffer: &'a MultiBufferSnapshot,
4133 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4134 let mut i = 0;
4135 let mut regions = self.autoclose_regions.as_slice();
4136 selections.into_iter().map(move |selection| {
4137 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4138
4139 let mut enclosing = None;
4140 while let Some(pair_state) = regions.get(i) {
4141 if pair_state.range.end.to_offset(buffer) < range.start {
4142 regions = ®ions[i + 1..];
4143 i = 0;
4144 } else if pair_state.range.start.to_offset(buffer) > range.end {
4145 break;
4146 } else {
4147 if pair_state.selection_id == selection.id {
4148 enclosing = Some(pair_state);
4149 }
4150 i += 1;
4151 }
4152 }
4153
4154 (selection, enclosing)
4155 })
4156 }
4157
4158 /// Remove any autoclose regions that no longer contain their selection.
4159 fn invalidate_autoclose_regions(
4160 &mut self,
4161 mut selections: &[Selection<Anchor>],
4162 buffer: &MultiBufferSnapshot,
4163 ) {
4164 self.autoclose_regions.retain(|state| {
4165 let mut i = 0;
4166 while let Some(selection) = selections.get(i) {
4167 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4168 selections = &selections[1..];
4169 continue;
4170 }
4171 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4172 break;
4173 }
4174 if selection.id == state.selection_id {
4175 return true;
4176 } else {
4177 i += 1;
4178 }
4179 }
4180 false
4181 });
4182 }
4183
4184 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4185 let offset = position.to_offset(buffer);
4186 let (word_range, kind) = buffer.surrounding_word(offset, true);
4187 if offset > word_range.start && kind == Some(CharKind::Word) {
4188 Some(
4189 buffer
4190 .text_for_range(word_range.start..offset)
4191 .collect::<String>(),
4192 )
4193 } else {
4194 None
4195 }
4196 }
4197
4198 pub fn toggle_inlay_hints(
4199 &mut self,
4200 _: &ToggleInlayHints,
4201 _: &mut Window,
4202 cx: &mut Context<Self>,
4203 ) {
4204 self.refresh_inlay_hints(
4205 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4206 cx,
4207 );
4208 }
4209
4210 pub fn inlay_hints_enabled(&self) -> bool {
4211 self.inlay_hint_cache.enabled
4212 }
4213
4214 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4215 if self.semantics_provider.is_none() || !self.mode.is_full() {
4216 return;
4217 }
4218
4219 let reason_description = reason.description();
4220 let ignore_debounce = matches!(
4221 reason,
4222 InlayHintRefreshReason::SettingsChange(_)
4223 | InlayHintRefreshReason::Toggle(_)
4224 | InlayHintRefreshReason::ExcerptsRemoved(_)
4225 | InlayHintRefreshReason::ModifiersChanged(_)
4226 );
4227 let (invalidate_cache, required_languages) = match reason {
4228 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4229 match self.inlay_hint_cache.modifiers_override(enabled) {
4230 Some(enabled) => {
4231 if enabled {
4232 (InvalidationStrategy::RefreshRequested, None)
4233 } else {
4234 self.splice_inlays(
4235 &self
4236 .visible_inlay_hints(cx)
4237 .iter()
4238 .map(|inlay| inlay.id)
4239 .collect::<Vec<InlayId>>(),
4240 Vec::new(),
4241 cx,
4242 );
4243 return;
4244 }
4245 }
4246 None => return,
4247 }
4248 }
4249 InlayHintRefreshReason::Toggle(enabled) => {
4250 if self.inlay_hint_cache.toggle(enabled) {
4251 if enabled {
4252 (InvalidationStrategy::RefreshRequested, None)
4253 } else {
4254 self.splice_inlays(
4255 &self
4256 .visible_inlay_hints(cx)
4257 .iter()
4258 .map(|inlay| inlay.id)
4259 .collect::<Vec<InlayId>>(),
4260 Vec::new(),
4261 cx,
4262 );
4263 return;
4264 }
4265 } else {
4266 return;
4267 }
4268 }
4269 InlayHintRefreshReason::SettingsChange(new_settings) => {
4270 match self.inlay_hint_cache.update_settings(
4271 &self.buffer,
4272 new_settings,
4273 self.visible_inlay_hints(cx),
4274 cx,
4275 ) {
4276 ControlFlow::Break(Some(InlaySplice {
4277 to_remove,
4278 to_insert,
4279 })) => {
4280 self.splice_inlays(&to_remove, to_insert, cx);
4281 return;
4282 }
4283 ControlFlow::Break(None) => return,
4284 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4285 }
4286 }
4287 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4288 if let Some(InlaySplice {
4289 to_remove,
4290 to_insert,
4291 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4292 {
4293 self.splice_inlays(&to_remove, to_insert, cx);
4294 }
4295 self.display_map.update(cx, |display_map, _| {
4296 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4297 });
4298 return;
4299 }
4300 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4301 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4302 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4303 }
4304 InlayHintRefreshReason::RefreshRequested => {
4305 (InvalidationStrategy::RefreshRequested, None)
4306 }
4307 };
4308
4309 if let Some(InlaySplice {
4310 to_remove,
4311 to_insert,
4312 }) = self.inlay_hint_cache.spawn_hint_refresh(
4313 reason_description,
4314 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4315 invalidate_cache,
4316 ignore_debounce,
4317 cx,
4318 ) {
4319 self.splice_inlays(&to_remove, to_insert, cx);
4320 }
4321 }
4322
4323 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4324 self.display_map
4325 .read(cx)
4326 .current_inlays()
4327 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4328 .cloned()
4329 .collect()
4330 }
4331
4332 pub fn excerpts_for_inlay_hints_query(
4333 &self,
4334 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4335 cx: &mut Context<Editor>,
4336 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4337 let Some(project) = self.project.as_ref() else {
4338 return HashMap::default();
4339 };
4340 let project = project.read(cx);
4341 let multi_buffer = self.buffer().read(cx);
4342 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4343 let multi_buffer_visible_start = self
4344 .scroll_manager
4345 .anchor()
4346 .anchor
4347 .to_point(&multi_buffer_snapshot);
4348 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4349 multi_buffer_visible_start
4350 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4351 Bias::Left,
4352 );
4353 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4354 multi_buffer_snapshot
4355 .range_to_buffer_ranges(multi_buffer_visible_range)
4356 .into_iter()
4357 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4358 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4359 let buffer_file = project::File::from_dyn(buffer.file())?;
4360 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4361 let worktree_entry = buffer_worktree
4362 .read(cx)
4363 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4364 if worktree_entry.is_ignored {
4365 return None;
4366 }
4367
4368 let language = buffer.language()?;
4369 if let Some(restrict_to_languages) = restrict_to_languages {
4370 if !restrict_to_languages.contains(language) {
4371 return None;
4372 }
4373 }
4374 Some((
4375 excerpt_id,
4376 (
4377 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4378 buffer.version().clone(),
4379 excerpt_visible_range,
4380 ),
4381 ))
4382 })
4383 .collect()
4384 }
4385
4386 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4387 TextLayoutDetails {
4388 text_system: window.text_system().clone(),
4389 editor_style: self.style.clone().unwrap(),
4390 rem_size: window.rem_size(),
4391 scroll_anchor: self.scroll_manager.anchor(),
4392 visible_rows: self.visible_line_count(),
4393 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4394 }
4395 }
4396
4397 pub fn splice_inlays(
4398 &self,
4399 to_remove: &[InlayId],
4400 to_insert: Vec<Inlay>,
4401 cx: &mut Context<Self>,
4402 ) {
4403 self.display_map.update(cx, |display_map, cx| {
4404 display_map.splice_inlays(to_remove, to_insert, cx)
4405 });
4406 cx.notify();
4407 }
4408
4409 fn trigger_on_type_formatting(
4410 &self,
4411 input: String,
4412 window: &mut Window,
4413 cx: &mut Context<Self>,
4414 ) -> Option<Task<Result<()>>> {
4415 if input.len() != 1 {
4416 return None;
4417 }
4418
4419 let project = self.project.as_ref()?;
4420 let position = self.selections.newest_anchor().head();
4421 let (buffer, buffer_position) = self
4422 .buffer
4423 .read(cx)
4424 .text_anchor_for_position(position, cx)?;
4425
4426 let settings = language_settings::language_settings(
4427 buffer
4428 .read(cx)
4429 .language_at(buffer_position)
4430 .map(|l| l.name()),
4431 buffer.read(cx).file(),
4432 cx,
4433 );
4434 if !settings.use_on_type_format {
4435 return None;
4436 }
4437
4438 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4439 // hence we do LSP request & edit on host side only — add formats to host's history.
4440 let push_to_lsp_host_history = true;
4441 // If this is not the host, append its history with new edits.
4442 let push_to_client_history = project.read(cx).is_via_collab();
4443
4444 let on_type_formatting = project.update(cx, |project, cx| {
4445 project.on_type_format(
4446 buffer.clone(),
4447 buffer_position,
4448 input,
4449 push_to_lsp_host_history,
4450 cx,
4451 )
4452 });
4453 Some(cx.spawn_in(window, async move |editor, cx| {
4454 if let Some(transaction) = on_type_formatting.await? {
4455 if push_to_client_history {
4456 buffer
4457 .update(cx, |buffer, _| {
4458 buffer.push_transaction(transaction, Instant::now());
4459 buffer.finalize_last_transaction();
4460 })
4461 .ok();
4462 }
4463 editor.update(cx, |editor, cx| {
4464 editor.refresh_document_highlights(cx);
4465 })?;
4466 }
4467 Ok(())
4468 }))
4469 }
4470
4471 pub fn show_word_completions(
4472 &mut self,
4473 _: &ShowWordCompletions,
4474 window: &mut Window,
4475 cx: &mut Context<Self>,
4476 ) {
4477 self.open_completions_menu(true, None, window, cx);
4478 }
4479
4480 pub fn show_completions(
4481 &mut self,
4482 options: &ShowCompletions,
4483 window: &mut Window,
4484 cx: &mut Context<Self>,
4485 ) {
4486 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4487 }
4488
4489 fn open_completions_menu(
4490 &mut self,
4491 ignore_completion_provider: bool,
4492 trigger: Option<&str>,
4493 window: &mut Window,
4494 cx: &mut Context<Self>,
4495 ) {
4496 if self.pending_rename.is_some() {
4497 return;
4498 }
4499 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4500 return;
4501 }
4502
4503 let position = self.selections.newest_anchor().head();
4504 if position.diff_base_anchor.is_some() {
4505 return;
4506 }
4507 let (buffer, buffer_position) =
4508 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4509 output
4510 } else {
4511 return;
4512 };
4513 let buffer_snapshot = buffer.read(cx).snapshot();
4514 let show_completion_documentation = buffer_snapshot
4515 .settings_at(buffer_position, cx)
4516 .show_completion_documentation;
4517
4518 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4519
4520 let trigger_kind = match trigger {
4521 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4522 CompletionTriggerKind::TRIGGER_CHARACTER
4523 }
4524 _ => CompletionTriggerKind::INVOKED,
4525 };
4526 let completion_context = CompletionContext {
4527 trigger_character: trigger.and_then(|trigger| {
4528 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4529 Some(String::from(trigger))
4530 } else {
4531 None
4532 }
4533 }),
4534 trigger_kind,
4535 };
4536
4537 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4538 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4539 let word_to_exclude = buffer_snapshot
4540 .text_for_range(old_range.clone())
4541 .collect::<String>();
4542 (
4543 buffer_snapshot.anchor_before(old_range.start)
4544 ..buffer_snapshot.anchor_after(old_range.end),
4545 Some(word_to_exclude),
4546 )
4547 } else {
4548 (buffer_position..buffer_position, None)
4549 };
4550
4551 let completion_settings = language_settings(
4552 buffer_snapshot
4553 .language_at(buffer_position)
4554 .map(|language| language.name()),
4555 buffer_snapshot.file(),
4556 cx,
4557 )
4558 .completions;
4559
4560 // The document can be large, so stay in reasonable bounds when searching for words,
4561 // otherwise completion pop-up might be slow to appear.
4562 const WORD_LOOKUP_ROWS: u32 = 5_000;
4563 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4564 let min_word_search = buffer_snapshot.clip_point(
4565 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4566 Bias::Left,
4567 );
4568 let max_word_search = buffer_snapshot.clip_point(
4569 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4570 Bias::Right,
4571 );
4572 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4573 ..buffer_snapshot.point_to_offset(max_word_search);
4574
4575 let provider = self
4576 .completion_provider
4577 .as_ref()
4578 .filter(|_| !ignore_completion_provider);
4579 let skip_digits = query
4580 .as_ref()
4581 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4582
4583 let (mut words, provided_completions) = match provider {
4584 Some(provider) => {
4585 let completions = provider.completions(
4586 position.excerpt_id,
4587 &buffer,
4588 buffer_position,
4589 completion_context,
4590 window,
4591 cx,
4592 );
4593
4594 let words = match completion_settings.words {
4595 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4596 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4597 .background_spawn(async move {
4598 buffer_snapshot.words_in_range(WordsQuery {
4599 fuzzy_contents: None,
4600 range: word_search_range,
4601 skip_digits,
4602 })
4603 }),
4604 };
4605
4606 (words, completions)
4607 }
4608 None => (
4609 cx.background_spawn(async move {
4610 buffer_snapshot.words_in_range(WordsQuery {
4611 fuzzy_contents: None,
4612 range: word_search_range,
4613 skip_digits,
4614 })
4615 }),
4616 Task::ready(Ok(None)),
4617 ),
4618 };
4619
4620 let sort_completions = provider
4621 .as_ref()
4622 .map_or(false, |provider| provider.sort_completions());
4623
4624 let filter_completions = provider
4625 .as_ref()
4626 .map_or(true, |provider| provider.filter_completions());
4627
4628 let id = post_inc(&mut self.next_completion_id);
4629 let task = cx.spawn_in(window, async move |editor, cx| {
4630 async move {
4631 editor.update(cx, |this, _| {
4632 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4633 })?;
4634
4635 let mut completions = Vec::new();
4636 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4637 completions.extend(provided_completions);
4638 if completion_settings.words == WordsCompletionMode::Fallback {
4639 words = Task::ready(BTreeMap::default());
4640 }
4641 }
4642
4643 let mut words = words.await;
4644 if let Some(word_to_exclude) = &word_to_exclude {
4645 words.remove(word_to_exclude);
4646 }
4647 for lsp_completion in &completions {
4648 words.remove(&lsp_completion.new_text);
4649 }
4650 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4651 replace_range: old_range.clone(),
4652 new_text: word.clone(),
4653 label: CodeLabel::plain(word, None),
4654 icon_path: None,
4655 documentation: None,
4656 source: CompletionSource::BufferWord {
4657 word_range,
4658 resolved: false,
4659 },
4660 insert_text_mode: Some(InsertTextMode::AS_IS),
4661 confirm: None,
4662 }));
4663
4664 let menu = if completions.is_empty() {
4665 None
4666 } else {
4667 let mut menu = CompletionsMenu::new(
4668 id,
4669 sort_completions,
4670 show_completion_documentation,
4671 ignore_completion_provider,
4672 position,
4673 buffer.clone(),
4674 completions.into(),
4675 );
4676
4677 menu.filter(
4678 if filter_completions {
4679 query.as_deref()
4680 } else {
4681 None
4682 },
4683 cx.background_executor().clone(),
4684 )
4685 .await;
4686
4687 menu.visible().then_some(menu)
4688 };
4689
4690 editor.update_in(cx, |editor, window, cx| {
4691 match editor.context_menu.borrow().as_ref() {
4692 None => {}
4693 Some(CodeContextMenu::Completions(prev_menu)) => {
4694 if prev_menu.id > id {
4695 return;
4696 }
4697 }
4698 _ => return,
4699 }
4700
4701 if editor.focus_handle.is_focused(window) && menu.is_some() {
4702 let mut menu = menu.unwrap();
4703 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4704
4705 *editor.context_menu.borrow_mut() =
4706 Some(CodeContextMenu::Completions(menu));
4707
4708 if editor.show_edit_predictions_in_menu() {
4709 editor.update_visible_inline_completion(window, cx);
4710 } else {
4711 editor.discard_inline_completion(false, cx);
4712 }
4713
4714 cx.notify();
4715 } else if editor.completion_tasks.len() <= 1 {
4716 // If there are no more completion tasks and the last menu was
4717 // empty, we should hide it.
4718 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4719 // If it was already hidden and we don't show inline
4720 // completions in the menu, we should also show the
4721 // inline-completion when available.
4722 if was_hidden && editor.show_edit_predictions_in_menu() {
4723 editor.update_visible_inline_completion(window, cx);
4724 }
4725 }
4726 })?;
4727
4728 anyhow::Ok(())
4729 }
4730 .log_err()
4731 .await
4732 });
4733
4734 self.completion_tasks.push((id, task));
4735 }
4736
4737 #[cfg(feature = "test-support")]
4738 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4739 let menu = self.context_menu.borrow();
4740 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4741 let completions = menu.completions.borrow();
4742 Some(completions.to_vec())
4743 } else {
4744 None
4745 }
4746 }
4747
4748 pub fn confirm_completion(
4749 &mut self,
4750 action: &ConfirmCompletion,
4751 window: &mut Window,
4752 cx: &mut Context<Self>,
4753 ) -> Option<Task<Result<()>>> {
4754 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4755 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4756 }
4757
4758 pub fn confirm_completion_insert(
4759 &mut self,
4760 _: &ConfirmCompletionInsert,
4761 window: &mut Window,
4762 cx: &mut Context<Self>,
4763 ) -> Option<Task<Result<()>>> {
4764 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4765 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4766 }
4767
4768 pub fn confirm_completion_replace(
4769 &mut self,
4770 _: &ConfirmCompletionReplace,
4771 window: &mut Window,
4772 cx: &mut Context<Self>,
4773 ) -> Option<Task<Result<()>>> {
4774 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4775 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4776 }
4777
4778 pub fn compose_completion(
4779 &mut self,
4780 action: &ComposeCompletion,
4781 window: &mut Window,
4782 cx: &mut Context<Self>,
4783 ) -> Option<Task<Result<()>>> {
4784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4785 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4786 }
4787
4788 fn do_completion(
4789 &mut self,
4790 item_ix: Option<usize>,
4791 intent: CompletionIntent,
4792 window: &mut Window,
4793 cx: &mut Context<Editor>,
4794 ) -> Option<Task<Result<()>>> {
4795 use language::ToOffset as _;
4796
4797 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4798 else {
4799 return None;
4800 };
4801
4802 let candidate_id = {
4803 let entries = completions_menu.entries.borrow();
4804 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4805 if self.show_edit_predictions_in_menu() {
4806 self.discard_inline_completion(true, cx);
4807 }
4808 mat.candidate_id
4809 };
4810
4811 let buffer_handle = completions_menu.buffer;
4812 let completion = completions_menu
4813 .completions
4814 .borrow()
4815 .get(candidate_id)?
4816 .clone();
4817 cx.stop_propagation();
4818
4819 let snippet;
4820 let new_text;
4821 if completion.is_snippet() {
4822 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4823 new_text = snippet.as_ref().unwrap().text.clone();
4824 } else {
4825 snippet = None;
4826 new_text = completion.new_text.clone();
4827 };
4828
4829 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4830 let buffer = buffer_handle.read(cx);
4831 let snapshot = self.buffer.read(cx).snapshot(cx);
4832 let replace_range_multibuffer = {
4833 let excerpt = snapshot
4834 .excerpt_containing(self.selections.newest_anchor().range())
4835 .unwrap();
4836 let multibuffer_anchor = snapshot
4837 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4838 .unwrap()
4839 ..snapshot
4840 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4841 .unwrap();
4842 multibuffer_anchor.start.to_offset(&snapshot)
4843 ..multibuffer_anchor.end.to_offset(&snapshot)
4844 };
4845 let newest_anchor = self.selections.newest_anchor();
4846 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4847 return None;
4848 }
4849
4850 let old_text = buffer
4851 .text_for_range(replace_range.clone())
4852 .collect::<String>();
4853 let lookbehind = newest_anchor
4854 .start
4855 .text_anchor
4856 .to_offset(buffer)
4857 .saturating_sub(replace_range.start);
4858 let lookahead = replace_range
4859 .end
4860 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4861 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4862 let suffix = &old_text[lookbehind.min(old_text.len())..];
4863
4864 let selections = self.selections.all::<usize>(cx);
4865 let mut ranges = Vec::new();
4866 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4867
4868 for selection in &selections {
4869 let range = if selection.id == newest_anchor.id {
4870 replace_range_multibuffer.clone()
4871 } else {
4872 let mut range = selection.range();
4873
4874 // if prefix is present, don't duplicate it
4875 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4876 range.start = range.start.saturating_sub(lookbehind);
4877
4878 // if suffix is also present, mimic the newest cursor and replace it
4879 if selection.id != newest_anchor.id
4880 && snapshot.contains_str_at(range.end, suffix)
4881 {
4882 range.end += lookahead;
4883 }
4884 }
4885 range
4886 };
4887
4888 ranges.push(range);
4889
4890 if !self.linked_edit_ranges.is_empty() {
4891 let start_anchor = snapshot.anchor_before(selection.head());
4892 let end_anchor = snapshot.anchor_after(selection.tail());
4893 if let Some(ranges) = self
4894 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4895 {
4896 for (buffer, edits) in ranges {
4897 linked_edits
4898 .entry(buffer.clone())
4899 .or_default()
4900 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4901 }
4902 }
4903 }
4904 }
4905
4906 cx.emit(EditorEvent::InputHandled {
4907 utf16_range_to_replace: None,
4908 text: new_text.clone().into(),
4909 });
4910
4911 self.transact(window, cx, |this, window, cx| {
4912 if let Some(mut snippet) = snippet {
4913 snippet.text = new_text.to_string();
4914 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4915 } else {
4916 this.buffer.update(cx, |buffer, cx| {
4917 let auto_indent = match completion.insert_text_mode {
4918 Some(InsertTextMode::AS_IS) => None,
4919 _ => this.autoindent_mode.clone(),
4920 };
4921 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
4922 buffer.edit(edits, auto_indent, cx);
4923 });
4924 }
4925 for (buffer, edits) in linked_edits {
4926 buffer.update(cx, |buffer, cx| {
4927 let snapshot = buffer.snapshot();
4928 let edits = edits
4929 .into_iter()
4930 .map(|(range, text)| {
4931 use text::ToPoint as TP;
4932 let end_point = TP::to_point(&range.end, &snapshot);
4933 let start_point = TP::to_point(&range.start, &snapshot);
4934 (start_point..end_point, text)
4935 })
4936 .sorted_by_key(|(range, _)| range.start);
4937 buffer.edit(edits, None, cx);
4938 })
4939 }
4940
4941 this.refresh_inline_completion(true, false, window, cx);
4942 });
4943
4944 let show_new_completions_on_confirm = completion
4945 .confirm
4946 .as_ref()
4947 .map_or(false, |confirm| confirm(intent, window, cx));
4948 if show_new_completions_on_confirm {
4949 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4950 }
4951
4952 let provider = self.completion_provider.as_ref()?;
4953 drop(completion);
4954 let apply_edits = provider.apply_additional_edits_for_completion(
4955 buffer_handle,
4956 completions_menu.completions.clone(),
4957 candidate_id,
4958 true,
4959 cx,
4960 );
4961
4962 let editor_settings = EditorSettings::get_global(cx);
4963 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4964 // After the code completion is finished, users often want to know what signatures are needed.
4965 // so we should automatically call signature_help
4966 self.show_signature_help(&ShowSignatureHelp, window, cx);
4967 }
4968
4969 Some(cx.foreground_executor().spawn(async move {
4970 apply_edits.await?;
4971 Ok(())
4972 }))
4973 }
4974
4975 pub fn toggle_code_actions(
4976 &mut self,
4977 action: &ToggleCodeActions,
4978 window: &mut Window,
4979 cx: &mut Context<Self>,
4980 ) {
4981 let mut context_menu = self.context_menu.borrow_mut();
4982 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4983 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4984 // Toggle if we're selecting the same one
4985 *context_menu = None;
4986 cx.notify();
4987 return;
4988 } else {
4989 // Otherwise, clear it and start a new one
4990 *context_menu = None;
4991 cx.notify();
4992 }
4993 }
4994 drop(context_menu);
4995 let snapshot = self.snapshot(window, cx);
4996 let deployed_from_indicator = action.deployed_from_indicator;
4997 let mut task = self.code_actions_task.take();
4998 let action = action.clone();
4999 cx.spawn_in(window, async move |editor, cx| {
5000 while let Some(prev_task) = task {
5001 prev_task.await.log_err();
5002 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5003 }
5004
5005 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5006 if editor.focus_handle.is_focused(window) {
5007 let multibuffer_point = action
5008 .deployed_from_indicator
5009 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5010 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5011 let (buffer, buffer_row) = snapshot
5012 .buffer_snapshot
5013 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5014 .and_then(|(buffer_snapshot, range)| {
5015 editor
5016 .buffer
5017 .read(cx)
5018 .buffer(buffer_snapshot.remote_id())
5019 .map(|buffer| (buffer, range.start.row))
5020 })?;
5021 let (_, code_actions) = editor
5022 .available_code_actions
5023 .clone()
5024 .and_then(|(location, code_actions)| {
5025 let snapshot = location.buffer.read(cx).snapshot();
5026 let point_range = location.range.to_point(&snapshot);
5027 let point_range = point_range.start.row..=point_range.end.row;
5028 if point_range.contains(&buffer_row) {
5029 Some((location, code_actions))
5030 } else {
5031 None
5032 }
5033 })
5034 .unzip();
5035 let buffer_id = buffer.read(cx).remote_id();
5036 let tasks = editor
5037 .tasks
5038 .get(&(buffer_id, buffer_row))
5039 .map(|t| Arc::new(t.to_owned()));
5040 if tasks.is_none() && code_actions.is_none() {
5041 return None;
5042 }
5043
5044 editor.completion_tasks.clear();
5045 editor.discard_inline_completion(false, cx);
5046 let task_context =
5047 tasks
5048 .as_ref()
5049 .zip(editor.project.clone())
5050 .map(|(tasks, project)| {
5051 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5052 });
5053
5054 let debugger_flag = cx.has_flag::<Debugger>();
5055
5056 Some(cx.spawn_in(window, async move |editor, cx| {
5057 let task_context = match task_context {
5058 Some(task_context) => task_context.await,
5059 None => None,
5060 };
5061 let resolved_tasks =
5062 tasks
5063 .zip(task_context)
5064 .map(|(tasks, task_context)| ResolvedTasks {
5065 templates: tasks.resolve(&task_context).collect(),
5066 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5067 multibuffer_point.row,
5068 tasks.column,
5069 )),
5070 });
5071 let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
5072 tasks
5073 .templates
5074 .iter()
5075 .filter(|task| {
5076 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
5077 debugger_flag
5078 } else {
5079 true
5080 }
5081 })
5082 .count()
5083 == 1
5084 }) && code_actions
5085 .as_ref()
5086 .map_or(true, |actions| actions.is_empty());
5087 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5088 *editor.context_menu.borrow_mut() =
5089 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5090 buffer,
5091 actions: CodeActionContents::new(
5092 resolved_tasks,
5093 code_actions,
5094 cx,
5095 ),
5096 selected_item: Default::default(),
5097 scroll_handle: UniformListScrollHandle::default(),
5098 deployed_from_indicator,
5099 }));
5100 if spawn_straight_away {
5101 if let Some(task) = editor.confirm_code_action(
5102 &ConfirmCodeAction { item_ix: Some(0) },
5103 window,
5104 cx,
5105 ) {
5106 cx.notify();
5107 return task;
5108 }
5109 }
5110 cx.notify();
5111 Task::ready(Ok(()))
5112 }) {
5113 task.await
5114 } else {
5115 Ok(())
5116 }
5117 }))
5118 } else {
5119 Some(Task::ready(Ok(())))
5120 }
5121 })?;
5122 if let Some(task) = spawned_test_task {
5123 task.await?;
5124 }
5125
5126 Ok::<_, anyhow::Error>(())
5127 })
5128 .detach_and_log_err(cx);
5129 }
5130
5131 pub fn confirm_code_action(
5132 &mut self,
5133 action: &ConfirmCodeAction,
5134 window: &mut Window,
5135 cx: &mut Context<Self>,
5136 ) -> Option<Task<Result<()>>> {
5137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5138
5139 let actions_menu =
5140 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5141 menu
5142 } else {
5143 return None;
5144 };
5145
5146 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5147 let action = actions_menu.actions.get(action_ix)?;
5148 let title = action.label();
5149 let buffer = actions_menu.buffer;
5150 let workspace = self.workspace()?;
5151
5152 match action {
5153 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5154 match resolved_task.task_type() {
5155 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5156 workspace.schedule_resolved_task(
5157 task_source_kind,
5158 resolved_task,
5159 false,
5160 window,
5161 cx,
5162 );
5163
5164 Some(Task::ready(Ok(())))
5165 }),
5166 task::TaskType::Debug(_) => {
5167 workspace.update(cx, |workspace, cx| {
5168 workspace.schedule_debug_task(resolved_task, window, cx);
5169 });
5170 Some(Task::ready(Ok(())))
5171 }
5172 }
5173 }
5174 CodeActionsItem::CodeAction {
5175 excerpt_id,
5176 action,
5177 provider,
5178 } => {
5179 let apply_code_action =
5180 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5181 let workspace = workspace.downgrade();
5182 Some(cx.spawn_in(window, async move |editor, cx| {
5183 let project_transaction = apply_code_action.await?;
5184 Self::open_project_transaction(
5185 &editor,
5186 workspace,
5187 project_transaction,
5188 title,
5189 cx,
5190 )
5191 .await
5192 }))
5193 }
5194 }
5195 }
5196
5197 pub async fn open_project_transaction(
5198 this: &WeakEntity<Editor>,
5199 workspace: WeakEntity<Workspace>,
5200 transaction: ProjectTransaction,
5201 title: String,
5202 cx: &mut AsyncWindowContext,
5203 ) -> Result<()> {
5204 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5205 cx.update(|_, cx| {
5206 entries.sort_unstable_by_key(|(buffer, _)| {
5207 buffer.read(cx).file().map(|f| f.path().clone())
5208 });
5209 })?;
5210
5211 // If the project transaction's edits are all contained within this editor, then
5212 // avoid opening a new editor to display them.
5213
5214 if let Some((buffer, transaction)) = entries.first() {
5215 if entries.len() == 1 {
5216 let excerpt = this.update(cx, |editor, cx| {
5217 editor
5218 .buffer()
5219 .read(cx)
5220 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5221 })?;
5222 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5223 if excerpted_buffer == *buffer {
5224 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5225 let excerpt_range = excerpt_range.to_offset(buffer);
5226 buffer
5227 .edited_ranges_for_transaction::<usize>(transaction)
5228 .all(|range| {
5229 excerpt_range.start <= range.start
5230 && excerpt_range.end >= range.end
5231 })
5232 })?;
5233
5234 if all_edits_within_excerpt {
5235 return Ok(());
5236 }
5237 }
5238 }
5239 }
5240 } else {
5241 return Ok(());
5242 }
5243
5244 let mut ranges_to_highlight = Vec::new();
5245 let excerpt_buffer = cx.new(|cx| {
5246 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5247 for (buffer_handle, transaction) in &entries {
5248 let edited_ranges = buffer_handle
5249 .read(cx)
5250 .edited_ranges_for_transaction::<Point>(transaction)
5251 .collect::<Vec<_>>();
5252 let (ranges, _) = multibuffer.set_excerpts_for_path(
5253 PathKey::for_buffer(buffer_handle, cx),
5254 buffer_handle.clone(),
5255 edited_ranges,
5256 DEFAULT_MULTIBUFFER_CONTEXT,
5257 cx,
5258 );
5259
5260 ranges_to_highlight.extend(ranges);
5261 }
5262 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5263 multibuffer
5264 })?;
5265
5266 workspace.update_in(cx, |workspace, window, cx| {
5267 let project = workspace.project().clone();
5268 let editor =
5269 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5270 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5271 editor.update(cx, |editor, cx| {
5272 editor.highlight_background::<Self>(
5273 &ranges_to_highlight,
5274 |theme| theme.editor_highlighted_line_background,
5275 cx,
5276 );
5277 });
5278 })?;
5279
5280 Ok(())
5281 }
5282
5283 pub fn clear_code_action_providers(&mut self) {
5284 self.code_action_providers.clear();
5285 self.available_code_actions.take();
5286 }
5287
5288 pub fn add_code_action_provider(
5289 &mut self,
5290 provider: Rc<dyn CodeActionProvider>,
5291 window: &mut Window,
5292 cx: &mut Context<Self>,
5293 ) {
5294 if self
5295 .code_action_providers
5296 .iter()
5297 .any(|existing_provider| existing_provider.id() == provider.id())
5298 {
5299 return;
5300 }
5301
5302 self.code_action_providers.push(provider);
5303 self.refresh_code_actions(window, cx);
5304 }
5305
5306 pub fn remove_code_action_provider(
5307 &mut self,
5308 id: Arc<str>,
5309 window: &mut Window,
5310 cx: &mut Context<Self>,
5311 ) {
5312 self.code_action_providers
5313 .retain(|provider| provider.id() != id);
5314 self.refresh_code_actions(window, cx);
5315 }
5316
5317 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5318 let newest_selection = self.selections.newest_anchor().clone();
5319 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5320 let buffer = self.buffer.read(cx);
5321 if newest_selection.head().diff_base_anchor.is_some() {
5322 return None;
5323 }
5324 let (start_buffer, start) =
5325 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5326 let (end_buffer, end) =
5327 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5328 if start_buffer != end_buffer {
5329 return None;
5330 }
5331
5332 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5333 cx.background_executor()
5334 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5335 .await;
5336
5337 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5338 let providers = this.code_action_providers.clone();
5339 let tasks = this
5340 .code_action_providers
5341 .iter()
5342 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5343 .collect::<Vec<_>>();
5344 (providers, tasks)
5345 })?;
5346
5347 let mut actions = Vec::new();
5348 for (provider, provider_actions) in
5349 providers.into_iter().zip(future::join_all(tasks).await)
5350 {
5351 if let Some(provider_actions) = provider_actions.log_err() {
5352 actions.extend(provider_actions.into_iter().map(|action| {
5353 AvailableCodeAction {
5354 excerpt_id: newest_selection.start.excerpt_id,
5355 action,
5356 provider: provider.clone(),
5357 }
5358 }));
5359 }
5360 }
5361
5362 this.update(cx, |this, cx| {
5363 this.available_code_actions = if actions.is_empty() {
5364 None
5365 } else {
5366 Some((
5367 Location {
5368 buffer: start_buffer,
5369 range: start..end,
5370 },
5371 actions.into(),
5372 ))
5373 };
5374 cx.notify();
5375 })
5376 }));
5377 None
5378 }
5379
5380 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5381 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5382 self.show_git_blame_inline = false;
5383
5384 self.show_git_blame_inline_delay_task =
5385 Some(cx.spawn_in(window, async move |this, cx| {
5386 cx.background_executor().timer(delay).await;
5387
5388 this.update(cx, |this, cx| {
5389 this.show_git_blame_inline = true;
5390 cx.notify();
5391 })
5392 .log_err();
5393 }));
5394 }
5395 }
5396
5397 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5398 if self.pending_rename.is_some() {
5399 return None;
5400 }
5401
5402 let provider = self.semantics_provider.clone()?;
5403 let buffer = self.buffer.read(cx);
5404 let newest_selection = self.selections.newest_anchor().clone();
5405 let cursor_position = newest_selection.head();
5406 let (cursor_buffer, cursor_buffer_position) =
5407 buffer.text_anchor_for_position(cursor_position, cx)?;
5408 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5409 if cursor_buffer != tail_buffer {
5410 return None;
5411 }
5412 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5413 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5414 cx.background_executor()
5415 .timer(Duration::from_millis(debounce))
5416 .await;
5417
5418 let highlights = if let Some(highlights) = cx
5419 .update(|cx| {
5420 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5421 })
5422 .ok()
5423 .flatten()
5424 {
5425 highlights.await.log_err()
5426 } else {
5427 None
5428 };
5429
5430 if let Some(highlights) = highlights {
5431 this.update(cx, |this, cx| {
5432 if this.pending_rename.is_some() {
5433 return;
5434 }
5435
5436 let buffer_id = cursor_position.buffer_id;
5437 let buffer = this.buffer.read(cx);
5438 if !buffer
5439 .text_anchor_for_position(cursor_position, cx)
5440 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5441 {
5442 return;
5443 }
5444
5445 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5446 let mut write_ranges = Vec::new();
5447 let mut read_ranges = Vec::new();
5448 for highlight in highlights {
5449 for (excerpt_id, excerpt_range) in
5450 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5451 {
5452 let start = highlight
5453 .range
5454 .start
5455 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5456 let end = highlight
5457 .range
5458 .end
5459 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5460 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5461 continue;
5462 }
5463
5464 let range = Anchor {
5465 buffer_id,
5466 excerpt_id,
5467 text_anchor: start,
5468 diff_base_anchor: None,
5469 }..Anchor {
5470 buffer_id,
5471 excerpt_id,
5472 text_anchor: end,
5473 diff_base_anchor: None,
5474 };
5475 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5476 write_ranges.push(range);
5477 } else {
5478 read_ranges.push(range);
5479 }
5480 }
5481 }
5482
5483 this.highlight_background::<DocumentHighlightRead>(
5484 &read_ranges,
5485 |theme| theme.editor_document_highlight_read_background,
5486 cx,
5487 );
5488 this.highlight_background::<DocumentHighlightWrite>(
5489 &write_ranges,
5490 |theme| theme.editor_document_highlight_write_background,
5491 cx,
5492 );
5493 cx.notify();
5494 })
5495 .log_err();
5496 }
5497 }));
5498 None
5499 }
5500
5501 fn prepare_highlight_query_from_selection(
5502 &mut self,
5503 cx: &mut Context<Editor>,
5504 ) -> Option<(String, Range<Anchor>)> {
5505 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5506 return None;
5507 }
5508 if !EditorSettings::get_global(cx).selection_highlight {
5509 return None;
5510 }
5511 if self.selections.count() != 1 || self.selections.line_mode {
5512 return None;
5513 }
5514 let selection = self.selections.newest::<Point>(cx);
5515 if selection.is_empty() || selection.start.row != selection.end.row {
5516 return None;
5517 }
5518 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5519 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5520 let query = multi_buffer_snapshot
5521 .text_for_range(selection_anchor_range.clone())
5522 .collect::<String>();
5523 if query.trim().is_empty() {
5524 return None;
5525 }
5526 Some((query, selection_anchor_range))
5527 }
5528
5529 fn update_selection_occurrence_highlights(
5530 &mut self,
5531 query_text: String,
5532 query_range: Range<Anchor>,
5533 multi_buffer_range_to_query: Range<Point>,
5534 use_debounce: bool,
5535 window: &mut Window,
5536 cx: &mut Context<Editor>,
5537 ) -> Task<()> {
5538 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5539 cx.spawn_in(window, async move |editor, cx| {
5540 if use_debounce {
5541 cx.background_executor()
5542 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5543 .await;
5544 }
5545 let match_task = cx.background_spawn(async move {
5546 let buffer_ranges = multi_buffer_snapshot
5547 .range_to_buffer_ranges(multi_buffer_range_to_query)
5548 .into_iter()
5549 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5550 let mut match_ranges = Vec::new();
5551 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5552 match_ranges.extend(
5553 project::search::SearchQuery::text(
5554 query_text.clone(),
5555 false,
5556 false,
5557 false,
5558 Default::default(),
5559 Default::default(),
5560 false,
5561 None,
5562 )
5563 .unwrap()
5564 .search(&buffer_snapshot, Some(search_range.clone()))
5565 .await
5566 .into_iter()
5567 .filter_map(|match_range| {
5568 let match_start = buffer_snapshot
5569 .anchor_after(search_range.start + match_range.start);
5570 let match_end =
5571 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5572 let match_anchor_range = Anchor::range_in_buffer(
5573 excerpt_id,
5574 buffer_snapshot.remote_id(),
5575 match_start..match_end,
5576 );
5577 (match_anchor_range != query_range).then_some(match_anchor_range)
5578 }),
5579 );
5580 }
5581 match_ranges
5582 });
5583 let match_ranges = match_task.await;
5584 editor
5585 .update_in(cx, |editor, _, cx| {
5586 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5587 if !match_ranges.is_empty() {
5588 editor.highlight_background::<SelectedTextHighlight>(
5589 &match_ranges,
5590 |theme| theme.editor_document_highlight_bracket_background,
5591 cx,
5592 )
5593 }
5594 })
5595 .log_err();
5596 })
5597 }
5598
5599 fn refresh_selected_text_highlights(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5600 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5601 else {
5602 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5603 self.quick_selection_highlight_task.take();
5604 self.debounced_selection_highlight_task.take();
5605 return;
5606 };
5607 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5608 if self
5609 .quick_selection_highlight_task
5610 .as_ref()
5611 .map_or(true, |(prev_anchor_range, _)| {
5612 prev_anchor_range != &query_range
5613 })
5614 {
5615 let multi_buffer_visible_start = self
5616 .scroll_manager
5617 .anchor()
5618 .anchor
5619 .to_point(&multi_buffer_snapshot);
5620 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5621 multi_buffer_visible_start
5622 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5623 Bias::Left,
5624 );
5625 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5626 self.quick_selection_highlight_task = Some((
5627 query_range.clone(),
5628 self.update_selection_occurrence_highlights(
5629 query_text.clone(),
5630 query_range.clone(),
5631 multi_buffer_visible_range,
5632 false,
5633 window,
5634 cx,
5635 ),
5636 ));
5637 }
5638 if self
5639 .debounced_selection_highlight_task
5640 .as_ref()
5641 .map_or(true, |(prev_anchor_range, _)| {
5642 prev_anchor_range != &query_range
5643 })
5644 {
5645 let multi_buffer_start = multi_buffer_snapshot
5646 .anchor_before(0)
5647 .to_point(&multi_buffer_snapshot);
5648 let multi_buffer_end = multi_buffer_snapshot
5649 .anchor_after(multi_buffer_snapshot.len())
5650 .to_point(&multi_buffer_snapshot);
5651 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5652 self.debounced_selection_highlight_task = Some((
5653 query_range.clone(),
5654 self.update_selection_occurrence_highlights(
5655 query_text,
5656 query_range,
5657 multi_buffer_full_range,
5658 true,
5659 window,
5660 cx,
5661 ),
5662 ));
5663 }
5664 }
5665
5666 pub fn refresh_inline_completion(
5667 &mut self,
5668 debounce: bool,
5669 user_requested: bool,
5670 window: &mut Window,
5671 cx: &mut Context<Self>,
5672 ) -> Option<()> {
5673 let provider = self.edit_prediction_provider()?;
5674 let cursor = self.selections.newest_anchor().head();
5675 let (buffer, cursor_buffer_position) =
5676 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5677
5678 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5679 self.discard_inline_completion(false, cx);
5680 return None;
5681 }
5682
5683 if !user_requested
5684 && (!self.should_show_edit_predictions()
5685 || !self.is_focused(window)
5686 || buffer.read(cx).is_empty())
5687 {
5688 self.discard_inline_completion(false, cx);
5689 return None;
5690 }
5691
5692 self.update_visible_inline_completion(window, cx);
5693 provider.refresh(
5694 self.project.clone(),
5695 buffer,
5696 cursor_buffer_position,
5697 debounce,
5698 cx,
5699 );
5700 Some(())
5701 }
5702
5703 fn show_edit_predictions_in_menu(&self) -> bool {
5704 match self.edit_prediction_settings {
5705 EditPredictionSettings::Disabled => false,
5706 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5707 }
5708 }
5709
5710 pub fn edit_predictions_enabled(&self) -> bool {
5711 match self.edit_prediction_settings {
5712 EditPredictionSettings::Disabled => false,
5713 EditPredictionSettings::Enabled { .. } => true,
5714 }
5715 }
5716
5717 fn edit_prediction_requires_modifier(&self) -> bool {
5718 match self.edit_prediction_settings {
5719 EditPredictionSettings::Disabled => false,
5720 EditPredictionSettings::Enabled {
5721 preview_requires_modifier,
5722 ..
5723 } => preview_requires_modifier,
5724 }
5725 }
5726
5727 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5728 if self.edit_prediction_provider.is_none() {
5729 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5730 } else {
5731 let selection = self.selections.newest_anchor();
5732 let cursor = selection.head();
5733
5734 if let Some((buffer, cursor_buffer_position)) =
5735 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5736 {
5737 self.edit_prediction_settings =
5738 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5739 }
5740 }
5741 }
5742
5743 fn edit_prediction_settings_at_position(
5744 &self,
5745 buffer: &Entity<Buffer>,
5746 buffer_position: language::Anchor,
5747 cx: &App,
5748 ) -> EditPredictionSettings {
5749 if !self.mode.is_full()
5750 || !self.show_inline_completions_override.unwrap_or(true)
5751 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5752 {
5753 return EditPredictionSettings::Disabled;
5754 }
5755
5756 let buffer = buffer.read(cx);
5757
5758 let file = buffer.file();
5759
5760 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5761 return EditPredictionSettings::Disabled;
5762 };
5763
5764 let by_provider = matches!(
5765 self.menu_inline_completions_policy,
5766 MenuInlineCompletionsPolicy::ByProvider
5767 );
5768
5769 let show_in_menu = by_provider
5770 && self
5771 .edit_prediction_provider
5772 .as_ref()
5773 .map_or(false, |provider| {
5774 provider.provider.show_completions_in_menu()
5775 });
5776
5777 let preview_requires_modifier =
5778 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5779
5780 EditPredictionSettings::Enabled {
5781 show_in_menu,
5782 preview_requires_modifier,
5783 }
5784 }
5785
5786 fn should_show_edit_predictions(&self) -> bool {
5787 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5788 }
5789
5790 pub fn edit_prediction_preview_is_active(&self) -> bool {
5791 matches!(
5792 self.edit_prediction_preview,
5793 EditPredictionPreview::Active { .. }
5794 )
5795 }
5796
5797 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5798 let cursor = self.selections.newest_anchor().head();
5799 if let Some((buffer, cursor_position)) =
5800 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5801 {
5802 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5803 } else {
5804 false
5805 }
5806 }
5807
5808 fn edit_predictions_enabled_in_buffer(
5809 &self,
5810 buffer: &Entity<Buffer>,
5811 buffer_position: language::Anchor,
5812 cx: &App,
5813 ) -> bool {
5814 maybe!({
5815 if self.read_only(cx) {
5816 return Some(false);
5817 }
5818 let provider = self.edit_prediction_provider()?;
5819 if !provider.is_enabled(&buffer, buffer_position, cx) {
5820 return Some(false);
5821 }
5822 let buffer = buffer.read(cx);
5823 let Some(file) = buffer.file() else {
5824 return Some(true);
5825 };
5826 let settings = all_language_settings(Some(file), cx);
5827 Some(settings.edit_predictions_enabled_for_file(file, cx))
5828 })
5829 .unwrap_or(false)
5830 }
5831
5832 fn cycle_inline_completion(
5833 &mut self,
5834 direction: Direction,
5835 window: &mut Window,
5836 cx: &mut Context<Self>,
5837 ) -> Option<()> {
5838 let provider = self.edit_prediction_provider()?;
5839 let cursor = self.selections.newest_anchor().head();
5840 let (buffer, cursor_buffer_position) =
5841 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5842 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5843 return None;
5844 }
5845
5846 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5847 self.update_visible_inline_completion(window, cx);
5848
5849 Some(())
5850 }
5851
5852 pub fn show_inline_completion(
5853 &mut self,
5854 _: &ShowEditPrediction,
5855 window: &mut Window,
5856 cx: &mut Context<Self>,
5857 ) {
5858 if !self.has_active_inline_completion() {
5859 self.refresh_inline_completion(false, true, window, cx);
5860 return;
5861 }
5862
5863 self.update_visible_inline_completion(window, cx);
5864 }
5865
5866 pub fn display_cursor_names(
5867 &mut self,
5868 _: &DisplayCursorNames,
5869 window: &mut Window,
5870 cx: &mut Context<Self>,
5871 ) {
5872 self.show_cursor_names(window, cx);
5873 }
5874
5875 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5876 self.show_cursor_names = true;
5877 cx.notify();
5878 cx.spawn_in(window, async move |this, cx| {
5879 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5880 this.update(cx, |this, cx| {
5881 this.show_cursor_names = false;
5882 cx.notify()
5883 })
5884 .ok()
5885 })
5886 .detach();
5887 }
5888
5889 pub fn next_edit_prediction(
5890 &mut self,
5891 _: &NextEditPrediction,
5892 window: &mut Window,
5893 cx: &mut Context<Self>,
5894 ) {
5895 if self.has_active_inline_completion() {
5896 self.cycle_inline_completion(Direction::Next, window, cx);
5897 } else {
5898 let is_copilot_disabled = self
5899 .refresh_inline_completion(false, true, window, cx)
5900 .is_none();
5901 if is_copilot_disabled {
5902 cx.propagate();
5903 }
5904 }
5905 }
5906
5907 pub fn previous_edit_prediction(
5908 &mut self,
5909 _: &PreviousEditPrediction,
5910 window: &mut Window,
5911 cx: &mut Context<Self>,
5912 ) {
5913 if self.has_active_inline_completion() {
5914 self.cycle_inline_completion(Direction::Prev, window, cx);
5915 } else {
5916 let is_copilot_disabled = self
5917 .refresh_inline_completion(false, true, window, cx)
5918 .is_none();
5919 if is_copilot_disabled {
5920 cx.propagate();
5921 }
5922 }
5923 }
5924
5925 pub fn accept_edit_prediction(
5926 &mut self,
5927 _: &AcceptEditPrediction,
5928 window: &mut Window,
5929 cx: &mut Context<Self>,
5930 ) {
5931 if self.show_edit_predictions_in_menu() {
5932 self.hide_context_menu(window, cx);
5933 }
5934
5935 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5936 return;
5937 };
5938
5939 self.report_inline_completion_event(
5940 active_inline_completion.completion_id.clone(),
5941 true,
5942 cx,
5943 );
5944
5945 match &active_inline_completion.completion {
5946 InlineCompletion::Move { target, .. } => {
5947 let target = *target;
5948
5949 if let Some(position_map) = &self.last_position_map {
5950 if position_map
5951 .visible_row_range
5952 .contains(&target.to_display_point(&position_map.snapshot).row())
5953 || !self.edit_prediction_requires_modifier()
5954 {
5955 self.unfold_ranges(&[target..target], true, false, cx);
5956 // Note that this is also done in vim's handler of the Tab action.
5957 self.change_selections(
5958 Some(Autoscroll::newest()),
5959 window,
5960 cx,
5961 |selections| {
5962 selections.select_anchor_ranges([target..target]);
5963 },
5964 );
5965 self.clear_row_highlights::<EditPredictionPreview>();
5966
5967 self.edit_prediction_preview
5968 .set_previous_scroll_position(None);
5969 } else {
5970 self.edit_prediction_preview
5971 .set_previous_scroll_position(Some(
5972 position_map.snapshot.scroll_anchor,
5973 ));
5974
5975 self.highlight_rows::<EditPredictionPreview>(
5976 target..target,
5977 cx.theme().colors().editor_highlighted_line_background,
5978 RowHighlightOptions {
5979 autoscroll: true,
5980 ..Default::default()
5981 },
5982 cx,
5983 );
5984 self.request_autoscroll(Autoscroll::fit(), cx);
5985 }
5986 }
5987 }
5988 InlineCompletion::Edit { edits, .. } => {
5989 if let Some(provider) = self.edit_prediction_provider() {
5990 provider.accept(cx);
5991 }
5992
5993 let snapshot = self.buffer.read(cx).snapshot(cx);
5994 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5995
5996 self.buffer.update(cx, |buffer, cx| {
5997 buffer.edit(edits.iter().cloned(), None, cx)
5998 });
5999
6000 self.change_selections(None, window, cx, |s| {
6001 s.select_anchor_ranges([last_edit_end..last_edit_end])
6002 });
6003
6004 self.update_visible_inline_completion(window, cx);
6005 if self.active_inline_completion.is_none() {
6006 self.refresh_inline_completion(true, true, window, cx);
6007 }
6008
6009 cx.notify();
6010 }
6011 }
6012
6013 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6014 }
6015
6016 pub fn accept_partial_inline_completion(
6017 &mut self,
6018 _: &AcceptPartialEditPrediction,
6019 window: &mut Window,
6020 cx: &mut Context<Self>,
6021 ) {
6022 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6023 return;
6024 };
6025 if self.selections.count() != 1 {
6026 return;
6027 }
6028
6029 self.report_inline_completion_event(
6030 active_inline_completion.completion_id.clone(),
6031 true,
6032 cx,
6033 );
6034
6035 match &active_inline_completion.completion {
6036 InlineCompletion::Move { target, .. } => {
6037 let target = *target;
6038 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6039 selections.select_anchor_ranges([target..target]);
6040 });
6041 }
6042 InlineCompletion::Edit { edits, .. } => {
6043 // Find an insertion that starts at the cursor position.
6044 let snapshot = self.buffer.read(cx).snapshot(cx);
6045 let cursor_offset = self.selections.newest::<usize>(cx).head();
6046 let insertion = edits.iter().find_map(|(range, text)| {
6047 let range = range.to_offset(&snapshot);
6048 if range.is_empty() && range.start == cursor_offset {
6049 Some(text)
6050 } else {
6051 None
6052 }
6053 });
6054
6055 if let Some(text) = insertion {
6056 let mut partial_completion = text
6057 .chars()
6058 .by_ref()
6059 .take_while(|c| c.is_alphabetic())
6060 .collect::<String>();
6061 if partial_completion.is_empty() {
6062 partial_completion = text
6063 .chars()
6064 .by_ref()
6065 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6066 .collect::<String>();
6067 }
6068
6069 cx.emit(EditorEvent::InputHandled {
6070 utf16_range_to_replace: None,
6071 text: partial_completion.clone().into(),
6072 });
6073
6074 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6075
6076 self.refresh_inline_completion(true, true, window, cx);
6077 cx.notify();
6078 } else {
6079 self.accept_edit_prediction(&Default::default(), window, cx);
6080 }
6081 }
6082 }
6083 }
6084
6085 fn discard_inline_completion(
6086 &mut self,
6087 should_report_inline_completion_event: bool,
6088 cx: &mut Context<Self>,
6089 ) -> bool {
6090 if should_report_inline_completion_event {
6091 let completion_id = self
6092 .active_inline_completion
6093 .as_ref()
6094 .and_then(|active_completion| active_completion.completion_id.clone());
6095
6096 self.report_inline_completion_event(completion_id, false, cx);
6097 }
6098
6099 if let Some(provider) = self.edit_prediction_provider() {
6100 provider.discard(cx);
6101 }
6102
6103 self.take_active_inline_completion(cx)
6104 }
6105
6106 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6107 let Some(provider) = self.edit_prediction_provider() else {
6108 return;
6109 };
6110
6111 let Some((_, buffer, _)) = self
6112 .buffer
6113 .read(cx)
6114 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6115 else {
6116 return;
6117 };
6118
6119 let extension = buffer
6120 .read(cx)
6121 .file()
6122 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6123
6124 let event_type = match accepted {
6125 true => "Edit Prediction Accepted",
6126 false => "Edit Prediction Discarded",
6127 };
6128 telemetry::event!(
6129 event_type,
6130 provider = provider.name(),
6131 prediction_id = id,
6132 suggestion_accepted = accepted,
6133 file_extension = extension,
6134 );
6135 }
6136
6137 pub fn has_active_inline_completion(&self) -> bool {
6138 self.active_inline_completion.is_some()
6139 }
6140
6141 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6142 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6143 return false;
6144 };
6145
6146 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6147 self.clear_highlights::<InlineCompletionHighlight>(cx);
6148 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6149 true
6150 }
6151
6152 /// Returns true when we're displaying the edit prediction popover below the cursor
6153 /// like we are not previewing and the LSP autocomplete menu is visible
6154 /// or we are in `when_holding_modifier` mode.
6155 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6156 if self.edit_prediction_preview_is_active()
6157 || !self.show_edit_predictions_in_menu()
6158 || !self.edit_predictions_enabled()
6159 {
6160 return false;
6161 }
6162
6163 if self.has_visible_completions_menu() {
6164 return true;
6165 }
6166
6167 has_completion && self.edit_prediction_requires_modifier()
6168 }
6169
6170 fn handle_modifiers_changed(
6171 &mut self,
6172 modifiers: Modifiers,
6173 position_map: &PositionMap,
6174 window: &mut Window,
6175 cx: &mut Context<Self>,
6176 ) {
6177 if self.show_edit_predictions_in_menu() {
6178 self.update_edit_prediction_preview(&modifiers, window, cx);
6179 }
6180
6181 self.update_selection_mode(&modifiers, position_map, window, cx);
6182
6183 let mouse_position = window.mouse_position();
6184 if !position_map.text_hitbox.is_hovered(window) {
6185 return;
6186 }
6187
6188 self.update_hovered_link(
6189 position_map.point_for_position(mouse_position),
6190 &position_map.snapshot,
6191 modifiers,
6192 window,
6193 cx,
6194 )
6195 }
6196
6197 fn update_selection_mode(
6198 &mut self,
6199 modifiers: &Modifiers,
6200 position_map: &PositionMap,
6201 window: &mut Window,
6202 cx: &mut Context<Self>,
6203 ) {
6204 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6205 return;
6206 }
6207
6208 let mouse_position = window.mouse_position();
6209 let point_for_position = position_map.point_for_position(mouse_position);
6210 let position = point_for_position.previous_valid;
6211
6212 self.select(
6213 SelectPhase::BeginColumnar {
6214 position,
6215 reset: false,
6216 goal_column: point_for_position.exact_unclipped.column(),
6217 },
6218 window,
6219 cx,
6220 );
6221 }
6222
6223 fn update_edit_prediction_preview(
6224 &mut self,
6225 modifiers: &Modifiers,
6226 window: &mut Window,
6227 cx: &mut Context<Self>,
6228 ) {
6229 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6230 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6231 return;
6232 };
6233
6234 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6235 if matches!(
6236 self.edit_prediction_preview,
6237 EditPredictionPreview::Inactive { .. }
6238 ) {
6239 self.edit_prediction_preview = EditPredictionPreview::Active {
6240 previous_scroll_position: None,
6241 since: Instant::now(),
6242 };
6243
6244 self.update_visible_inline_completion(window, cx);
6245 cx.notify();
6246 }
6247 } else if let EditPredictionPreview::Active {
6248 previous_scroll_position,
6249 since,
6250 } = self.edit_prediction_preview
6251 {
6252 if let (Some(previous_scroll_position), Some(position_map)) =
6253 (previous_scroll_position, self.last_position_map.as_ref())
6254 {
6255 self.set_scroll_position(
6256 previous_scroll_position
6257 .scroll_position(&position_map.snapshot.display_snapshot),
6258 window,
6259 cx,
6260 );
6261 }
6262
6263 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6264 released_too_fast: since.elapsed() < Duration::from_millis(200),
6265 };
6266 self.clear_row_highlights::<EditPredictionPreview>();
6267 self.update_visible_inline_completion(window, cx);
6268 cx.notify();
6269 }
6270 }
6271
6272 fn update_visible_inline_completion(
6273 &mut self,
6274 _window: &mut Window,
6275 cx: &mut Context<Self>,
6276 ) -> Option<()> {
6277 let selection = self.selections.newest_anchor();
6278 let cursor = selection.head();
6279 let multibuffer = self.buffer.read(cx).snapshot(cx);
6280 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6281 let excerpt_id = cursor.excerpt_id;
6282
6283 let show_in_menu = self.show_edit_predictions_in_menu();
6284 let completions_menu_has_precedence = !show_in_menu
6285 && (self.context_menu.borrow().is_some()
6286 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6287
6288 if completions_menu_has_precedence
6289 || !offset_selection.is_empty()
6290 || self
6291 .active_inline_completion
6292 .as_ref()
6293 .map_or(false, |completion| {
6294 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6295 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6296 !invalidation_range.contains(&offset_selection.head())
6297 })
6298 {
6299 self.discard_inline_completion(false, cx);
6300 return None;
6301 }
6302
6303 self.take_active_inline_completion(cx);
6304 let Some(provider) = self.edit_prediction_provider() else {
6305 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6306 return None;
6307 };
6308
6309 let (buffer, cursor_buffer_position) =
6310 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6311
6312 self.edit_prediction_settings =
6313 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6314
6315 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6316
6317 if self.edit_prediction_indent_conflict {
6318 let cursor_point = cursor.to_point(&multibuffer);
6319
6320 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6321
6322 if let Some((_, indent)) = indents.iter().next() {
6323 if indent.len == cursor_point.column {
6324 self.edit_prediction_indent_conflict = false;
6325 }
6326 }
6327 }
6328
6329 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6330 let edits = inline_completion
6331 .edits
6332 .into_iter()
6333 .flat_map(|(range, new_text)| {
6334 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6335 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6336 Some((start..end, new_text))
6337 })
6338 .collect::<Vec<_>>();
6339 if edits.is_empty() {
6340 return None;
6341 }
6342
6343 let first_edit_start = edits.first().unwrap().0.start;
6344 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6345 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6346
6347 let last_edit_end = edits.last().unwrap().0.end;
6348 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6349 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6350
6351 let cursor_row = cursor.to_point(&multibuffer).row;
6352
6353 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6354
6355 let mut inlay_ids = Vec::new();
6356 let invalidation_row_range;
6357 let move_invalidation_row_range = if cursor_row < edit_start_row {
6358 Some(cursor_row..edit_end_row)
6359 } else if cursor_row > edit_end_row {
6360 Some(edit_start_row..cursor_row)
6361 } else {
6362 None
6363 };
6364 let is_move =
6365 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6366 let completion = if is_move {
6367 invalidation_row_range =
6368 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6369 let target = first_edit_start;
6370 InlineCompletion::Move { target, snapshot }
6371 } else {
6372 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6373 && !self.inline_completions_hidden_for_vim_mode;
6374
6375 if show_completions_in_buffer {
6376 if edits
6377 .iter()
6378 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6379 {
6380 let mut inlays = Vec::new();
6381 for (range, new_text) in &edits {
6382 let inlay = Inlay::inline_completion(
6383 post_inc(&mut self.next_inlay_id),
6384 range.start,
6385 new_text.as_str(),
6386 );
6387 inlay_ids.push(inlay.id);
6388 inlays.push(inlay);
6389 }
6390
6391 self.splice_inlays(&[], inlays, cx);
6392 } else {
6393 let background_color = cx.theme().status().deleted_background;
6394 self.highlight_text::<InlineCompletionHighlight>(
6395 edits.iter().map(|(range, _)| range.clone()).collect(),
6396 HighlightStyle {
6397 background_color: Some(background_color),
6398 ..Default::default()
6399 },
6400 cx,
6401 );
6402 }
6403 }
6404
6405 invalidation_row_range = edit_start_row..edit_end_row;
6406
6407 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6408 if provider.show_tab_accept_marker() {
6409 EditDisplayMode::TabAccept
6410 } else {
6411 EditDisplayMode::Inline
6412 }
6413 } else {
6414 EditDisplayMode::DiffPopover
6415 };
6416
6417 InlineCompletion::Edit {
6418 edits,
6419 edit_preview: inline_completion.edit_preview,
6420 display_mode,
6421 snapshot,
6422 }
6423 };
6424
6425 let invalidation_range = multibuffer
6426 .anchor_before(Point::new(invalidation_row_range.start, 0))
6427 ..multibuffer.anchor_after(Point::new(
6428 invalidation_row_range.end,
6429 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6430 ));
6431
6432 self.stale_inline_completion_in_menu = None;
6433 self.active_inline_completion = Some(InlineCompletionState {
6434 inlay_ids,
6435 completion,
6436 completion_id: inline_completion.id,
6437 invalidation_range,
6438 });
6439
6440 cx.notify();
6441
6442 Some(())
6443 }
6444
6445 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6446 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6447 }
6448
6449 fn render_code_actions_indicator(
6450 &self,
6451 _style: &EditorStyle,
6452 row: DisplayRow,
6453 is_active: bool,
6454 breakpoint: Option<&(Anchor, Breakpoint)>,
6455 cx: &mut Context<Self>,
6456 ) -> Option<IconButton> {
6457 let color = Color::Muted;
6458 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6459 let show_tooltip = !self.context_menu_visible();
6460
6461 if self.available_code_actions.is_some() {
6462 Some(
6463 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6464 .shape(ui::IconButtonShape::Square)
6465 .icon_size(IconSize::XSmall)
6466 .icon_color(color)
6467 .toggle_state(is_active)
6468 .when(show_tooltip, |this| {
6469 this.tooltip({
6470 let focus_handle = self.focus_handle.clone();
6471 move |window, cx| {
6472 Tooltip::for_action_in(
6473 "Toggle Code Actions",
6474 &ToggleCodeActions {
6475 deployed_from_indicator: None,
6476 },
6477 &focus_handle,
6478 window,
6479 cx,
6480 )
6481 }
6482 })
6483 })
6484 .on_click(cx.listener(move |editor, _e, window, cx| {
6485 window.focus(&editor.focus_handle(cx));
6486 editor.toggle_code_actions(
6487 &ToggleCodeActions {
6488 deployed_from_indicator: Some(row),
6489 },
6490 window,
6491 cx,
6492 );
6493 }))
6494 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6495 editor.set_breakpoint_context_menu(
6496 row,
6497 position,
6498 event.down.position,
6499 window,
6500 cx,
6501 );
6502 })),
6503 )
6504 } else {
6505 None
6506 }
6507 }
6508
6509 fn clear_tasks(&mut self) {
6510 self.tasks.clear()
6511 }
6512
6513 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6514 if self.tasks.insert(key, value).is_some() {
6515 // This case should hopefully be rare, but just in case...
6516 log::error!(
6517 "multiple different run targets found on a single line, only the last target will be rendered"
6518 )
6519 }
6520 }
6521
6522 /// Get all display points of breakpoints that will be rendered within editor
6523 ///
6524 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6525 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6526 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6527 fn active_breakpoints(
6528 &self,
6529 range: Range<DisplayRow>,
6530 window: &mut Window,
6531 cx: &mut Context<Self>,
6532 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6533 let mut breakpoint_display_points = HashMap::default();
6534
6535 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6536 return breakpoint_display_points;
6537 };
6538
6539 let snapshot = self.snapshot(window, cx);
6540
6541 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6542 let Some(project) = self.project.as_ref() else {
6543 return breakpoint_display_points;
6544 };
6545
6546 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6547 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6548
6549 for (buffer_snapshot, range, excerpt_id) in
6550 multi_buffer_snapshot.range_to_buffer_ranges(range)
6551 {
6552 let Some(buffer) = project.read_with(cx, |this, cx| {
6553 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6554 }) else {
6555 continue;
6556 };
6557 let breakpoints = breakpoint_store.read(cx).breakpoints(
6558 &buffer,
6559 Some(
6560 buffer_snapshot.anchor_before(range.start)
6561 ..buffer_snapshot.anchor_after(range.end),
6562 ),
6563 buffer_snapshot,
6564 cx,
6565 );
6566 for (anchor, breakpoint) in breakpoints {
6567 let multi_buffer_anchor =
6568 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6569 let position = multi_buffer_anchor
6570 .to_point(&multi_buffer_snapshot)
6571 .to_display_point(&snapshot);
6572
6573 breakpoint_display_points
6574 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6575 }
6576 }
6577
6578 breakpoint_display_points
6579 }
6580
6581 fn breakpoint_context_menu(
6582 &self,
6583 anchor: Anchor,
6584 window: &mut Window,
6585 cx: &mut Context<Self>,
6586 ) -> Entity<ui::ContextMenu> {
6587 let weak_editor = cx.weak_entity();
6588 let focus_handle = self.focus_handle(cx);
6589
6590 let row = self
6591 .buffer
6592 .read(cx)
6593 .snapshot(cx)
6594 .summary_for_anchor::<Point>(&anchor)
6595 .row;
6596
6597 let breakpoint = self
6598 .breakpoint_at_row(row, window, cx)
6599 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6600
6601 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6602 "Edit Log Breakpoint"
6603 } else {
6604 "Set Log Breakpoint"
6605 };
6606
6607 let condition_breakpoint_msg = if breakpoint
6608 .as_ref()
6609 .is_some_and(|bp| bp.1.condition.is_some())
6610 {
6611 "Edit Condition Breakpoint"
6612 } else {
6613 "Set Condition Breakpoint"
6614 };
6615
6616 let hit_condition_breakpoint_msg = if breakpoint
6617 .as_ref()
6618 .is_some_and(|bp| bp.1.hit_condition.is_some())
6619 {
6620 "Edit Hit Condition Breakpoint"
6621 } else {
6622 "Set Hit Condition Breakpoint"
6623 };
6624
6625 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6626 "Unset Breakpoint"
6627 } else {
6628 "Set Breakpoint"
6629 };
6630
6631 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6632 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6633
6634 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6635 BreakpointState::Enabled => Some("Disable"),
6636 BreakpointState::Disabled => Some("Enable"),
6637 });
6638
6639 let (anchor, breakpoint) =
6640 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6641
6642 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6643 menu.on_blur_subscription(Subscription::new(|| {}))
6644 .context(focus_handle)
6645 .when(run_to_cursor, |this| {
6646 let weak_editor = weak_editor.clone();
6647 this.entry("Run to cursor", None, move |window, cx| {
6648 weak_editor
6649 .update(cx, |editor, cx| {
6650 editor.change_selections(None, window, cx, |s| {
6651 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6652 });
6653 })
6654 .ok();
6655
6656 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6657 })
6658 .separator()
6659 })
6660 .when_some(toggle_state_msg, |this, msg| {
6661 this.entry(msg, None, {
6662 let weak_editor = weak_editor.clone();
6663 let breakpoint = breakpoint.clone();
6664 move |_window, cx| {
6665 weak_editor
6666 .update(cx, |this, cx| {
6667 this.edit_breakpoint_at_anchor(
6668 anchor,
6669 breakpoint.as_ref().clone(),
6670 BreakpointEditAction::InvertState,
6671 cx,
6672 );
6673 })
6674 .log_err();
6675 }
6676 })
6677 })
6678 .entry(set_breakpoint_msg, None, {
6679 let weak_editor = weak_editor.clone();
6680 let breakpoint = breakpoint.clone();
6681 move |_window, cx| {
6682 weak_editor
6683 .update(cx, |this, cx| {
6684 this.edit_breakpoint_at_anchor(
6685 anchor,
6686 breakpoint.as_ref().clone(),
6687 BreakpointEditAction::Toggle,
6688 cx,
6689 );
6690 })
6691 .log_err();
6692 }
6693 })
6694 .entry(log_breakpoint_msg, None, {
6695 let breakpoint = breakpoint.clone();
6696 let weak_editor = weak_editor.clone();
6697 move |window, cx| {
6698 weak_editor
6699 .update(cx, |this, cx| {
6700 this.add_edit_breakpoint_block(
6701 anchor,
6702 breakpoint.as_ref(),
6703 BreakpointPromptEditAction::Log,
6704 window,
6705 cx,
6706 );
6707 })
6708 .log_err();
6709 }
6710 })
6711 .entry(condition_breakpoint_msg, None, {
6712 let breakpoint = breakpoint.clone();
6713 let weak_editor = weak_editor.clone();
6714 move |window, cx| {
6715 weak_editor
6716 .update(cx, |this, cx| {
6717 this.add_edit_breakpoint_block(
6718 anchor,
6719 breakpoint.as_ref(),
6720 BreakpointPromptEditAction::Condition,
6721 window,
6722 cx,
6723 );
6724 })
6725 .log_err();
6726 }
6727 })
6728 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6729 weak_editor
6730 .update(cx, |this, cx| {
6731 this.add_edit_breakpoint_block(
6732 anchor,
6733 breakpoint.as_ref(),
6734 BreakpointPromptEditAction::HitCondition,
6735 window,
6736 cx,
6737 );
6738 })
6739 .log_err();
6740 })
6741 })
6742 }
6743
6744 fn render_breakpoint(
6745 &self,
6746 position: Anchor,
6747 row: DisplayRow,
6748 breakpoint: &Breakpoint,
6749 cx: &mut Context<Self>,
6750 ) -> IconButton {
6751 let (color, icon) = {
6752 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6753 (false, false) => ui::IconName::DebugBreakpoint,
6754 (true, false) => ui::IconName::DebugLogBreakpoint,
6755 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6756 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6757 };
6758
6759 let color = if self
6760 .gutter_breakpoint_indicator
6761 .0
6762 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6763 {
6764 Color::Hint
6765 } else {
6766 Color::Debugger
6767 };
6768
6769 (color, icon)
6770 };
6771
6772 let breakpoint = Arc::from(breakpoint.clone());
6773
6774 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6775 .icon_size(IconSize::XSmall)
6776 .size(ui::ButtonSize::None)
6777 .icon_color(color)
6778 .style(ButtonStyle::Transparent)
6779 .on_click(cx.listener({
6780 let breakpoint = breakpoint.clone();
6781
6782 move |editor, event: &ClickEvent, window, cx| {
6783 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6784 BreakpointEditAction::InvertState
6785 } else {
6786 BreakpointEditAction::Toggle
6787 };
6788
6789 window.focus(&editor.focus_handle(cx));
6790 editor.edit_breakpoint_at_anchor(
6791 position,
6792 breakpoint.as_ref().clone(),
6793 edit_action,
6794 cx,
6795 );
6796 }
6797 }))
6798 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6799 editor.set_breakpoint_context_menu(
6800 row,
6801 Some(position),
6802 event.down.position,
6803 window,
6804 cx,
6805 );
6806 }))
6807 }
6808
6809 fn build_tasks_context(
6810 project: &Entity<Project>,
6811 buffer: &Entity<Buffer>,
6812 buffer_row: u32,
6813 tasks: &Arc<RunnableTasks>,
6814 cx: &mut Context<Self>,
6815 ) -> Task<Option<task::TaskContext>> {
6816 let position = Point::new(buffer_row, tasks.column);
6817 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6818 let location = Location {
6819 buffer: buffer.clone(),
6820 range: range_start..range_start,
6821 };
6822 // Fill in the environmental variables from the tree-sitter captures
6823 let mut captured_task_variables = TaskVariables::default();
6824 for (capture_name, value) in tasks.extra_variables.clone() {
6825 captured_task_variables.insert(
6826 task::VariableName::Custom(capture_name.into()),
6827 value.clone(),
6828 );
6829 }
6830 project.update(cx, |project, cx| {
6831 project.task_store().update(cx, |task_store, cx| {
6832 task_store.task_context_for_location(captured_task_variables, location, cx)
6833 })
6834 })
6835 }
6836
6837 pub fn spawn_nearest_task(
6838 &mut self,
6839 action: &SpawnNearestTask,
6840 window: &mut Window,
6841 cx: &mut Context<Self>,
6842 ) {
6843 let Some((workspace, _)) = self.workspace.clone() else {
6844 return;
6845 };
6846 let Some(project) = self.project.clone() else {
6847 return;
6848 };
6849
6850 // Try to find a closest, enclosing node using tree-sitter that has a
6851 // task
6852 let Some((buffer, buffer_row, tasks)) = self
6853 .find_enclosing_node_task(cx)
6854 // Or find the task that's closest in row-distance.
6855 .or_else(|| self.find_closest_task(cx))
6856 else {
6857 return;
6858 };
6859
6860 let reveal_strategy = action.reveal;
6861 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6862 cx.spawn_in(window, async move |_, cx| {
6863 let context = task_context.await?;
6864 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6865
6866 let resolved = resolved_task.resolved.as_mut()?;
6867 resolved.reveal = reveal_strategy;
6868
6869 workspace
6870 .update_in(cx, |workspace, window, cx| {
6871 workspace.schedule_resolved_task(
6872 task_source_kind,
6873 resolved_task,
6874 false,
6875 window,
6876 cx,
6877 );
6878 })
6879 .ok()
6880 })
6881 .detach();
6882 }
6883
6884 fn find_closest_task(
6885 &mut self,
6886 cx: &mut Context<Self>,
6887 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6888 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6889
6890 let ((buffer_id, row), tasks) = self
6891 .tasks
6892 .iter()
6893 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6894
6895 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6896 let tasks = Arc::new(tasks.to_owned());
6897 Some((buffer, *row, tasks))
6898 }
6899
6900 fn find_enclosing_node_task(
6901 &mut self,
6902 cx: &mut Context<Self>,
6903 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6904 let snapshot = self.buffer.read(cx).snapshot(cx);
6905 let offset = self.selections.newest::<usize>(cx).head();
6906 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6907 let buffer_id = excerpt.buffer().remote_id();
6908
6909 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6910 let mut cursor = layer.node().walk();
6911
6912 while cursor.goto_first_child_for_byte(offset).is_some() {
6913 if cursor.node().end_byte() == offset {
6914 cursor.goto_next_sibling();
6915 }
6916 }
6917
6918 // Ascend to the smallest ancestor that contains the range and has a task.
6919 loop {
6920 let node = cursor.node();
6921 let node_range = node.byte_range();
6922 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6923
6924 // Check if this node contains our offset
6925 if node_range.start <= offset && node_range.end >= offset {
6926 // If it contains offset, check for task
6927 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6928 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6929 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6930 }
6931 }
6932
6933 if !cursor.goto_parent() {
6934 break;
6935 }
6936 }
6937 None
6938 }
6939
6940 fn render_run_indicator(
6941 &self,
6942 _style: &EditorStyle,
6943 is_active: bool,
6944 row: DisplayRow,
6945 breakpoint: Option<(Anchor, Breakpoint)>,
6946 cx: &mut Context<Self>,
6947 ) -> IconButton {
6948 let color = Color::Muted;
6949 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6950
6951 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6952 .shape(ui::IconButtonShape::Square)
6953 .icon_size(IconSize::XSmall)
6954 .icon_color(color)
6955 .toggle_state(is_active)
6956 .on_click(cx.listener(move |editor, _e, window, cx| {
6957 window.focus(&editor.focus_handle(cx));
6958 editor.toggle_code_actions(
6959 &ToggleCodeActions {
6960 deployed_from_indicator: Some(row),
6961 },
6962 window,
6963 cx,
6964 );
6965 }))
6966 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6967 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6968 }))
6969 }
6970
6971 pub fn context_menu_visible(&self) -> bool {
6972 !self.edit_prediction_preview_is_active()
6973 && self
6974 .context_menu
6975 .borrow()
6976 .as_ref()
6977 .map_or(false, |menu| menu.visible())
6978 }
6979
6980 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6981 self.context_menu
6982 .borrow()
6983 .as_ref()
6984 .map(|menu| menu.origin())
6985 }
6986
6987 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6988 self.context_menu_options = Some(options);
6989 }
6990
6991 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6992 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6993
6994 fn render_edit_prediction_popover(
6995 &mut self,
6996 text_bounds: &Bounds<Pixels>,
6997 content_origin: gpui::Point<Pixels>,
6998 editor_snapshot: &EditorSnapshot,
6999 visible_row_range: Range<DisplayRow>,
7000 scroll_top: f32,
7001 scroll_bottom: f32,
7002 line_layouts: &[LineWithInvisibles],
7003 line_height: Pixels,
7004 scroll_pixel_position: gpui::Point<Pixels>,
7005 newest_selection_head: Option<DisplayPoint>,
7006 editor_width: Pixels,
7007 style: &EditorStyle,
7008 window: &mut Window,
7009 cx: &mut App,
7010 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7011 let active_inline_completion = self.active_inline_completion.as_ref()?;
7012
7013 if self.edit_prediction_visible_in_cursor_popover(true) {
7014 return None;
7015 }
7016
7017 match &active_inline_completion.completion {
7018 InlineCompletion::Move { target, .. } => {
7019 let target_display_point = target.to_display_point(editor_snapshot);
7020
7021 if self.edit_prediction_requires_modifier() {
7022 if !self.edit_prediction_preview_is_active() {
7023 return None;
7024 }
7025
7026 self.render_edit_prediction_modifier_jump_popover(
7027 text_bounds,
7028 content_origin,
7029 visible_row_range,
7030 line_layouts,
7031 line_height,
7032 scroll_pixel_position,
7033 newest_selection_head,
7034 target_display_point,
7035 window,
7036 cx,
7037 )
7038 } else {
7039 self.render_edit_prediction_eager_jump_popover(
7040 text_bounds,
7041 content_origin,
7042 editor_snapshot,
7043 visible_row_range,
7044 scroll_top,
7045 scroll_bottom,
7046 line_height,
7047 scroll_pixel_position,
7048 target_display_point,
7049 editor_width,
7050 window,
7051 cx,
7052 )
7053 }
7054 }
7055 InlineCompletion::Edit {
7056 display_mode: EditDisplayMode::Inline,
7057 ..
7058 } => None,
7059 InlineCompletion::Edit {
7060 display_mode: EditDisplayMode::TabAccept,
7061 edits,
7062 ..
7063 } => {
7064 let range = &edits.first()?.0;
7065 let target_display_point = range.end.to_display_point(editor_snapshot);
7066
7067 self.render_edit_prediction_end_of_line_popover(
7068 "Accept",
7069 editor_snapshot,
7070 visible_row_range,
7071 target_display_point,
7072 line_height,
7073 scroll_pixel_position,
7074 content_origin,
7075 editor_width,
7076 window,
7077 cx,
7078 )
7079 }
7080 InlineCompletion::Edit {
7081 edits,
7082 edit_preview,
7083 display_mode: EditDisplayMode::DiffPopover,
7084 snapshot,
7085 } => self.render_edit_prediction_diff_popover(
7086 text_bounds,
7087 content_origin,
7088 editor_snapshot,
7089 visible_row_range,
7090 line_layouts,
7091 line_height,
7092 scroll_pixel_position,
7093 newest_selection_head,
7094 editor_width,
7095 style,
7096 edits,
7097 edit_preview,
7098 snapshot,
7099 window,
7100 cx,
7101 ),
7102 }
7103 }
7104
7105 fn render_edit_prediction_modifier_jump_popover(
7106 &mut self,
7107 text_bounds: &Bounds<Pixels>,
7108 content_origin: gpui::Point<Pixels>,
7109 visible_row_range: Range<DisplayRow>,
7110 line_layouts: &[LineWithInvisibles],
7111 line_height: Pixels,
7112 scroll_pixel_position: gpui::Point<Pixels>,
7113 newest_selection_head: Option<DisplayPoint>,
7114 target_display_point: DisplayPoint,
7115 window: &mut Window,
7116 cx: &mut App,
7117 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7118 let scrolled_content_origin =
7119 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7120
7121 const SCROLL_PADDING_Y: Pixels = px(12.);
7122
7123 if target_display_point.row() < visible_row_range.start {
7124 return self.render_edit_prediction_scroll_popover(
7125 |_| SCROLL_PADDING_Y,
7126 IconName::ArrowUp,
7127 visible_row_range,
7128 line_layouts,
7129 newest_selection_head,
7130 scrolled_content_origin,
7131 window,
7132 cx,
7133 );
7134 } else if target_display_point.row() >= visible_row_range.end {
7135 return self.render_edit_prediction_scroll_popover(
7136 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7137 IconName::ArrowDown,
7138 visible_row_range,
7139 line_layouts,
7140 newest_selection_head,
7141 scrolled_content_origin,
7142 window,
7143 cx,
7144 );
7145 }
7146
7147 const POLE_WIDTH: Pixels = px(2.);
7148
7149 let line_layout =
7150 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7151 let target_column = target_display_point.column() as usize;
7152
7153 let target_x = line_layout.x_for_index(target_column);
7154 let target_y =
7155 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7156
7157 let flag_on_right = target_x < text_bounds.size.width / 2.;
7158
7159 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7160 border_color.l += 0.001;
7161
7162 let mut element = v_flex()
7163 .items_end()
7164 .when(flag_on_right, |el| el.items_start())
7165 .child(if flag_on_right {
7166 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7167 .rounded_bl(px(0.))
7168 .rounded_tl(px(0.))
7169 .border_l_2()
7170 .border_color(border_color)
7171 } else {
7172 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7173 .rounded_br(px(0.))
7174 .rounded_tr(px(0.))
7175 .border_r_2()
7176 .border_color(border_color)
7177 })
7178 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7179 .into_any();
7180
7181 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7182
7183 let mut origin = scrolled_content_origin + point(target_x, target_y)
7184 - point(
7185 if flag_on_right {
7186 POLE_WIDTH
7187 } else {
7188 size.width - POLE_WIDTH
7189 },
7190 size.height - line_height,
7191 );
7192
7193 origin.x = origin.x.max(content_origin.x);
7194
7195 element.prepaint_at(origin, window, cx);
7196
7197 Some((element, origin))
7198 }
7199
7200 fn render_edit_prediction_scroll_popover(
7201 &mut self,
7202 to_y: impl Fn(Size<Pixels>) -> Pixels,
7203 scroll_icon: IconName,
7204 visible_row_range: Range<DisplayRow>,
7205 line_layouts: &[LineWithInvisibles],
7206 newest_selection_head: Option<DisplayPoint>,
7207 scrolled_content_origin: gpui::Point<Pixels>,
7208 window: &mut Window,
7209 cx: &mut App,
7210 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7211 let mut element = self
7212 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7213 .into_any();
7214
7215 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7216
7217 let cursor = newest_selection_head?;
7218 let cursor_row_layout =
7219 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7220 let cursor_column = cursor.column() as usize;
7221
7222 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7223
7224 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7225
7226 element.prepaint_at(origin, window, cx);
7227 Some((element, origin))
7228 }
7229
7230 fn render_edit_prediction_eager_jump_popover(
7231 &mut self,
7232 text_bounds: &Bounds<Pixels>,
7233 content_origin: gpui::Point<Pixels>,
7234 editor_snapshot: &EditorSnapshot,
7235 visible_row_range: Range<DisplayRow>,
7236 scroll_top: f32,
7237 scroll_bottom: f32,
7238 line_height: Pixels,
7239 scroll_pixel_position: gpui::Point<Pixels>,
7240 target_display_point: DisplayPoint,
7241 editor_width: Pixels,
7242 window: &mut Window,
7243 cx: &mut App,
7244 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7245 if target_display_point.row().as_f32() < scroll_top {
7246 let mut element = self
7247 .render_edit_prediction_line_popover(
7248 "Jump to Edit",
7249 Some(IconName::ArrowUp),
7250 window,
7251 cx,
7252 )?
7253 .into_any();
7254
7255 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7256 let offset = point(
7257 (text_bounds.size.width - size.width) / 2.,
7258 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7259 );
7260
7261 let origin = text_bounds.origin + offset;
7262 element.prepaint_at(origin, window, cx);
7263 Some((element, origin))
7264 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7265 let mut element = self
7266 .render_edit_prediction_line_popover(
7267 "Jump to Edit",
7268 Some(IconName::ArrowDown),
7269 window,
7270 cx,
7271 )?
7272 .into_any();
7273
7274 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7275 let offset = point(
7276 (text_bounds.size.width - size.width) / 2.,
7277 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7278 );
7279
7280 let origin = text_bounds.origin + offset;
7281 element.prepaint_at(origin, window, cx);
7282 Some((element, origin))
7283 } else {
7284 self.render_edit_prediction_end_of_line_popover(
7285 "Jump to Edit",
7286 editor_snapshot,
7287 visible_row_range,
7288 target_display_point,
7289 line_height,
7290 scroll_pixel_position,
7291 content_origin,
7292 editor_width,
7293 window,
7294 cx,
7295 )
7296 }
7297 }
7298
7299 fn render_edit_prediction_end_of_line_popover(
7300 self: &mut Editor,
7301 label: &'static str,
7302 editor_snapshot: &EditorSnapshot,
7303 visible_row_range: Range<DisplayRow>,
7304 target_display_point: DisplayPoint,
7305 line_height: Pixels,
7306 scroll_pixel_position: gpui::Point<Pixels>,
7307 content_origin: gpui::Point<Pixels>,
7308 editor_width: Pixels,
7309 window: &mut Window,
7310 cx: &mut App,
7311 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7312 let target_line_end = DisplayPoint::new(
7313 target_display_point.row(),
7314 editor_snapshot.line_len(target_display_point.row()),
7315 );
7316
7317 let mut element = self
7318 .render_edit_prediction_line_popover(label, None, window, cx)?
7319 .into_any();
7320
7321 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7322
7323 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7324
7325 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7326 let mut origin = start_point
7327 + line_origin
7328 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7329 origin.x = origin.x.max(content_origin.x);
7330
7331 let max_x = content_origin.x + editor_width - size.width;
7332
7333 if origin.x > max_x {
7334 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7335
7336 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7337 origin.y += offset;
7338 IconName::ArrowUp
7339 } else {
7340 origin.y -= offset;
7341 IconName::ArrowDown
7342 };
7343
7344 element = self
7345 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7346 .into_any();
7347
7348 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7349
7350 origin.x = content_origin.x + editor_width - size.width - px(2.);
7351 }
7352
7353 element.prepaint_at(origin, window, cx);
7354 Some((element, origin))
7355 }
7356
7357 fn render_edit_prediction_diff_popover(
7358 self: &Editor,
7359 text_bounds: &Bounds<Pixels>,
7360 content_origin: gpui::Point<Pixels>,
7361 editor_snapshot: &EditorSnapshot,
7362 visible_row_range: Range<DisplayRow>,
7363 line_layouts: &[LineWithInvisibles],
7364 line_height: Pixels,
7365 scroll_pixel_position: gpui::Point<Pixels>,
7366 newest_selection_head: Option<DisplayPoint>,
7367 editor_width: Pixels,
7368 style: &EditorStyle,
7369 edits: &Vec<(Range<Anchor>, String)>,
7370 edit_preview: &Option<language::EditPreview>,
7371 snapshot: &language::BufferSnapshot,
7372 window: &mut Window,
7373 cx: &mut App,
7374 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7375 let edit_start = edits
7376 .first()
7377 .unwrap()
7378 .0
7379 .start
7380 .to_display_point(editor_snapshot);
7381 let edit_end = edits
7382 .last()
7383 .unwrap()
7384 .0
7385 .end
7386 .to_display_point(editor_snapshot);
7387
7388 let is_visible = visible_row_range.contains(&edit_start.row())
7389 || visible_row_range.contains(&edit_end.row());
7390 if !is_visible {
7391 return None;
7392 }
7393
7394 let highlighted_edits =
7395 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7396
7397 let styled_text = highlighted_edits.to_styled_text(&style.text);
7398 let line_count = highlighted_edits.text.lines().count();
7399
7400 const BORDER_WIDTH: Pixels = px(1.);
7401
7402 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7403 let has_keybind = keybind.is_some();
7404
7405 let mut element = h_flex()
7406 .items_start()
7407 .child(
7408 h_flex()
7409 .bg(cx.theme().colors().editor_background)
7410 .border(BORDER_WIDTH)
7411 .shadow_sm()
7412 .border_color(cx.theme().colors().border)
7413 .rounded_l_lg()
7414 .when(line_count > 1, |el| el.rounded_br_lg())
7415 .pr_1()
7416 .child(styled_text),
7417 )
7418 .child(
7419 h_flex()
7420 .h(line_height + BORDER_WIDTH * 2.)
7421 .px_1p5()
7422 .gap_1()
7423 // Workaround: For some reason, there's a gap if we don't do this
7424 .ml(-BORDER_WIDTH)
7425 .shadow(smallvec![gpui::BoxShadow {
7426 color: gpui::black().opacity(0.05),
7427 offset: point(px(1.), px(1.)),
7428 blur_radius: px(2.),
7429 spread_radius: px(0.),
7430 }])
7431 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7432 .border(BORDER_WIDTH)
7433 .border_color(cx.theme().colors().border)
7434 .rounded_r_lg()
7435 .id("edit_prediction_diff_popover_keybind")
7436 .when(!has_keybind, |el| {
7437 let status_colors = cx.theme().status();
7438
7439 el.bg(status_colors.error_background)
7440 .border_color(status_colors.error.opacity(0.6))
7441 .child(Icon::new(IconName::Info).color(Color::Error))
7442 .cursor_default()
7443 .hoverable_tooltip(move |_window, cx| {
7444 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7445 })
7446 })
7447 .children(keybind),
7448 )
7449 .into_any();
7450
7451 let longest_row =
7452 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7453 let longest_line_width = if visible_row_range.contains(&longest_row) {
7454 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7455 } else {
7456 layout_line(
7457 longest_row,
7458 editor_snapshot,
7459 style,
7460 editor_width,
7461 |_| false,
7462 window,
7463 cx,
7464 )
7465 .width
7466 };
7467
7468 let viewport_bounds =
7469 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7470 right: -EditorElement::SCROLLBAR_WIDTH,
7471 ..Default::default()
7472 });
7473
7474 let x_after_longest =
7475 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7476 - scroll_pixel_position.x;
7477
7478 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7479
7480 // Fully visible if it can be displayed within the window (allow overlapping other
7481 // panes). However, this is only allowed if the popover starts within text_bounds.
7482 let can_position_to_the_right = x_after_longest < text_bounds.right()
7483 && x_after_longest + element_bounds.width < viewport_bounds.right();
7484
7485 let mut origin = if can_position_to_the_right {
7486 point(
7487 x_after_longest,
7488 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7489 - scroll_pixel_position.y,
7490 )
7491 } else {
7492 let cursor_row = newest_selection_head.map(|head| head.row());
7493 let above_edit = edit_start
7494 .row()
7495 .0
7496 .checked_sub(line_count as u32)
7497 .map(DisplayRow);
7498 let below_edit = Some(edit_end.row() + 1);
7499 let above_cursor =
7500 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7501 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7502
7503 // Place the edit popover adjacent to the edit if there is a location
7504 // available that is onscreen and does not obscure the cursor. Otherwise,
7505 // place it adjacent to the cursor.
7506 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7507 .into_iter()
7508 .flatten()
7509 .find(|&start_row| {
7510 let end_row = start_row + line_count as u32;
7511 visible_row_range.contains(&start_row)
7512 && visible_row_range.contains(&end_row)
7513 && cursor_row.map_or(true, |cursor_row| {
7514 !((start_row..end_row).contains(&cursor_row))
7515 })
7516 })?;
7517
7518 content_origin
7519 + point(
7520 -scroll_pixel_position.x,
7521 row_target.as_f32() * line_height - scroll_pixel_position.y,
7522 )
7523 };
7524
7525 origin.x -= BORDER_WIDTH;
7526
7527 window.defer_draw(element, origin, 1);
7528
7529 // Do not return an element, since it will already be drawn due to defer_draw.
7530 None
7531 }
7532
7533 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7534 px(30.)
7535 }
7536
7537 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7538 if self.read_only(cx) {
7539 cx.theme().players().read_only()
7540 } else {
7541 self.style.as_ref().unwrap().local_player
7542 }
7543 }
7544
7545 fn render_edit_prediction_accept_keybind(
7546 &self,
7547 window: &mut Window,
7548 cx: &App,
7549 ) -> Option<AnyElement> {
7550 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7551 let accept_keystroke = accept_binding.keystroke()?;
7552
7553 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7554
7555 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7556 Color::Accent
7557 } else {
7558 Color::Muted
7559 };
7560
7561 h_flex()
7562 .px_0p5()
7563 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7564 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7565 .text_size(TextSize::XSmall.rems(cx))
7566 .child(h_flex().children(ui::render_modifiers(
7567 &accept_keystroke.modifiers,
7568 PlatformStyle::platform(),
7569 Some(modifiers_color),
7570 Some(IconSize::XSmall.rems().into()),
7571 true,
7572 )))
7573 .when(is_platform_style_mac, |parent| {
7574 parent.child(accept_keystroke.key.clone())
7575 })
7576 .when(!is_platform_style_mac, |parent| {
7577 parent.child(
7578 Key::new(
7579 util::capitalize(&accept_keystroke.key),
7580 Some(Color::Default),
7581 )
7582 .size(Some(IconSize::XSmall.rems().into())),
7583 )
7584 })
7585 .into_any()
7586 .into()
7587 }
7588
7589 fn render_edit_prediction_line_popover(
7590 &self,
7591 label: impl Into<SharedString>,
7592 icon: Option<IconName>,
7593 window: &mut Window,
7594 cx: &App,
7595 ) -> Option<Stateful<Div>> {
7596 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7597
7598 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7599 let has_keybind = keybind.is_some();
7600
7601 let result = h_flex()
7602 .id("ep-line-popover")
7603 .py_0p5()
7604 .pl_1()
7605 .pr(padding_right)
7606 .gap_1()
7607 .rounded_md()
7608 .border_1()
7609 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7610 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7611 .shadow_sm()
7612 .when(!has_keybind, |el| {
7613 let status_colors = cx.theme().status();
7614
7615 el.bg(status_colors.error_background)
7616 .border_color(status_colors.error.opacity(0.6))
7617 .pl_2()
7618 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7619 .cursor_default()
7620 .hoverable_tooltip(move |_window, cx| {
7621 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7622 })
7623 })
7624 .children(keybind)
7625 .child(
7626 Label::new(label)
7627 .size(LabelSize::Small)
7628 .when(!has_keybind, |el| {
7629 el.color(cx.theme().status().error.into()).strikethrough()
7630 }),
7631 )
7632 .when(!has_keybind, |el| {
7633 el.child(
7634 h_flex().ml_1().child(
7635 Icon::new(IconName::Info)
7636 .size(IconSize::Small)
7637 .color(cx.theme().status().error.into()),
7638 ),
7639 )
7640 })
7641 .when_some(icon, |element, icon| {
7642 element.child(
7643 div()
7644 .mt(px(1.5))
7645 .child(Icon::new(icon).size(IconSize::Small)),
7646 )
7647 });
7648
7649 Some(result)
7650 }
7651
7652 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7653 let accent_color = cx.theme().colors().text_accent;
7654 let editor_bg_color = cx.theme().colors().editor_background;
7655 editor_bg_color.blend(accent_color.opacity(0.1))
7656 }
7657
7658 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7659 let accent_color = cx.theme().colors().text_accent;
7660 let editor_bg_color = cx.theme().colors().editor_background;
7661 editor_bg_color.blend(accent_color.opacity(0.6))
7662 }
7663
7664 fn render_edit_prediction_cursor_popover(
7665 &self,
7666 min_width: Pixels,
7667 max_width: Pixels,
7668 cursor_point: Point,
7669 style: &EditorStyle,
7670 accept_keystroke: Option<&gpui::Keystroke>,
7671 _window: &Window,
7672 cx: &mut Context<Editor>,
7673 ) -> Option<AnyElement> {
7674 let provider = self.edit_prediction_provider.as_ref()?;
7675
7676 if provider.provider.needs_terms_acceptance(cx) {
7677 return Some(
7678 h_flex()
7679 .min_w(min_width)
7680 .flex_1()
7681 .px_2()
7682 .py_1()
7683 .gap_3()
7684 .elevation_2(cx)
7685 .hover(|style| style.bg(cx.theme().colors().element_hover))
7686 .id("accept-terms")
7687 .cursor_pointer()
7688 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7689 .on_click(cx.listener(|this, _event, window, cx| {
7690 cx.stop_propagation();
7691 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7692 window.dispatch_action(
7693 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7694 cx,
7695 );
7696 }))
7697 .child(
7698 h_flex()
7699 .flex_1()
7700 .gap_2()
7701 .child(Icon::new(IconName::ZedPredict))
7702 .child(Label::new("Accept Terms of Service"))
7703 .child(div().w_full())
7704 .child(
7705 Icon::new(IconName::ArrowUpRight)
7706 .color(Color::Muted)
7707 .size(IconSize::Small),
7708 )
7709 .into_any_element(),
7710 )
7711 .into_any(),
7712 );
7713 }
7714
7715 let is_refreshing = provider.provider.is_refreshing(cx);
7716
7717 fn pending_completion_container() -> Div {
7718 h_flex()
7719 .h_full()
7720 .flex_1()
7721 .gap_2()
7722 .child(Icon::new(IconName::ZedPredict))
7723 }
7724
7725 let completion = match &self.active_inline_completion {
7726 Some(prediction) => {
7727 if !self.has_visible_completions_menu() {
7728 const RADIUS: Pixels = px(6.);
7729 const BORDER_WIDTH: Pixels = px(1.);
7730
7731 return Some(
7732 h_flex()
7733 .elevation_2(cx)
7734 .border(BORDER_WIDTH)
7735 .border_color(cx.theme().colors().border)
7736 .when(accept_keystroke.is_none(), |el| {
7737 el.border_color(cx.theme().status().error)
7738 })
7739 .rounded(RADIUS)
7740 .rounded_tl(px(0.))
7741 .overflow_hidden()
7742 .child(div().px_1p5().child(match &prediction.completion {
7743 InlineCompletion::Move { target, snapshot } => {
7744 use text::ToPoint as _;
7745 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7746 {
7747 Icon::new(IconName::ZedPredictDown)
7748 } else {
7749 Icon::new(IconName::ZedPredictUp)
7750 }
7751 }
7752 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7753 }))
7754 .child(
7755 h_flex()
7756 .gap_1()
7757 .py_1()
7758 .px_2()
7759 .rounded_r(RADIUS - BORDER_WIDTH)
7760 .border_l_1()
7761 .border_color(cx.theme().colors().border)
7762 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7763 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7764 el.child(
7765 Label::new("Hold")
7766 .size(LabelSize::Small)
7767 .when(accept_keystroke.is_none(), |el| {
7768 el.strikethrough()
7769 })
7770 .line_height_style(LineHeightStyle::UiLabel),
7771 )
7772 })
7773 .id("edit_prediction_cursor_popover_keybind")
7774 .when(accept_keystroke.is_none(), |el| {
7775 let status_colors = cx.theme().status();
7776
7777 el.bg(status_colors.error_background)
7778 .border_color(status_colors.error.opacity(0.6))
7779 .child(Icon::new(IconName::Info).color(Color::Error))
7780 .cursor_default()
7781 .hoverable_tooltip(move |_window, cx| {
7782 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7783 .into()
7784 })
7785 })
7786 .when_some(
7787 accept_keystroke.as_ref(),
7788 |el, accept_keystroke| {
7789 el.child(h_flex().children(ui::render_modifiers(
7790 &accept_keystroke.modifiers,
7791 PlatformStyle::platform(),
7792 Some(Color::Default),
7793 Some(IconSize::XSmall.rems().into()),
7794 false,
7795 )))
7796 },
7797 ),
7798 )
7799 .into_any(),
7800 );
7801 }
7802
7803 self.render_edit_prediction_cursor_popover_preview(
7804 prediction,
7805 cursor_point,
7806 style,
7807 cx,
7808 )?
7809 }
7810
7811 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7812 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7813 stale_completion,
7814 cursor_point,
7815 style,
7816 cx,
7817 )?,
7818
7819 None => {
7820 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7821 }
7822 },
7823
7824 None => pending_completion_container().child(Label::new("No Prediction")),
7825 };
7826
7827 let completion = if is_refreshing {
7828 completion
7829 .with_animation(
7830 "loading-completion",
7831 Animation::new(Duration::from_secs(2))
7832 .repeat()
7833 .with_easing(pulsating_between(0.4, 0.8)),
7834 |label, delta| label.opacity(delta),
7835 )
7836 .into_any_element()
7837 } else {
7838 completion.into_any_element()
7839 };
7840
7841 let has_completion = self.active_inline_completion.is_some();
7842
7843 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7844 Some(
7845 h_flex()
7846 .min_w(min_width)
7847 .max_w(max_width)
7848 .flex_1()
7849 .elevation_2(cx)
7850 .border_color(cx.theme().colors().border)
7851 .child(
7852 div()
7853 .flex_1()
7854 .py_1()
7855 .px_2()
7856 .overflow_hidden()
7857 .child(completion),
7858 )
7859 .when_some(accept_keystroke, |el, accept_keystroke| {
7860 if !accept_keystroke.modifiers.modified() {
7861 return el;
7862 }
7863
7864 el.child(
7865 h_flex()
7866 .h_full()
7867 .border_l_1()
7868 .rounded_r_lg()
7869 .border_color(cx.theme().colors().border)
7870 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7871 .gap_1()
7872 .py_1()
7873 .px_2()
7874 .child(
7875 h_flex()
7876 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7877 .when(is_platform_style_mac, |parent| parent.gap_1())
7878 .child(h_flex().children(ui::render_modifiers(
7879 &accept_keystroke.modifiers,
7880 PlatformStyle::platform(),
7881 Some(if !has_completion {
7882 Color::Muted
7883 } else {
7884 Color::Default
7885 }),
7886 None,
7887 false,
7888 ))),
7889 )
7890 .child(Label::new("Preview").into_any_element())
7891 .opacity(if has_completion { 1.0 } else { 0.4 }),
7892 )
7893 })
7894 .into_any(),
7895 )
7896 }
7897
7898 fn render_edit_prediction_cursor_popover_preview(
7899 &self,
7900 completion: &InlineCompletionState,
7901 cursor_point: Point,
7902 style: &EditorStyle,
7903 cx: &mut Context<Editor>,
7904 ) -> Option<Div> {
7905 use text::ToPoint as _;
7906
7907 fn render_relative_row_jump(
7908 prefix: impl Into<String>,
7909 current_row: u32,
7910 target_row: u32,
7911 ) -> Div {
7912 let (row_diff, arrow) = if target_row < current_row {
7913 (current_row - target_row, IconName::ArrowUp)
7914 } else {
7915 (target_row - current_row, IconName::ArrowDown)
7916 };
7917
7918 h_flex()
7919 .child(
7920 Label::new(format!("{}{}", prefix.into(), row_diff))
7921 .color(Color::Muted)
7922 .size(LabelSize::Small),
7923 )
7924 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7925 }
7926
7927 match &completion.completion {
7928 InlineCompletion::Move {
7929 target, snapshot, ..
7930 } => Some(
7931 h_flex()
7932 .px_2()
7933 .gap_2()
7934 .flex_1()
7935 .child(
7936 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7937 Icon::new(IconName::ZedPredictDown)
7938 } else {
7939 Icon::new(IconName::ZedPredictUp)
7940 },
7941 )
7942 .child(Label::new("Jump to Edit")),
7943 ),
7944
7945 InlineCompletion::Edit {
7946 edits,
7947 edit_preview,
7948 snapshot,
7949 display_mode: _,
7950 } => {
7951 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7952
7953 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7954 &snapshot,
7955 &edits,
7956 edit_preview.as_ref()?,
7957 true,
7958 cx,
7959 )
7960 .first_line_preview();
7961
7962 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7963 .with_default_highlights(&style.text, highlighted_edits.highlights);
7964
7965 let preview = h_flex()
7966 .gap_1()
7967 .min_w_16()
7968 .child(styled_text)
7969 .when(has_more_lines, |parent| parent.child("…"));
7970
7971 let left = if first_edit_row != cursor_point.row {
7972 render_relative_row_jump("", cursor_point.row, first_edit_row)
7973 .into_any_element()
7974 } else {
7975 Icon::new(IconName::ZedPredict).into_any_element()
7976 };
7977
7978 Some(
7979 h_flex()
7980 .h_full()
7981 .flex_1()
7982 .gap_2()
7983 .pr_1()
7984 .overflow_x_hidden()
7985 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7986 .child(left)
7987 .child(preview),
7988 )
7989 }
7990 }
7991 }
7992
7993 fn render_context_menu(
7994 &self,
7995 style: &EditorStyle,
7996 max_height_in_lines: u32,
7997 window: &mut Window,
7998 cx: &mut Context<Editor>,
7999 ) -> Option<AnyElement> {
8000 let menu = self.context_menu.borrow();
8001 let menu = menu.as_ref()?;
8002 if !menu.visible() {
8003 return None;
8004 };
8005 Some(menu.render(style, max_height_in_lines, window, cx))
8006 }
8007
8008 fn render_context_menu_aside(
8009 &mut self,
8010 max_size: Size<Pixels>,
8011 window: &mut Window,
8012 cx: &mut Context<Editor>,
8013 ) -> Option<AnyElement> {
8014 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8015 if menu.visible() {
8016 menu.render_aside(self, max_size, window, cx)
8017 } else {
8018 None
8019 }
8020 })
8021 }
8022
8023 fn hide_context_menu(
8024 &mut self,
8025 window: &mut Window,
8026 cx: &mut Context<Self>,
8027 ) -> Option<CodeContextMenu> {
8028 cx.notify();
8029 self.completion_tasks.clear();
8030 let context_menu = self.context_menu.borrow_mut().take();
8031 self.stale_inline_completion_in_menu.take();
8032 self.update_visible_inline_completion(window, cx);
8033 context_menu
8034 }
8035
8036 fn show_snippet_choices(
8037 &mut self,
8038 choices: &Vec<String>,
8039 selection: Range<Anchor>,
8040 cx: &mut Context<Self>,
8041 ) {
8042 if selection.start.buffer_id.is_none() {
8043 return;
8044 }
8045 let buffer_id = selection.start.buffer_id.unwrap();
8046 let buffer = self.buffer().read(cx).buffer(buffer_id);
8047 let id = post_inc(&mut self.next_completion_id);
8048
8049 if let Some(buffer) = buffer {
8050 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8051 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
8052 ));
8053 }
8054 }
8055
8056 pub fn insert_snippet(
8057 &mut self,
8058 insertion_ranges: &[Range<usize>],
8059 snippet: Snippet,
8060 window: &mut Window,
8061 cx: &mut Context<Self>,
8062 ) -> Result<()> {
8063 struct Tabstop<T> {
8064 is_end_tabstop: bool,
8065 ranges: Vec<Range<T>>,
8066 choices: Option<Vec<String>>,
8067 }
8068
8069 let tabstops = self.buffer.update(cx, |buffer, cx| {
8070 let snippet_text: Arc<str> = snippet.text.clone().into();
8071 let edits = insertion_ranges
8072 .iter()
8073 .cloned()
8074 .map(|range| (range, snippet_text.clone()));
8075 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8076
8077 let snapshot = &*buffer.read(cx);
8078 let snippet = &snippet;
8079 snippet
8080 .tabstops
8081 .iter()
8082 .map(|tabstop| {
8083 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8084 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8085 });
8086 let mut tabstop_ranges = tabstop
8087 .ranges
8088 .iter()
8089 .flat_map(|tabstop_range| {
8090 let mut delta = 0_isize;
8091 insertion_ranges.iter().map(move |insertion_range| {
8092 let insertion_start = insertion_range.start as isize + delta;
8093 delta +=
8094 snippet.text.len() as isize - insertion_range.len() as isize;
8095
8096 let start = ((insertion_start + tabstop_range.start) as usize)
8097 .min(snapshot.len());
8098 let end = ((insertion_start + tabstop_range.end) as usize)
8099 .min(snapshot.len());
8100 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8101 })
8102 })
8103 .collect::<Vec<_>>();
8104 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8105
8106 Tabstop {
8107 is_end_tabstop,
8108 ranges: tabstop_ranges,
8109 choices: tabstop.choices.clone(),
8110 }
8111 })
8112 .collect::<Vec<_>>()
8113 });
8114 if let Some(tabstop) = tabstops.first() {
8115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8116 s.select_ranges(tabstop.ranges.iter().cloned());
8117 });
8118
8119 if let Some(choices) = &tabstop.choices {
8120 if let Some(selection) = tabstop.ranges.first() {
8121 self.show_snippet_choices(choices, selection.clone(), cx)
8122 }
8123 }
8124
8125 // If we're already at the last tabstop and it's at the end of the snippet,
8126 // we're done, we don't need to keep the state around.
8127 if !tabstop.is_end_tabstop {
8128 let choices = tabstops
8129 .iter()
8130 .map(|tabstop| tabstop.choices.clone())
8131 .collect();
8132
8133 let ranges = tabstops
8134 .into_iter()
8135 .map(|tabstop| tabstop.ranges)
8136 .collect::<Vec<_>>();
8137
8138 self.snippet_stack.push(SnippetState {
8139 active_index: 0,
8140 ranges,
8141 choices,
8142 });
8143 }
8144
8145 // Check whether the just-entered snippet ends with an auto-closable bracket.
8146 if self.autoclose_regions.is_empty() {
8147 let snapshot = self.buffer.read(cx).snapshot(cx);
8148 for selection in &mut self.selections.all::<Point>(cx) {
8149 let selection_head = selection.head();
8150 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8151 continue;
8152 };
8153
8154 let mut bracket_pair = None;
8155 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8156 let prev_chars = snapshot
8157 .reversed_chars_at(selection_head)
8158 .collect::<String>();
8159 for (pair, enabled) in scope.brackets() {
8160 if enabled
8161 && pair.close
8162 && prev_chars.starts_with(pair.start.as_str())
8163 && next_chars.starts_with(pair.end.as_str())
8164 {
8165 bracket_pair = Some(pair.clone());
8166 break;
8167 }
8168 }
8169 if let Some(pair) = bracket_pair {
8170 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8171 let autoclose_enabled =
8172 self.use_autoclose && snapshot_settings.use_autoclose;
8173 if autoclose_enabled {
8174 let start = snapshot.anchor_after(selection_head);
8175 let end = snapshot.anchor_after(selection_head);
8176 self.autoclose_regions.push(AutocloseRegion {
8177 selection_id: selection.id,
8178 range: start..end,
8179 pair,
8180 });
8181 }
8182 }
8183 }
8184 }
8185 }
8186 Ok(())
8187 }
8188
8189 pub fn move_to_next_snippet_tabstop(
8190 &mut self,
8191 window: &mut Window,
8192 cx: &mut Context<Self>,
8193 ) -> bool {
8194 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8195 }
8196
8197 pub fn move_to_prev_snippet_tabstop(
8198 &mut self,
8199 window: &mut Window,
8200 cx: &mut Context<Self>,
8201 ) -> bool {
8202 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8203 }
8204
8205 pub fn move_to_snippet_tabstop(
8206 &mut self,
8207 bias: Bias,
8208 window: &mut Window,
8209 cx: &mut Context<Self>,
8210 ) -> bool {
8211 if let Some(mut snippet) = self.snippet_stack.pop() {
8212 match bias {
8213 Bias::Left => {
8214 if snippet.active_index > 0 {
8215 snippet.active_index -= 1;
8216 } else {
8217 self.snippet_stack.push(snippet);
8218 return false;
8219 }
8220 }
8221 Bias::Right => {
8222 if snippet.active_index + 1 < snippet.ranges.len() {
8223 snippet.active_index += 1;
8224 } else {
8225 self.snippet_stack.push(snippet);
8226 return false;
8227 }
8228 }
8229 }
8230 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8232 s.select_anchor_ranges(current_ranges.iter().cloned())
8233 });
8234
8235 if let Some(choices) = &snippet.choices[snippet.active_index] {
8236 if let Some(selection) = current_ranges.first() {
8237 self.show_snippet_choices(&choices, selection.clone(), cx);
8238 }
8239 }
8240
8241 // If snippet state is not at the last tabstop, push it back on the stack
8242 if snippet.active_index + 1 < snippet.ranges.len() {
8243 self.snippet_stack.push(snippet);
8244 }
8245 return true;
8246 }
8247 }
8248
8249 false
8250 }
8251
8252 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8253 self.transact(window, cx, |this, window, cx| {
8254 this.select_all(&SelectAll, window, cx);
8255 this.insert("", window, cx);
8256 });
8257 }
8258
8259 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8260 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8261 self.transact(window, cx, |this, window, cx| {
8262 this.select_autoclose_pair(window, cx);
8263 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8264 if !this.linked_edit_ranges.is_empty() {
8265 let selections = this.selections.all::<MultiBufferPoint>(cx);
8266 let snapshot = this.buffer.read(cx).snapshot(cx);
8267
8268 for selection in selections.iter() {
8269 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8270 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8271 if selection_start.buffer_id != selection_end.buffer_id {
8272 continue;
8273 }
8274 if let Some(ranges) =
8275 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8276 {
8277 for (buffer, entries) in ranges {
8278 linked_ranges.entry(buffer).or_default().extend(entries);
8279 }
8280 }
8281 }
8282 }
8283
8284 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8285 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8286 for selection in &mut selections {
8287 if selection.is_empty() {
8288 let old_head = selection.head();
8289 let mut new_head =
8290 movement::left(&display_map, old_head.to_display_point(&display_map))
8291 .to_point(&display_map);
8292 if let Some((buffer, line_buffer_range)) = display_map
8293 .buffer_snapshot
8294 .buffer_line_for_row(MultiBufferRow(old_head.row))
8295 {
8296 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8297 let indent_len = match indent_size.kind {
8298 IndentKind::Space => {
8299 buffer.settings_at(line_buffer_range.start, cx).tab_size
8300 }
8301 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8302 };
8303 if old_head.column <= indent_size.len && old_head.column > 0 {
8304 let indent_len = indent_len.get();
8305 new_head = cmp::min(
8306 new_head,
8307 MultiBufferPoint::new(
8308 old_head.row,
8309 ((old_head.column - 1) / indent_len) * indent_len,
8310 ),
8311 );
8312 }
8313 }
8314
8315 selection.set_head(new_head, SelectionGoal::None);
8316 }
8317 }
8318
8319 this.signature_help_state.set_backspace_pressed(true);
8320 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8321 s.select(selections)
8322 });
8323 this.insert("", window, cx);
8324 let empty_str: Arc<str> = Arc::from("");
8325 for (buffer, edits) in linked_ranges {
8326 let snapshot = buffer.read(cx).snapshot();
8327 use text::ToPoint as TP;
8328
8329 let edits = edits
8330 .into_iter()
8331 .map(|range| {
8332 let end_point = TP::to_point(&range.end, &snapshot);
8333 let mut start_point = TP::to_point(&range.start, &snapshot);
8334
8335 if end_point == start_point {
8336 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8337 .saturating_sub(1);
8338 start_point =
8339 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8340 };
8341
8342 (start_point..end_point, empty_str.clone())
8343 })
8344 .sorted_by_key(|(range, _)| range.start)
8345 .collect::<Vec<_>>();
8346 buffer.update(cx, |this, cx| {
8347 this.edit(edits, None, cx);
8348 })
8349 }
8350 this.refresh_inline_completion(true, false, window, cx);
8351 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8352 });
8353 }
8354
8355 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8356 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8357 self.transact(window, cx, |this, window, cx| {
8358 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8359 s.move_with(|map, selection| {
8360 if selection.is_empty() {
8361 let cursor = movement::right(map, selection.head());
8362 selection.end = cursor;
8363 selection.reversed = true;
8364 selection.goal = SelectionGoal::None;
8365 }
8366 })
8367 });
8368 this.insert("", window, cx);
8369 this.refresh_inline_completion(true, false, window, cx);
8370 });
8371 }
8372
8373 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8374 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8375 if self.move_to_prev_snippet_tabstop(window, cx) {
8376 return;
8377 }
8378 self.outdent(&Outdent, window, cx);
8379 }
8380
8381 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8382 if self.move_to_next_snippet_tabstop(window, cx) {
8383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8384 return;
8385 }
8386 if self.read_only(cx) {
8387 return;
8388 }
8389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8390 let mut selections = self.selections.all_adjusted(cx);
8391 let buffer = self.buffer.read(cx);
8392 let snapshot = buffer.snapshot(cx);
8393 let rows_iter = selections.iter().map(|s| s.head().row);
8394 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8395
8396 let mut edits = Vec::new();
8397 let mut prev_edited_row = 0;
8398 let mut row_delta = 0;
8399 for selection in &mut selections {
8400 if selection.start.row != prev_edited_row {
8401 row_delta = 0;
8402 }
8403 prev_edited_row = selection.end.row;
8404
8405 // If the selection is non-empty, then increase the indentation of the selected lines.
8406 if !selection.is_empty() {
8407 row_delta =
8408 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8409 continue;
8410 }
8411
8412 // If the selection is empty and the cursor is in the leading whitespace before the
8413 // suggested indentation, then auto-indent the line.
8414 let cursor = selection.head();
8415 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8416 if let Some(suggested_indent) =
8417 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8418 {
8419 if cursor.column < suggested_indent.len
8420 && cursor.column <= current_indent.len
8421 && current_indent.len <= suggested_indent.len
8422 {
8423 selection.start = Point::new(cursor.row, suggested_indent.len);
8424 selection.end = selection.start;
8425 if row_delta == 0 {
8426 edits.extend(Buffer::edit_for_indent_size_adjustment(
8427 cursor.row,
8428 current_indent,
8429 suggested_indent,
8430 ));
8431 row_delta = suggested_indent.len - current_indent.len;
8432 }
8433 continue;
8434 }
8435 }
8436
8437 // Otherwise, insert a hard or soft tab.
8438 let settings = buffer.language_settings_at(cursor, cx);
8439 let tab_size = if settings.hard_tabs {
8440 IndentSize::tab()
8441 } else {
8442 let tab_size = settings.tab_size.get();
8443 let indent_remainder = snapshot
8444 .text_for_range(Point::new(cursor.row, 0)..cursor)
8445 .flat_map(str::chars)
8446 .fold(row_delta % tab_size, |counter: u32, c| {
8447 if c == '\t' {
8448 0
8449 } else {
8450 (counter + 1) % tab_size
8451 }
8452 });
8453
8454 let chars_to_next_tab_stop = tab_size - indent_remainder;
8455 IndentSize::spaces(chars_to_next_tab_stop)
8456 };
8457 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8458 selection.end = selection.start;
8459 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8460 row_delta += tab_size.len;
8461 }
8462
8463 self.transact(window, cx, |this, window, cx| {
8464 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8465 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8466 s.select(selections)
8467 });
8468 this.refresh_inline_completion(true, false, window, cx);
8469 });
8470 }
8471
8472 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8473 if self.read_only(cx) {
8474 return;
8475 }
8476 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8477 let mut selections = self.selections.all::<Point>(cx);
8478 let mut prev_edited_row = 0;
8479 let mut row_delta = 0;
8480 let mut edits = Vec::new();
8481 let buffer = self.buffer.read(cx);
8482 let snapshot = buffer.snapshot(cx);
8483 for selection in &mut selections {
8484 if selection.start.row != prev_edited_row {
8485 row_delta = 0;
8486 }
8487 prev_edited_row = selection.end.row;
8488
8489 row_delta =
8490 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8491 }
8492
8493 self.transact(window, cx, |this, window, cx| {
8494 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8495 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8496 s.select(selections)
8497 });
8498 });
8499 }
8500
8501 fn indent_selection(
8502 buffer: &MultiBuffer,
8503 snapshot: &MultiBufferSnapshot,
8504 selection: &mut Selection<Point>,
8505 edits: &mut Vec<(Range<Point>, String)>,
8506 delta_for_start_row: u32,
8507 cx: &App,
8508 ) -> u32 {
8509 let settings = buffer.language_settings_at(selection.start, cx);
8510 let tab_size = settings.tab_size.get();
8511 let indent_kind = if settings.hard_tabs {
8512 IndentKind::Tab
8513 } else {
8514 IndentKind::Space
8515 };
8516 let mut start_row = selection.start.row;
8517 let mut end_row = selection.end.row + 1;
8518
8519 // If a selection ends at the beginning of a line, don't indent
8520 // that last line.
8521 if selection.end.column == 0 && selection.end.row > selection.start.row {
8522 end_row -= 1;
8523 }
8524
8525 // Avoid re-indenting a row that has already been indented by a
8526 // previous selection, but still update this selection's column
8527 // to reflect that indentation.
8528 if delta_for_start_row > 0 {
8529 start_row += 1;
8530 selection.start.column += delta_for_start_row;
8531 if selection.end.row == selection.start.row {
8532 selection.end.column += delta_for_start_row;
8533 }
8534 }
8535
8536 let mut delta_for_end_row = 0;
8537 let has_multiple_rows = start_row + 1 != end_row;
8538 for row in start_row..end_row {
8539 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8540 let indent_delta = match (current_indent.kind, indent_kind) {
8541 (IndentKind::Space, IndentKind::Space) => {
8542 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8543 IndentSize::spaces(columns_to_next_tab_stop)
8544 }
8545 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8546 (_, IndentKind::Tab) => IndentSize::tab(),
8547 };
8548
8549 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8550 0
8551 } else {
8552 selection.start.column
8553 };
8554 let row_start = Point::new(row, start);
8555 edits.push((
8556 row_start..row_start,
8557 indent_delta.chars().collect::<String>(),
8558 ));
8559
8560 // Update this selection's endpoints to reflect the indentation.
8561 if row == selection.start.row {
8562 selection.start.column += indent_delta.len;
8563 }
8564 if row == selection.end.row {
8565 selection.end.column += indent_delta.len;
8566 delta_for_end_row = indent_delta.len;
8567 }
8568 }
8569
8570 if selection.start.row == selection.end.row {
8571 delta_for_start_row + delta_for_end_row
8572 } else {
8573 delta_for_end_row
8574 }
8575 }
8576
8577 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8578 if self.read_only(cx) {
8579 return;
8580 }
8581 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8583 let selections = self.selections.all::<Point>(cx);
8584 let mut deletion_ranges = Vec::new();
8585 let mut last_outdent = None;
8586 {
8587 let buffer = self.buffer.read(cx);
8588 let snapshot = buffer.snapshot(cx);
8589 for selection in &selections {
8590 let settings = buffer.language_settings_at(selection.start, cx);
8591 let tab_size = settings.tab_size.get();
8592 let mut rows = selection.spanned_rows(false, &display_map);
8593
8594 // Avoid re-outdenting a row that has already been outdented by a
8595 // previous selection.
8596 if let Some(last_row) = last_outdent {
8597 if last_row == rows.start {
8598 rows.start = rows.start.next_row();
8599 }
8600 }
8601 let has_multiple_rows = rows.len() > 1;
8602 for row in rows.iter_rows() {
8603 let indent_size = snapshot.indent_size_for_line(row);
8604 if indent_size.len > 0 {
8605 let deletion_len = match indent_size.kind {
8606 IndentKind::Space => {
8607 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8608 if columns_to_prev_tab_stop == 0 {
8609 tab_size
8610 } else {
8611 columns_to_prev_tab_stop
8612 }
8613 }
8614 IndentKind::Tab => 1,
8615 };
8616 let start = if has_multiple_rows
8617 || deletion_len > selection.start.column
8618 || indent_size.len < selection.start.column
8619 {
8620 0
8621 } else {
8622 selection.start.column - deletion_len
8623 };
8624 deletion_ranges.push(
8625 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8626 );
8627 last_outdent = Some(row);
8628 }
8629 }
8630 }
8631 }
8632
8633 self.transact(window, cx, |this, window, cx| {
8634 this.buffer.update(cx, |buffer, cx| {
8635 let empty_str: Arc<str> = Arc::default();
8636 buffer.edit(
8637 deletion_ranges
8638 .into_iter()
8639 .map(|range| (range, empty_str.clone())),
8640 None,
8641 cx,
8642 );
8643 });
8644 let selections = this.selections.all::<usize>(cx);
8645 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8646 s.select(selections)
8647 });
8648 });
8649 }
8650
8651 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8652 if self.read_only(cx) {
8653 return;
8654 }
8655 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8656 let selections = self
8657 .selections
8658 .all::<usize>(cx)
8659 .into_iter()
8660 .map(|s| s.range());
8661
8662 self.transact(window, cx, |this, window, cx| {
8663 this.buffer.update(cx, |buffer, cx| {
8664 buffer.autoindent_ranges(selections, cx);
8665 });
8666 let selections = this.selections.all::<usize>(cx);
8667 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8668 s.select(selections)
8669 });
8670 });
8671 }
8672
8673 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8674 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8676 let selections = self.selections.all::<Point>(cx);
8677
8678 let mut new_cursors = Vec::new();
8679 let mut edit_ranges = Vec::new();
8680 let mut selections = selections.iter().peekable();
8681 while let Some(selection) = selections.next() {
8682 let mut rows = selection.spanned_rows(false, &display_map);
8683 let goal_display_column = selection.head().to_display_point(&display_map).column();
8684
8685 // Accumulate contiguous regions of rows that we want to delete.
8686 while let Some(next_selection) = selections.peek() {
8687 let next_rows = next_selection.spanned_rows(false, &display_map);
8688 if next_rows.start <= rows.end {
8689 rows.end = next_rows.end;
8690 selections.next().unwrap();
8691 } else {
8692 break;
8693 }
8694 }
8695
8696 let buffer = &display_map.buffer_snapshot;
8697 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8698 let edit_end;
8699 let cursor_buffer_row;
8700 if buffer.max_point().row >= rows.end.0 {
8701 // If there's a line after the range, delete the \n from the end of the row range
8702 // and position the cursor on the next line.
8703 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8704 cursor_buffer_row = rows.end;
8705 } else {
8706 // If there isn't a line after the range, delete the \n from the line before the
8707 // start of the row range and position the cursor there.
8708 edit_start = edit_start.saturating_sub(1);
8709 edit_end = buffer.len();
8710 cursor_buffer_row = rows.start.previous_row();
8711 }
8712
8713 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8714 *cursor.column_mut() =
8715 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8716
8717 new_cursors.push((
8718 selection.id,
8719 buffer.anchor_after(cursor.to_point(&display_map)),
8720 ));
8721 edit_ranges.push(edit_start..edit_end);
8722 }
8723
8724 self.transact(window, cx, |this, window, cx| {
8725 let buffer = this.buffer.update(cx, |buffer, cx| {
8726 let empty_str: Arc<str> = Arc::default();
8727 buffer.edit(
8728 edit_ranges
8729 .into_iter()
8730 .map(|range| (range, empty_str.clone())),
8731 None,
8732 cx,
8733 );
8734 buffer.snapshot(cx)
8735 });
8736 let new_selections = new_cursors
8737 .into_iter()
8738 .map(|(id, cursor)| {
8739 let cursor = cursor.to_point(&buffer);
8740 Selection {
8741 id,
8742 start: cursor,
8743 end: cursor,
8744 reversed: false,
8745 goal: SelectionGoal::None,
8746 }
8747 })
8748 .collect();
8749
8750 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8751 s.select(new_selections);
8752 });
8753 });
8754 }
8755
8756 pub fn join_lines_impl(
8757 &mut self,
8758 insert_whitespace: bool,
8759 window: &mut Window,
8760 cx: &mut Context<Self>,
8761 ) {
8762 if self.read_only(cx) {
8763 return;
8764 }
8765 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8766 for selection in self.selections.all::<Point>(cx) {
8767 let start = MultiBufferRow(selection.start.row);
8768 // Treat single line selections as if they include the next line. Otherwise this action
8769 // would do nothing for single line selections individual cursors.
8770 let end = if selection.start.row == selection.end.row {
8771 MultiBufferRow(selection.start.row + 1)
8772 } else {
8773 MultiBufferRow(selection.end.row)
8774 };
8775
8776 if let Some(last_row_range) = row_ranges.last_mut() {
8777 if start <= last_row_range.end {
8778 last_row_range.end = end;
8779 continue;
8780 }
8781 }
8782 row_ranges.push(start..end);
8783 }
8784
8785 let snapshot = self.buffer.read(cx).snapshot(cx);
8786 let mut cursor_positions = Vec::new();
8787 for row_range in &row_ranges {
8788 let anchor = snapshot.anchor_before(Point::new(
8789 row_range.end.previous_row().0,
8790 snapshot.line_len(row_range.end.previous_row()),
8791 ));
8792 cursor_positions.push(anchor..anchor);
8793 }
8794
8795 self.transact(window, cx, |this, window, cx| {
8796 for row_range in row_ranges.into_iter().rev() {
8797 for row in row_range.iter_rows().rev() {
8798 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8799 let next_line_row = row.next_row();
8800 let indent = snapshot.indent_size_for_line(next_line_row);
8801 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8802
8803 let replace =
8804 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8805 " "
8806 } else {
8807 ""
8808 };
8809
8810 this.buffer.update(cx, |buffer, cx| {
8811 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8812 });
8813 }
8814 }
8815
8816 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8817 s.select_anchor_ranges(cursor_positions)
8818 });
8819 });
8820 }
8821
8822 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8823 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8824 self.join_lines_impl(true, window, cx);
8825 }
8826
8827 pub fn sort_lines_case_sensitive(
8828 &mut self,
8829 _: &SortLinesCaseSensitive,
8830 window: &mut Window,
8831 cx: &mut Context<Self>,
8832 ) {
8833 self.manipulate_lines(window, cx, |lines| lines.sort())
8834 }
8835
8836 pub fn sort_lines_case_insensitive(
8837 &mut self,
8838 _: &SortLinesCaseInsensitive,
8839 window: &mut Window,
8840 cx: &mut Context<Self>,
8841 ) {
8842 self.manipulate_lines(window, cx, |lines| {
8843 lines.sort_by_key(|line| line.to_lowercase())
8844 })
8845 }
8846
8847 pub fn unique_lines_case_insensitive(
8848 &mut self,
8849 _: &UniqueLinesCaseInsensitive,
8850 window: &mut Window,
8851 cx: &mut Context<Self>,
8852 ) {
8853 self.manipulate_lines(window, cx, |lines| {
8854 let mut seen = HashSet::default();
8855 lines.retain(|line| seen.insert(line.to_lowercase()));
8856 })
8857 }
8858
8859 pub fn unique_lines_case_sensitive(
8860 &mut self,
8861 _: &UniqueLinesCaseSensitive,
8862 window: &mut Window,
8863 cx: &mut Context<Self>,
8864 ) {
8865 self.manipulate_lines(window, cx, |lines| {
8866 let mut seen = HashSet::default();
8867 lines.retain(|line| seen.insert(*line));
8868 })
8869 }
8870
8871 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8872 let Some(project) = self.project.clone() else {
8873 return;
8874 };
8875 self.reload(project, window, cx)
8876 .detach_and_notify_err(window, cx);
8877 }
8878
8879 pub fn restore_file(
8880 &mut self,
8881 _: &::git::RestoreFile,
8882 window: &mut Window,
8883 cx: &mut Context<Self>,
8884 ) {
8885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8886 let mut buffer_ids = HashSet::default();
8887 let snapshot = self.buffer().read(cx).snapshot(cx);
8888 for selection in self.selections.all::<usize>(cx) {
8889 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8890 }
8891
8892 let buffer = self.buffer().read(cx);
8893 let ranges = buffer_ids
8894 .into_iter()
8895 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8896 .collect::<Vec<_>>();
8897
8898 self.restore_hunks_in_ranges(ranges, window, cx);
8899 }
8900
8901 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8902 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8903 let selections = self
8904 .selections
8905 .all(cx)
8906 .into_iter()
8907 .map(|s| s.range())
8908 .collect();
8909 self.restore_hunks_in_ranges(selections, window, cx);
8910 }
8911
8912 pub fn restore_hunks_in_ranges(
8913 &mut self,
8914 ranges: Vec<Range<Point>>,
8915 window: &mut Window,
8916 cx: &mut Context<Editor>,
8917 ) {
8918 let mut revert_changes = HashMap::default();
8919 let chunk_by = self
8920 .snapshot(window, cx)
8921 .hunks_for_ranges(ranges)
8922 .into_iter()
8923 .chunk_by(|hunk| hunk.buffer_id);
8924 for (buffer_id, hunks) in &chunk_by {
8925 let hunks = hunks.collect::<Vec<_>>();
8926 for hunk in &hunks {
8927 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8928 }
8929 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8930 }
8931 drop(chunk_by);
8932 if !revert_changes.is_empty() {
8933 self.transact(window, cx, |editor, window, cx| {
8934 editor.restore(revert_changes, window, cx);
8935 });
8936 }
8937 }
8938
8939 pub fn open_active_item_in_terminal(
8940 &mut self,
8941 _: &OpenInTerminal,
8942 window: &mut Window,
8943 cx: &mut Context<Self>,
8944 ) {
8945 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8946 let project_path = buffer.read(cx).project_path(cx)?;
8947 let project = self.project.as_ref()?.read(cx);
8948 let entry = project.entry_for_path(&project_path, cx)?;
8949 let parent = match &entry.canonical_path {
8950 Some(canonical_path) => canonical_path.to_path_buf(),
8951 None => project.absolute_path(&project_path, cx)?,
8952 }
8953 .parent()?
8954 .to_path_buf();
8955 Some(parent)
8956 }) {
8957 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8958 }
8959 }
8960
8961 fn set_breakpoint_context_menu(
8962 &mut self,
8963 display_row: DisplayRow,
8964 position: Option<Anchor>,
8965 clicked_point: gpui::Point<Pixels>,
8966 window: &mut Window,
8967 cx: &mut Context<Self>,
8968 ) {
8969 if !cx.has_flag::<Debugger>() {
8970 return;
8971 }
8972 let source = self
8973 .buffer
8974 .read(cx)
8975 .snapshot(cx)
8976 .anchor_before(Point::new(display_row.0, 0u32));
8977
8978 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8979
8980 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8981 self,
8982 source,
8983 clicked_point,
8984 context_menu,
8985 window,
8986 cx,
8987 );
8988 }
8989
8990 fn add_edit_breakpoint_block(
8991 &mut self,
8992 anchor: Anchor,
8993 breakpoint: &Breakpoint,
8994 edit_action: BreakpointPromptEditAction,
8995 window: &mut Window,
8996 cx: &mut Context<Self>,
8997 ) {
8998 let weak_editor = cx.weak_entity();
8999 let bp_prompt = cx.new(|cx| {
9000 BreakpointPromptEditor::new(
9001 weak_editor,
9002 anchor,
9003 breakpoint.clone(),
9004 edit_action,
9005 window,
9006 cx,
9007 )
9008 });
9009
9010 let height = bp_prompt.update(cx, |this, cx| {
9011 this.prompt
9012 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9013 });
9014 let cloned_prompt = bp_prompt.clone();
9015 let blocks = vec![BlockProperties {
9016 style: BlockStyle::Sticky,
9017 placement: BlockPlacement::Above(anchor),
9018 height: Some(height),
9019 render: Arc::new(move |cx| {
9020 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9021 cloned_prompt.clone().into_any_element()
9022 }),
9023 priority: 0,
9024 }];
9025
9026 let focus_handle = bp_prompt.focus_handle(cx);
9027 window.focus(&focus_handle);
9028
9029 let block_ids = self.insert_blocks(blocks, None, cx);
9030 bp_prompt.update(cx, |prompt, _| {
9031 prompt.add_block_ids(block_ids);
9032 });
9033 }
9034
9035 pub(crate) fn breakpoint_at_row(
9036 &self,
9037 row: u32,
9038 window: &mut Window,
9039 cx: &mut Context<Self>,
9040 ) -> Option<(Anchor, Breakpoint)> {
9041 let snapshot = self.snapshot(window, cx);
9042 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9043
9044 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9045 }
9046
9047 pub(crate) fn breakpoint_at_anchor(
9048 &self,
9049 breakpoint_position: Anchor,
9050 snapshot: &EditorSnapshot,
9051 cx: &mut Context<Self>,
9052 ) -> Option<(Anchor, Breakpoint)> {
9053 let project = self.project.clone()?;
9054
9055 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9056 snapshot
9057 .buffer_snapshot
9058 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9059 })?;
9060
9061 let enclosing_excerpt = breakpoint_position.excerpt_id;
9062 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9063 let buffer_snapshot = buffer.read(cx).snapshot();
9064
9065 let row = buffer_snapshot
9066 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9067 .row;
9068
9069 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9070 let anchor_end = snapshot
9071 .buffer_snapshot
9072 .anchor_after(Point::new(row, line_len));
9073
9074 let bp = self
9075 .breakpoint_store
9076 .as_ref()?
9077 .read_with(cx, |breakpoint_store, cx| {
9078 breakpoint_store
9079 .breakpoints(
9080 &buffer,
9081 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9082 &buffer_snapshot,
9083 cx,
9084 )
9085 .next()
9086 .and_then(|(anchor, bp)| {
9087 let breakpoint_row = buffer_snapshot
9088 .summary_for_anchor::<text::PointUtf16>(anchor)
9089 .row;
9090
9091 if breakpoint_row == row {
9092 snapshot
9093 .buffer_snapshot
9094 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9095 .map(|anchor| (anchor, bp.clone()))
9096 } else {
9097 None
9098 }
9099 })
9100 });
9101 bp
9102 }
9103
9104 pub fn edit_log_breakpoint(
9105 &mut self,
9106 _: &EditLogBreakpoint,
9107 window: &mut Window,
9108 cx: &mut Context<Self>,
9109 ) {
9110 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9111 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9112 message: None,
9113 state: BreakpointState::Enabled,
9114 condition: None,
9115 hit_condition: None,
9116 });
9117
9118 self.add_edit_breakpoint_block(
9119 anchor,
9120 &breakpoint,
9121 BreakpointPromptEditAction::Log,
9122 window,
9123 cx,
9124 );
9125 }
9126 }
9127
9128 fn breakpoints_at_cursors(
9129 &self,
9130 window: &mut Window,
9131 cx: &mut Context<Self>,
9132 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9133 let snapshot = self.snapshot(window, cx);
9134 let cursors = self
9135 .selections
9136 .disjoint_anchors()
9137 .into_iter()
9138 .map(|selection| {
9139 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9140
9141 let breakpoint_position = self
9142 .breakpoint_at_row(cursor_position.row, window, cx)
9143 .map(|bp| bp.0)
9144 .unwrap_or_else(|| {
9145 snapshot
9146 .display_snapshot
9147 .buffer_snapshot
9148 .anchor_after(Point::new(cursor_position.row, 0))
9149 });
9150
9151 let breakpoint = self
9152 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9153 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9154
9155 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9156 })
9157 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
9158 .collect::<HashMap<Anchor, _>>();
9159
9160 cursors.into_iter().collect()
9161 }
9162
9163 pub fn enable_breakpoint(
9164 &mut self,
9165 _: &crate::actions::EnableBreakpoint,
9166 window: &mut Window,
9167 cx: &mut Context<Self>,
9168 ) {
9169 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9170 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9171 continue;
9172 };
9173 self.edit_breakpoint_at_anchor(
9174 anchor,
9175 breakpoint,
9176 BreakpointEditAction::InvertState,
9177 cx,
9178 );
9179 }
9180 }
9181
9182 pub fn disable_breakpoint(
9183 &mut self,
9184 _: &crate::actions::DisableBreakpoint,
9185 window: &mut Window,
9186 cx: &mut Context<Self>,
9187 ) {
9188 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9189 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9190 continue;
9191 };
9192 self.edit_breakpoint_at_anchor(
9193 anchor,
9194 breakpoint,
9195 BreakpointEditAction::InvertState,
9196 cx,
9197 );
9198 }
9199 }
9200
9201 pub fn toggle_breakpoint(
9202 &mut self,
9203 _: &crate::actions::ToggleBreakpoint,
9204 window: &mut Window,
9205 cx: &mut Context<Self>,
9206 ) {
9207 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9208 if let Some(breakpoint) = breakpoint {
9209 self.edit_breakpoint_at_anchor(
9210 anchor,
9211 breakpoint,
9212 BreakpointEditAction::Toggle,
9213 cx,
9214 );
9215 } else {
9216 self.edit_breakpoint_at_anchor(
9217 anchor,
9218 Breakpoint::new_standard(),
9219 BreakpointEditAction::Toggle,
9220 cx,
9221 );
9222 }
9223 }
9224 }
9225
9226 pub fn edit_breakpoint_at_anchor(
9227 &mut self,
9228 breakpoint_position: Anchor,
9229 breakpoint: Breakpoint,
9230 edit_action: BreakpointEditAction,
9231 cx: &mut Context<Self>,
9232 ) {
9233 let Some(breakpoint_store) = &self.breakpoint_store else {
9234 return;
9235 };
9236
9237 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9238 if breakpoint_position == Anchor::min() {
9239 self.buffer()
9240 .read(cx)
9241 .excerpt_buffer_ids()
9242 .into_iter()
9243 .next()
9244 } else {
9245 None
9246 }
9247 }) else {
9248 return;
9249 };
9250
9251 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9252 return;
9253 };
9254
9255 breakpoint_store.update(cx, |breakpoint_store, cx| {
9256 breakpoint_store.toggle_breakpoint(
9257 buffer,
9258 (breakpoint_position.text_anchor, breakpoint),
9259 edit_action,
9260 cx,
9261 );
9262 });
9263
9264 cx.notify();
9265 }
9266
9267 #[cfg(any(test, feature = "test-support"))]
9268 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9269 self.breakpoint_store.clone()
9270 }
9271
9272 pub fn prepare_restore_change(
9273 &self,
9274 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9275 hunk: &MultiBufferDiffHunk,
9276 cx: &mut App,
9277 ) -> Option<()> {
9278 if hunk.is_created_file() {
9279 return None;
9280 }
9281 let buffer = self.buffer.read(cx);
9282 let diff = buffer.diff_for(hunk.buffer_id)?;
9283 let buffer = buffer.buffer(hunk.buffer_id)?;
9284 let buffer = buffer.read(cx);
9285 let original_text = diff
9286 .read(cx)
9287 .base_text()
9288 .as_rope()
9289 .slice(hunk.diff_base_byte_range.clone());
9290 let buffer_snapshot = buffer.snapshot();
9291 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9292 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9293 probe
9294 .0
9295 .start
9296 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9297 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9298 }) {
9299 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9300 Some(())
9301 } else {
9302 None
9303 }
9304 }
9305
9306 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9307 self.manipulate_lines(window, cx, |lines| lines.reverse())
9308 }
9309
9310 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9311 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9312 }
9313
9314 fn manipulate_lines<Fn>(
9315 &mut self,
9316 window: &mut Window,
9317 cx: &mut Context<Self>,
9318 mut callback: Fn,
9319 ) where
9320 Fn: FnMut(&mut Vec<&str>),
9321 {
9322 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9323
9324 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9325 let buffer = self.buffer.read(cx).snapshot(cx);
9326
9327 let mut edits = Vec::new();
9328
9329 let selections = self.selections.all::<Point>(cx);
9330 let mut selections = selections.iter().peekable();
9331 let mut contiguous_row_selections = Vec::new();
9332 let mut new_selections = Vec::new();
9333 let mut added_lines = 0;
9334 let mut removed_lines = 0;
9335
9336 while let Some(selection) = selections.next() {
9337 let (start_row, end_row) = consume_contiguous_rows(
9338 &mut contiguous_row_selections,
9339 selection,
9340 &display_map,
9341 &mut selections,
9342 );
9343
9344 let start_point = Point::new(start_row.0, 0);
9345 let end_point = Point::new(
9346 end_row.previous_row().0,
9347 buffer.line_len(end_row.previous_row()),
9348 );
9349 let text = buffer
9350 .text_for_range(start_point..end_point)
9351 .collect::<String>();
9352
9353 let mut lines = text.split('\n').collect_vec();
9354
9355 let lines_before = lines.len();
9356 callback(&mut lines);
9357 let lines_after = lines.len();
9358
9359 edits.push((start_point..end_point, lines.join("\n")));
9360
9361 // Selections must change based on added and removed line count
9362 let start_row =
9363 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9364 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9365 new_selections.push(Selection {
9366 id: selection.id,
9367 start: start_row,
9368 end: end_row,
9369 goal: SelectionGoal::None,
9370 reversed: selection.reversed,
9371 });
9372
9373 if lines_after > lines_before {
9374 added_lines += lines_after - lines_before;
9375 } else if lines_before > lines_after {
9376 removed_lines += lines_before - lines_after;
9377 }
9378 }
9379
9380 self.transact(window, cx, |this, window, cx| {
9381 let buffer = this.buffer.update(cx, |buffer, cx| {
9382 buffer.edit(edits, None, cx);
9383 buffer.snapshot(cx)
9384 });
9385
9386 // Recalculate offsets on newly edited buffer
9387 let new_selections = new_selections
9388 .iter()
9389 .map(|s| {
9390 let start_point = Point::new(s.start.0, 0);
9391 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9392 Selection {
9393 id: s.id,
9394 start: buffer.point_to_offset(start_point),
9395 end: buffer.point_to_offset(end_point),
9396 goal: s.goal,
9397 reversed: s.reversed,
9398 }
9399 })
9400 .collect();
9401
9402 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9403 s.select(new_selections);
9404 });
9405
9406 this.request_autoscroll(Autoscroll::fit(), cx);
9407 });
9408 }
9409
9410 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9411 self.manipulate_text(window, cx, |text| {
9412 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9413 if has_upper_case_characters {
9414 text.to_lowercase()
9415 } else {
9416 text.to_uppercase()
9417 }
9418 })
9419 }
9420
9421 pub fn convert_to_upper_case(
9422 &mut self,
9423 _: &ConvertToUpperCase,
9424 window: &mut Window,
9425 cx: &mut Context<Self>,
9426 ) {
9427 self.manipulate_text(window, cx, |text| text.to_uppercase())
9428 }
9429
9430 pub fn convert_to_lower_case(
9431 &mut self,
9432 _: &ConvertToLowerCase,
9433 window: &mut Window,
9434 cx: &mut Context<Self>,
9435 ) {
9436 self.manipulate_text(window, cx, |text| text.to_lowercase())
9437 }
9438
9439 pub fn convert_to_title_case(
9440 &mut self,
9441 _: &ConvertToTitleCase,
9442 window: &mut Window,
9443 cx: &mut Context<Self>,
9444 ) {
9445 self.manipulate_text(window, cx, |text| {
9446 text.split('\n')
9447 .map(|line| line.to_case(Case::Title))
9448 .join("\n")
9449 })
9450 }
9451
9452 pub fn convert_to_snake_case(
9453 &mut self,
9454 _: &ConvertToSnakeCase,
9455 window: &mut Window,
9456 cx: &mut Context<Self>,
9457 ) {
9458 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9459 }
9460
9461 pub fn convert_to_kebab_case(
9462 &mut self,
9463 _: &ConvertToKebabCase,
9464 window: &mut Window,
9465 cx: &mut Context<Self>,
9466 ) {
9467 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9468 }
9469
9470 pub fn convert_to_upper_camel_case(
9471 &mut self,
9472 _: &ConvertToUpperCamelCase,
9473 window: &mut Window,
9474 cx: &mut Context<Self>,
9475 ) {
9476 self.manipulate_text(window, cx, |text| {
9477 text.split('\n')
9478 .map(|line| line.to_case(Case::UpperCamel))
9479 .join("\n")
9480 })
9481 }
9482
9483 pub fn convert_to_lower_camel_case(
9484 &mut self,
9485 _: &ConvertToLowerCamelCase,
9486 window: &mut Window,
9487 cx: &mut Context<Self>,
9488 ) {
9489 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9490 }
9491
9492 pub fn convert_to_opposite_case(
9493 &mut self,
9494 _: &ConvertToOppositeCase,
9495 window: &mut Window,
9496 cx: &mut Context<Self>,
9497 ) {
9498 self.manipulate_text(window, cx, |text| {
9499 text.chars()
9500 .fold(String::with_capacity(text.len()), |mut t, c| {
9501 if c.is_uppercase() {
9502 t.extend(c.to_lowercase());
9503 } else {
9504 t.extend(c.to_uppercase());
9505 }
9506 t
9507 })
9508 })
9509 }
9510
9511 pub fn convert_to_rot13(
9512 &mut self,
9513 _: &ConvertToRot13,
9514 window: &mut Window,
9515 cx: &mut Context<Self>,
9516 ) {
9517 self.manipulate_text(window, cx, |text| {
9518 text.chars()
9519 .map(|c| match c {
9520 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9521 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9522 _ => c,
9523 })
9524 .collect()
9525 })
9526 }
9527
9528 pub fn convert_to_rot47(
9529 &mut self,
9530 _: &ConvertToRot47,
9531 window: &mut Window,
9532 cx: &mut Context<Self>,
9533 ) {
9534 self.manipulate_text(window, cx, |text| {
9535 text.chars()
9536 .map(|c| {
9537 let code_point = c as u32;
9538 if code_point >= 33 && code_point <= 126 {
9539 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9540 }
9541 c
9542 })
9543 .collect()
9544 })
9545 }
9546
9547 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9548 where
9549 Fn: FnMut(&str) -> String,
9550 {
9551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9552 let buffer = self.buffer.read(cx).snapshot(cx);
9553
9554 let mut new_selections = Vec::new();
9555 let mut edits = Vec::new();
9556 let mut selection_adjustment = 0i32;
9557
9558 for selection in self.selections.all::<usize>(cx) {
9559 let selection_is_empty = selection.is_empty();
9560
9561 let (start, end) = if selection_is_empty {
9562 let word_range = movement::surrounding_word(
9563 &display_map,
9564 selection.start.to_display_point(&display_map),
9565 );
9566 let start = word_range.start.to_offset(&display_map, Bias::Left);
9567 let end = word_range.end.to_offset(&display_map, Bias::Left);
9568 (start, end)
9569 } else {
9570 (selection.start, selection.end)
9571 };
9572
9573 let text = buffer.text_for_range(start..end).collect::<String>();
9574 let old_length = text.len() as i32;
9575 let text = callback(&text);
9576
9577 new_selections.push(Selection {
9578 start: (start as i32 - selection_adjustment) as usize,
9579 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9580 goal: SelectionGoal::None,
9581 ..selection
9582 });
9583
9584 selection_adjustment += old_length - text.len() as i32;
9585
9586 edits.push((start..end, text));
9587 }
9588
9589 self.transact(window, cx, |this, window, cx| {
9590 this.buffer.update(cx, |buffer, cx| {
9591 buffer.edit(edits, None, cx);
9592 });
9593
9594 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9595 s.select(new_selections);
9596 });
9597
9598 this.request_autoscroll(Autoscroll::fit(), cx);
9599 });
9600 }
9601
9602 pub fn duplicate(
9603 &mut self,
9604 upwards: bool,
9605 whole_lines: bool,
9606 window: &mut Window,
9607 cx: &mut Context<Self>,
9608 ) {
9609 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9610
9611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9612 let buffer = &display_map.buffer_snapshot;
9613 let selections = self.selections.all::<Point>(cx);
9614
9615 let mut edits = Vec::new();
9616 let mut selections_iter = selections.iter().peekable();
9617 while let Some(selection) = selections_iter.next() {
9618 let mut rows = selection.spanned_rows(false, &display_map);
9619 // duplicate line-wise
9620 if whole_lines || selection.start == selection.end {
9621 // Avoid duplicating the same lines twice.
9622 while let Some(next_selection) = selections_iter.peek() {
9623 let next_rows = next_selection.spanned_rows(false, &display_map);
9624 if next_rows.start < rows.end {
9625 rows.end = next_rows.end;
9626 selections_iter.next().unwrap();
9627 } else {
9628 break;
9629 }
9630 }
9631
9632 // Copy the text from the selected row region and splice it either at the start
9633 // or end of the region.
9634 let start = Point::new(rows.start.0, 0);
9635 let end = Point::new(
9636 rows.end.previous_row().0,
9637 buffer.line_len(rows.end.previous_row()),
9638 );
9639 let text = buffer
9640 .text_for_range(start..end)
9641 .chain(Some("\n"))
9642 .collect::<String>();
9643 let insert_location = if upwards {
9644 Point::new(rows.end.0, 0)
9645 } else {
9646 start
9647 };
9648 edits.push((insert_location..insert_location, text));
9649 } else {
9650 // duplicate character-wise
9651 let start = selection.start;
9652 let end = selection.end;
9653 let text = buffer.text_for_range(start..end).collect::<String>();
9654 edits.push((selection.end..selection.end, text));
9655 }
9656 }
9657
9658 self.transact(window, cx, |this, _, cx| {
9659 this.buffer.update(cx, |buffer, cx| {
9660 buffer.edit(edits, None, cx);
9661 });
9662
9663 this.request_autoscroll(Autoscroll::fit(), cx);
9664 });
9665 }
9666
9667 pub fn duplicate_line_up(
9668 &mut self,
9669 _: &DuplicateLineUp,
9670 window: &mut Window,
9671 cx: &mut Context<Self>,
9672 ) {
9673 self.duplicate(true, true, window, cx);
9674 }
9675
9676 pub fn duplicate_line_down(
9677 &mut self,
9678 _: &DuplicateLineDown,
9679 window: &mut Window,
9680 cx: &mut Context<Self>,
9681 ) {
9682 self.duplicate(false, true, window, cx);
9683 }
9684
9685 pub fn duplicate_selection(
9686 &mut self,
9687 _: &DuplicateSelection,
9688 window: &mut Window,
9689 cx: &mut Context<Self>,
9690 ) {
9691 self.duplicate(false, false, window, cx);
9692 }
9693
9694 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9695 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9696
9697 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9698 let buffer = self.buffer.read(cx).snapshot(cx);
9699
9700 let mut edits = Vec::new();
9701 let mut unfold_ranges = Vec::new();
9702 let mut refold_creases = Vec::new();
9703
9704 let selections = self.selections.all::<Point>(cx);
9705 let mut selections = selections.iter().peekable();
9706 let mut contiguous_row_selections = Vec::new();
9707 let mut new_selections = Vec::new();
9708
9709 while let Some(selection) = selections.next() {
9710 // Find all the selections that span a contiguous row range
9711 let (start_row, end_row) = consume_contiguous_rows(
9712 &mut contiguous_row_selections,
9713 selection,
9714 &display_map,
9715 &mut selections,
9716 );
9717
9718 // Move the text spanned by the row range to be before the line preceding the row range
9719 if start_row.0 > 0 {
9720 let range_to_move = Point::new(
9721 start_row.previous_row().0,
9722 buffer.line_len(start_row.previous_row()),
9723 )
9724 ..Point::new(
9725 end_row.previous_row().0,
9726 buffer.line_len(end_row.previous_row()),
9727 );
9728 let insertion_point = display_map
9729 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9730 .0;
9731
9732 // Don't move lines across excerpts
9733 if buffer
9734 .excerpt_containing(insertion_point..range_to_move.end)
9735 .is_some()
9736 {
9737 let text = buffer
9738 .text_for_range(range_to_move.clone())
9739 .flat_map(|s| s.chars())
9740 .skip(1)
9741 .chain(['\n'])
9742 .collect::<String>();
9743
9744 edits.push((
9745 buffer.anchor_after(range_to_move.start)
9746 ..buffer.anchor_before(range_to_move.end),
9747 String::new(),
9748 ));
9749 let insertion_anchor = buffer.anchor_after(insertion_point);
9750 edits.push((insertion_anchor..insertion_anchor, text));
9751
9752 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9753
9754 // Move selections up
9755 new_selections.extend(contiguous_row_selections.drain(..).map(
9756 |mut selection| {
9757 selection.start.row -= row_delta;
9758 selection.end.row -= row_delta;
9759 selection
9760 },
9761 ));
9762
9763 // Move folds up
9764 unfold_ranges.push(range_to_move.clone());
9765 for fold in display_map.folds_in_range(
9766 buffer.anchor_before(range_to_move.start)
9767 ..buffer.anchor_after(range_to_move.end),
9768 ) {
9769 let mut start = fold.range.start.to_point(&buffer);
9770 let mut end = fold.range.end.to_point(&buffer);
9771 start.row -= row_delta;
9772 end.row -= row_delta;
9773 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9774 }
9775 }
9776 }
9777
9778 // If we didn't move line(s), preserve the existing selections
9779 new_selections.append(&mut contiguous_row_selections);
9780 }
9781
9782 self.transact(window, cx, |this, window, cx| {
9783 this.unfold_ranges(&unfold_ranges, true, true, cx);
9784 this.buffer.update(cx, |buffer, cx| {
9785 for (range, text) in edits {
9786 buffer.edit([(range, text)], None, cx);
9787 }
9788 });
9789 this.fold_creases(refold_creases, true, window, cx);
9790 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9791 s.select(new_selections);
9792 })
9793 });
9794 }
9795
9796 pub fn move_line_down(
9797 &mut self,
9798 _: &MoveLineDown,
9799 window: &mut Window,
9800 cx: &mut Context<Self>,
9801 ) {
9802 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9803
9804 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9805 let buffer = self.buffer.read(cx).snapshot(cx);
9806
9807 let mut edits = Vec::new();
9808 let mut unfold_ranges = Vec::new();
9809 let mut refold_creases = Vec::new();
9810
9811 let selections = self.selections.all::<Point>(cx);
9812 let mut selections = selections.iter().peekable();
9813 let mut contiguous_row_selections = Vec::new();
9814 let mut new_selections = Vec::new();
9815
9816 while let Some(selection) = selections.next() {
9817 // Find all the selections that span a contiguous row range
9818 let (start_row, end_row) = consume_contiguous_rows(
9819 &mut contiguous_row_selections,
9820 selection,
9821 &display_map,
9822 &mut selections,
9823 );
9824
9825 // Move the text spanned by the row range to be after the last line of the row range
9826 if end_row.0 <= buffer.max_point().row {
9827 let range_to_move =
9828 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9829 let insertion_point = display_map
9830 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9831 .0;
9832
9833 // Don't move lines across excerpt boundaries
9834 if buffer
9835 .excerpt_containing(range_to_move.start..insertion_point)
9836 .is_some()
9837 {
9838 let mut text = String::from("\n");
9839 text.extend(buffer.text_for_range(range_to_move.clone()));
9840 text.pop(); // Drop trailing newline
9841 edits.push((
9842 buffer.anchor_after(range_to_move.start)
9843 ..buffer.anchor_before(range_to_move.end),
9844 String::new(),
9845 ));
9846 let insertion_anchor = buffer.anchor_after(insertion_point);
9847 edits.push((insertion_anchor..insertion_anchor, text));
9848
9849 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9850
9851 // Move selections down
9852 new_selections.extend(contiguous_row_selections.drain(..).map(
9853 |mut selection| {
9854 selection.start.row += row_delta;
9855 selection.end.row += row_delta;
9856 selection
9857 },
9858 ));
9859
9860 // Move folds down
9861 unfold_ranges.push(range_to_move.clone());
9862 for fold in display_map.folds_in_range(
9863 buffer.anchor_before(range_to_move.start)
9864 ..buffer.anchor_after(range_to_move.end),
9865 ) {
9866 let mut start = fold.range.start.to_point(&buffer);
9867 let mut end = fold.range.end.to_point(&buffer);
9868 start.row += row_delta;
9869 end.row += row_delta;
9870 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9871 }
9872 }
9873 }
9874
9875 // If we didn't move line(s), preserve the existing selections
9876 new_selections.append(&mut contiguous_row_selections);
9877 }
9878
9879 self.transact(window, cx, |this, window, cx| {
9880 this.unfold_ranges(&unfold_ranges, true, true, cx);
9881 this.buffer.update(cx, |buffer, cx| {
9882 for (range, text) in edits {
9883 buffer.edit([(range, text)], None, cx);
9884 }
9885 });
9886 this.fold_creases(refold_creases, true, window, cx);
9887 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9888 s.select(new_selections)
9889 });
9890 });
9891 }
9892
9893 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9894 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9895 let text_layout_details = &self.text_layout_details(window);
9896 self.transact(window, cx, |this, window, cx| {
9897 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9898 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9899 s.move_with(|display_map, selection| {
9900 if !selection.is_empty() {
9901 return;
9902 }
9903
9904 let mut head = selection.head();
9905 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9906 if head.column() == display_map.line_len(head.row()) {
9907 transpose_offset = display_map
9908 .buffer_snapshot
9909 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9910 }
9911
9912 if transpose_offset == 0 {
9913 return;
9914 }
9915
9916 *head.column_mut() += 1;
9917 head = display_map.clip_point(head, Bias::Right);
9918 let goal = SelectionGoal::HorizontalPosition(
9919 display_map
9920 .x_for_display_point(head, text_layout_details)
9921 .into(),
9922 );
9923 selection.collapse_to(head, goal);
9924
9925 let transpose_start = display_map
9926 .buffer_snapshot
9927 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9928 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9929 let transpose_end = display_map
9930 .buffer_snapshot
9931 .clip_offset(transpose_offset + 1, Bias::Right);
9932 if let Some(ch) =
9933 display_map.buffer_snapshot.chars_at(transpose_start).next()
9934 {
9935 edits.push((transpose_start..transpose_offset, String::new()));
9936 edits.push((transpose_end..transpose_end, ch.to_string()));
9937 }
9938 }
9939 });
9940 edits
9941 });
9942 this.buffer
9943 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9944 let selections = this.selections.all::<usize>(cx);
9945 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9946 s.select(selections);
9947 });
9948 });
9949 }
9950
9951 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9953 self.rewrap_impl(RewrapOptions::default(), cx)
9954 }
9955
9956 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9957 let buffer = self.buffer.read(cx).snapshot(cx);
9958 let selections = self.selections.all::<Point>(cx);
9959 let mut selections = selections.iter().peekable();
9960
9961 let mut edits = Vec::new();
9962 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9963
9964 while let Some(selection) = selections.next() {
9965 let mut start_row = selection.start.row;
9966 let mut end_row = selection.end.row;
9967
9968 // Skip selections that overlap with a range that has already been rewrapped.
9969 let selection_range = start_row..end_row;
9970 if rewrapped_row_ranges
9971 .iter()
9972 .any(|range| range.overlaps(&selection_range))
9973 {
9974 continue;
9975 }
9976
9977 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9978
9979 // Since not all lines in the selection may be at the same indent
9980 // level, choose the indent size that is the most common between all
9981 // of the lines.
9982 //
9983 // If there is a tie, we use the deepest indent.
9984 let (indent_size, indent_end) = {
9985 let mut indent_size_occurrences = HashMap::default();
9986 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9987
9988 for row in start_row..=end_row {
9989 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9990 rows_by_indent_size.entry(indent).or_default().push(row);
9991 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9992 }
9993
9994 let indent_size = indent_size_occurrences
9995 .into_iter()
9996 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9997 .map(|(indent, _)| indent)
9998 .unwrap_or_default();
9999 let row = rows_by_indent_size[&indent_size][0];
10000 let indent_end = Point::new(row, indent_size.len);
10001
10002 (indent_size, indent_end)
10003 };
10004
10005 let mut line_prefix = indent_size.chars().collect::<String>();
10006
10007 let mut inside_comment = false;
10008 if let Some(comment_prefix) =
10009 buffer
10010 .language_scope_at(selection.head())
10011 .and_then(|language| {
10012 language
10013 .line_comment_prefixes()
10014 .iter()
10015 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10016 .cloned()
10017 })
10018 {
10019 line_prefix.push_str(&comment_prefix);
10020 inside_comment = true;
10021 }
10022
10023 let language_settings = buffer.language_settings_at(selection.head(), cx);
10024 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10025 RewrapBehavior::InComments => inside_comment,
10026 RewrapBehavior::InSelections => !selection.is_empty(),
10027 RewrapBehavior::Anywhere => true,
10028 };
10029
10030 let should_rewrap = options.override_language_settings
10031 || allow_rewrap_based_on_language
10032 || self.hard_wrap.is_some();
10033 if !should_rewrap {
10034 continue;
10035 }
10036
10037 if selection.is_empty() {
10038 'expand_upwards: while start_row > 0 {
10039 let prev_row = start_row - 1;
10040 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10041 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10042 {
10043 start_row = prev_row;
10044 } else {
10045 break 'expand_upwards;
10046 }
10047 }
10048
10049 'expand_downwards: while end_row < buffer.max_point().row {
10050 let next_row = end_row + 1;
10051 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10052 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10053 {
10054 end_row = next_row;
10055 } else {
10056 break 'expand_downwards;
10057 }
10058 }
10059 }
10060
10061 let start = Point::new(start_row, 0);
10062 let start_offset = start.to_offset(&buffer);
10063 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10064 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10065 let Some(lines_without_prefixes) = selection_text
10066 .lines()
10067 .map(|line| {
10068 line.strip_prefix(&line_prefix)
10069 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10070 .ok_or_else(|| {
10071 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10072 })
10073 })
10074 .collect::<Result<Vec<_>, _>>()
10075 .log_err()
10076 else {
10077 continue;
10078 };
10079
10080 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10081 buffer
10082 .language_settings_at(Point::new(start_row, 0), cx)
10083 .preferred_line_length as usize
10084 });
10085 let wrapped_text = wrap_with_prefix(
10086 line_prefix,
10087 lines_without_prefixes.join("\n"),
10088 wrap_column,
10089 tab_size,
10090 options.preserve_existing_whitespace,
10091 );
10092
10093 // TODO: should always use char-based diff while still supporting cursor behavior that
10094 // matches vim.
10095 let mut diff_options = DiffOptions::default();
10096 if options.override_language_settings {
10097 diff_options.max_word_diff_len = 0;
10098 diff_options.max_word_diff_line_count = 0;
10099 } else {
10100 diff_options.max_word_diff_len = usize::MAX;
10101 diff_options.max_word_diff_line_count = usize::MAX;
10102 }
10103
10104 for (old_range, new_text) in
10105 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10106 {
10107 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10108 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10109 edits.push((edit_start..edit_end, new_text));
10110 }
10111
10112 rewrapped_row_ranges.push(start_row..=end_row);
10113 }
10114
10115 self.buffer
10116 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10117 }
10118
10119 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10120 let mut text = String::new();
10121 let buffer = self.buffer.read(cx).snapshot(cx);
10122 let mut selections = self.selections.all::<Point>(cx);
10123 let mut clipboard_selections = Vec::with_capacity(selections.len());
10124 {
10125 let max_point = buffer.max_point();
10126 let mut is_first = true;
10127 for selection in &mut selections {
10128 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10129 if is_entire_line {
10130 selection.start = Point::new(selection.start.row, 0);
10131 if !selection.is_empty() && selection.end.column == 0 {
10132 selection.end = cmp::min(max_point, selection.end);
10133 } else {
10134 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10135 }
10136 selection.goal = SelectionGoal::None;
10137 }
10138 if is_first {
10139 is_first = false;
10140 } else {
10141 text += "\n";
10142 }
10143 let mut len = 0;
10144 for chunk in buffer.text_for_range(selection.start..selection.end) {
10145 text.push_str(chunk);
10146 len += chunk.len();
10147 }
10148 clipboard_selections.push(ClipboardSelection {
10149 len,
10150 is_entire_line,
10151 first_line_indent: buffer
10152 .indent_size_for_line(MultiBufferRow(selection.start.row))
10153 .len,
10154 });
10155 }
10156 }
10157
10158 self.transact(window, cx, |this, window, cx| {
10159 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10160 s.select(selections);
10161 });
10162 this.insert("", window, cx);
10163 });
10164 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10165 }
10166
10167 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10168 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10169 let item = self.cut_common(window, cx);
10170 cx.write_to_clipboard(item);
10171 }
10172
10173 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10174 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10175 self.change_selections(None, window, cx, |s| {
10176 s.move_with(|snapshot, sel| {
10177 if sel.is_empty() {
10178 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10179 }
10180 });
10181 });
10182 let item = self.cut_common(window, cx);
10183 cx.set_global(KillRing(item))
10184 }
10185
10186 pub fn kill_ring_yank(
10187 &mut self,
10188 _: &KillRingYank,
10189 window: &mut Window,
10190 cx: &mut Context<Self>,
10191 ) {
10192 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10193 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10194 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10195 (kill_ring.text().to_string(), kill_ring.metadata_json())
10196 } else {
10197 return;
10198 }
10199 } else {
10200 return;
10201 };
10202 self.do_paste(&text, metadata, false, window, cx);
10203 }
10204
10205 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10206 self.do_copy(true, cx);
10207 }
10208
10209 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10210 self.do_copy(false, cx);
10211 }
10212
10213 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10214 let selections = self.selections.all::<Point>(cx);
10215 let buffer = self.buffer.read(cx).read(cx);
10216 let mut text = String::new();
10217
10218 let mut clipboard_selections = Vec::with_capacity(selections.len());
10219 {
10220 let max_point = buffer.max_point();
10221 let mut is_first = true;
10222 for selection in &selections {
10223 let mut start = selection.start;
10224 let mut end = selection.end;
10225 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10226 if is_entire_line {
10227 start = Point::new(start.row, 0);
10228 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10229 }
10230
10231 let mut trimmed_selections = Vec::new();
10232 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10233 let row = MultiBufferRow(start.row);
10234 let first_indent = buffer.indent_size_for_line(row);
10235 if first_indent.len == 0 || start.column > first_indent.len {
10236 trimmed_selections.push(start..end);
10237 } else {
10238 trimmed_selections.push(
10239 Point::new(row.0, first_indent.len)
10240 ..Point::new(row.0, buffer.line_len(row)),
10241 );
10242 for row in start.row + 1..=end.row {
10243 let mut line_len = buffer.line_len(MultiBufferRow(row));
10244 if row == end.row {
10245 line_len = end.column;
10246 }
10247 if line_len == 0 {
10248 trimmed_selections
10249 .push(Point::new(row, 0)..Point::new(row, line_len));
10250 continue;
10251 }
10252 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10253 if row_indent_size.len >= first_indent.len {
10254 trimmed_selections.push(
10255 Point::new(row, first_indent.len)..Point::new(row, line_len),
10256 );
10257 } else {
10258 trimmed_selections.clear();
10259 trimmed_selections.push(start..end);
10260 break;
10261 }
10262 }
10263 }
10264 } else {
10265 trimmed_selections.push(start..end);
10266 }
10267
10268 for trimmed_range in trimmed_selections {
10269 if is_first {
10270 is_first = false;
10271 } else {
10272 text += "\n";
10273 }
10274 let mut len = 0;
10275 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10276 text.push_str(chunk);
10277 len += chunk.len();
10278 }
10279 clipboard_selections.push(ClipboardSelection {
10280 len,
10281 is_entire_line,
10282 first_line_indent: buffer
10283 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10284 .len,
10285 });
10286 }
10287 }
10288 }
10289
10290 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10291 text,
10292 clipboard_selections,
10293 ));
10294 }
10295
10296 pub fn do_paste(
10297 &mut self,
10298 text: &String,
10299 clipboard_selections: Option<Vec<ClipboardSelection>>,
10300 handle_entire_lines: bool,
10301 window: &mut Window,
10302 cx: &mut Context<Self>,
10303 ) {
10304 if self.read_only(cx) {
10305 return;
10306 }
10307
10308 let clipboard_text = Cow::Borrowed(text);
10309
10310 self.transact(window, cx, |this, window, cx| {
10311 if let Some(mut clipboard_selections) = clipboard_selections {
10312 let old_selections = this.selections.all::<usize>(cx);
10313 let all_selections_were_entire_line =
10314 clipboard_selections.iter().all(|s| s.is_entire_line);
10315 let first_selection_indent_column =
10316 clipboard_selections.first().map(|s| s.first_line_indent);
10317 if clipboard_selections.len() != old_selections.len() {
10318 clipboard_selections.drain(..);
10319 }
10320 let cursor_offset = this.selections.last::<usize>(cx).head();
10321 let mut auto_indent_on_paste = true;
10322
10323 this.buffer.update(cx, |buffer, cx| {
10324 let snapshot = buffer.read(cx);
10325 auto_indent_on_paste = snapshot
10326 .language_settings_at(cursor_offset, cx)
10327 .auto_indent_on_paste;
10328
10329 let mut start_offset = 0;
10330 let mut edits = Vec::new();
10331 let mut original_indent_columns = Vec::new();
10332 for (ix, selection) in old_selections.iter().enumerate() {
10333 let to_insert;
10334 let entire_line;
10335 let original_indent_column;
10336 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10337 let end_offset = start_offset + clipboard_selection.len;
10338 to_insert = &clipboard_text[start_offset..end_offset];
10339 entire_line = clipboard_selection.is_entire_line;
10340 start_offset = end_offset + 1;
10341 original_indent_column = Some(clipboard_selection.first_line_indent);
10342 } else {
10343 to_insert = clipboard_text.as_str();
10344 entire_line = all_selections_were_entire_line;
10345 original_indent_column = first_selection_indent_column
10346 }
10347
10348 // If the corresponding selection was empty when this slice of the
10349 // clipboard text was written, then the entire line containing the
10350 // selection was copied. If this selection is also currently empty,
10351 // then paste the line before the current line of the buffer.
10352 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10353 let column = selection.start.to_point(&snapshot).column as usize;
10354 let line_start = selection.start - column;
10355 line_start..line_start
10356 } else {
10357 selection.range()
10358 };
10359
10360 edits.push((range, to_insert));
10361 original_indent_columns.push(original_indent_column);
10362 }
10363 drop(snapshot);
10364
10365 buffer.edit(
10366 edits,
10367 if auto_indent_on_paste {
10368 Some(AutoindentMode::Block {
10369 original_indent_columns,
10370 })
10371 } else {
10372 None
10373 },
10374 cx,
10375 );
10376 });
10377
10378 let selections = this.selections.all::<usize>(cx);
10379 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10380 s.select(selections)
10381 });
10382 } else {
10383 this.insert(&clipboard_text, window, cx);
10384 }
10385 });
10386 }
10387
10388 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10389 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10390 if let Some(item) = cx.read_from_clipboard() {
10391 let entries = item.entries();
10392
10393 match entries.first() {
10394 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10395 // of all the pasted entries.
10396 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10397 .do_paste(
10398 clipboard_string.text(),
10399 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10400 true,
10401 window,
10402 cx,
10403 ),
10404 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10405 }
10406 }
10407 }
10408
10409 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10410 if self.read_only(cx) {
10411 return;
10412 }
10413
10414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10415
10416 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10417 if let Some((selections, _)) =
10418 self.selection_history.transaction(transaction_id).cloned()
10419 {
10420 self.change_selections(None, window, cx, |s| {
10421 s.select_anchors(selections.to_vec());
10422 });
10423 } else {
10424 log::error!(
10425 "No entry in selection_history found for undo. \
10426 This may correspond to a bug where undo does not update the selection. \
10427 If this is occurring, please add details to \
10428 https://github.com/zed-industries/zed/issues/22692"
10429 );
10430 }
10431 self.request_autoscroll(Autoscroll::fit(), cx);
10432 self.unmark_text(window, cx);
10433 self.refresh_inline_completion(true, false, window, cx);
10434 cx.emit(EditorEvent::Edited { transaction_id });
10435 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10436 }
10437 }
10438
10439 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10440 if self.read_only(cx) {
10441 return;
10442 }
10443
10444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10445
10446 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10447 if let Some((_, Some(selections))) =
10448 self.selection_history.transaction(transaction_id).cloned()
10449 {
10450 self.change_selections(None, window, cx, |s| {
10451 s.select_anchors(selections.to_vec());
10452 });
10453 } else {
10454 log::error!(
10455 "No entry in selection_history found for redo. \
10456 This may correspond to a bug where undo does not update the selection. \
10457 If this is occurring, please add details to \
10458 https://github.com/zed-industries/zed/issues/22692"
10459 );
10460 }
10461 self.request_autoscroll(Autoscroll::fit(), cx);
10462 self.unmark_text(window, cx);
10463 self.refresh_inline_completion(true, false, window, cx);
10464 cx.emit(EditorEvent::Edited { transaction_id });
10465 }
10466 }
10467
10468 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10469 self.buffer
10470 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10471 }
10472
10473 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10474 self.buffer
10475 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10476 }
10477
10478 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10479 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10480 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10481 s.move_with(|map, selection| {
10482 let cursor = if selection.is_empty() {
10483 movement::left(map, selection.start)
10484 } else {
10485 selection.start
10486 };
10487 selection.collapse_to(cursor, SelectionGoal::None);
10488 });
10489 })
10490 }
10491
10492 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10493 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10494 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10495 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10496 })
10497 }
10498
10499 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10500 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10501 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10502 s.move_with(|map, selection| {
10503 let cursor = if selection.is_empty() {
10504 movement::right(map, selection.end)
10505 } else {
10506 selection.end
10507 };
10508 selection.collapse_to(cursor, SelectionGoal::None)
10509 });
10510 })
10511 }
10512
10513 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10514 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10515 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10516 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10517 })
10518 }
10519
10520 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10521 if self.take_rename(true, window, cx).is_some() {
10522 return;
10523 }
10524
10525 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10526 cx.propagate();
10527 return;
10528 }
10529
10530 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10531
10532 let text_layout_details = &self.text_layout_details(window);
10533 let selection_count = self.selections.count();
10534 let first_selection = self.selections.first_anchor();
10535
10536 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10537 s.move_with(|map, selection| {
10538 if !selection.is_empty() {
10539 selection.goal = SelectionGoal::None;
10540 }
10541 let (cursor, goal) = movement::up(
10542 map,
10543 selection.start,
10544 selection.goal,
10545 false,
10546 text_layout_details,
10547 );
10548 selection.collapse_to(cursor, goal);
10549 });
10550 });
10551
10552 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10553 {
10554 cx.propagate();
10555 }
10556 }
10557
10558 pub fn move_up_by_lines(
10559 &mut self,
10560 action: &MoveUpByLines,
10561 window: &mut Window,
10562 cx: &mut Context<Self>,
10563 ) {
10564 if self.take_rename(true, window, cx).is_some() {
10565 return;
10566 }
10567
10568 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10569 cx.propagate();
10570 return;
10571 }
10572
10573 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10574
10575 let text_layout_details = &self.text_layout_details(window);
10576
10577 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10578 s.move_with(|map, selection| {
10579 if !selection.is_empty() {
10580 selection.goal = SelectionGoal::None;
10581 }
10582 let (cursor, goal) = movement::up_by_rows(
10583 map,
10584 selection.start,
10585 action.lines,
10586 selection.goal,
10587 false,
10588 text_layout_details,
10589 );
10590 selection.collapse_to(cursor, goal);
10591 });
10592 })
10593 }
10594
10595 pub fn move_down_by_lines(
10596 &mut self,
10597 action: &MoveDownByLines,
10598 window: &mut Window,
10599 cx: &mut Context<Self>,
10600 ) {
10601 if self.take_rename(true, window, cx).is_some() {
10602 return;
10603 }
10604
10605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10606 cx.propagate();
10607 return;
10608 }
10609
10610 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10611
10612 let text_layout_details = &self.text_layout_details(window);
10613
10614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10615 s.move_with(|map, selection| {
10616 if !selection.is_empty() {
10617 selection.goal = SelectionGoal::None;
10618 }
10619 let (cursor, goal) = movement::down_by_rows(
10620 map,
10621 selection.start,
10622 action.lines,
10623 selection.goal,
10624 false,
10625 text_layout_details,
10626 );
10627 selection.collapse_to(cursor, goal);
10628 });
10629 })
10630 }
10631
10632 pub fn select_down_by_lines(
10633 &mut self,
10634 action: &SelectDownByLines,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10639 let text_layout_details = &self.text_layout_details(window);
10640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10641 s.move_heads_with(|map, head, goal| {
10642 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10643 })
10644 })
10645 }
10646
10647 pub fn select_up_by_lines(
10648 &mut self,
10649 action: &SelectUpByLines,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10654 let text_layout_details = &self.text_layout_details(window);
10655 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10656 s.move_heads_with(|map, head, goal| {
10657 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10658 })
10659 })
10660 }
10661
10662 pub fn select_page_up(
10663 &mut self,
10664 _: &SelectPageUp,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 let Some(row_count) = self.visible_row_count() else {
10669 return;
10670 };
10671
10672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10673
10674 let text_layout_details = &self.text_layout_details(window);
10675
10676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10677 s.move_heads_with(|map, head, goal| {
10678 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10679 })
10680 })
10681 }
10682
10683 pub fn move_page_up(
10684 &mut self,
10685 action: &MovePageUp,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 if self.take_rename(true, window, cx).is_some() {
10690 return;
10691 }
10692
10693 if self
10694 .context_menu
10695 .borrow_mut()
10696 .as_mut()
10697 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10698 .unwrap_or(false)
10699 {
10700 return;
10701 }
10702
10703 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10704 cx.propagate();
10705 return;
10706 }
10707
10708 let Some(row_count) = self.visible_row_count() else {
10709 return;
10710 };
10711
10712 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10713
10714 let autoscroll = if action.center_cursor {
10715 Autoscroll::center()
10716 } else {
10717 Autoscroll::fit()
10718 };
10719
10720 let text_layout_details = &self.text_layout_details(window);
10721
10722 self.change_selections(Some(autoscroll), window, cx, |s| {
10723 s.move_with(|map, selection| {
10724 if !selection.is_empty() {
10725 selection.goal = SelectionGoal::None;
10726 }
10727 let (cursor, goal) = movement::up_by_rows(
10728 map,
10729 selection.end,
10730 row_count,
10731 selection.goal,
10732 false,
10733 text_layout_details,
10734 );
10735 selection.collapse_to(cursor, goal);
10736 });
10737 });
10738 }
10739
10740 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10741 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10742 let text_layout_details = &self.text_layout_details(window);
10743 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10744 s.move_heads_with(|map, head, goal| {
10745 movement::up(map, head, goal, false, text_layout_details)
10746 })
10747 })
10748 }
10749
10750 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10751 self.take_rename(true, window, cx);
10752
10753 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10754 cx.propagate();
10755 return;
10756 }
10757
10758 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10759
10760 let text_layout_details = &self.text_layout_details(window);
10761 let selection_count = self.selections.count();
10762 let first_selection = self.selections.first_anchor();
10763
10764 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10765 s.move_with(|map, selection| {
10766 if !selection.is_empty() {
10767 selection.goal = SelectionGoal::None;
10768 }
10769 let (cursor, goal) = movement::down(
10770 map,
10771 selection.end,
10772 selection.goal,
10773 false,
10774 text_layout_details,
10775 );
10776 selection.collapse_to(cursor, goal);
10777 });
10778 });
10779
10780 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10781 {
10782 cx.propagate();
10783 }
10784 }
10785
10786 pub fn select_page_down(
10787 &mut self,
10788 _: &SelectPageDown,
10789 window: &mut Window,
10790 cx: &mut Context<Self>,
10791 ) {
10792 let Some(row_count) = self.visible_row_count() else {
10793 return;
10794 };
10795
10796 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10797
10798 let text_layout_details = &self.text_layout_details(window);
10799
10800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10801 s.move_heads_with(|map, head, goal| {
10802 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10803 })
10804 })
10805 }
10806
10807 pub fn move_page_down(
10808 &mut self,
10809 action: &MovePageDown,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 ) {
10813 if self.take_rename(true, window, cx).is_some() {
10814 return;
10815 }
10816
10817 if self
10818 .context_menu
10819 .borrow_mut()
10820 .as_mut()
10821 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10822 .unwrap_or(false)
10823 {
10824 return;
10825 }
10826
10827 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10828 cx.propagate();
10829 return;
10830 }
10831
10832 let Some(row_count) = self.visible_row_count() else {
10833 return;
10834 };
10835
10836 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10837
10838 let autoscroll = if action.center_cursor {
10839 Autoscroll::center()
10840 } else {
10841 Autoscroll::fit()
10842 };
10843
10844 let text_layout_details = &self.text_layout_details(window);
10845 self.change_selections(Some(autoscroll), window, cx, |s| {
10846 s.move_with(|map, selection| {
10847 if !selection.is_empty() {
10848 selection.goal = SelectionGoal::None;
10849 }
10850 let (cursor, goal) = movement::down_by_rows(
10851 map,
10852 selection.end,
10853 row_count,
10854 selection.goal,
10855 false,
10856 text_layout_details,
10857 );
10858 selection.collapse_to(cursor, goal);
10859 });
10860 });
10861 }
10862
10863 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10864 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10865 let text_layout_details = &self.text_layout_details(window);
10866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10867 s.move_heads_with(|map, head, goal| {
10868 movement::down(map, head, goal, false, text_layout_details)
10869 })
10870 });
10871 }
10872
10873 pub fn context_menu_first(
10874 &mut self,
10875 _: &ContextMenuFirst,
10876 _window: &mut Window,
10877 cx: &mut Context<Self>,
10878 ) {
10879 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10880 context_menu.select_first(self.completion_provider.as_deref(), cx);
10881 }
10882 }
10883
10884 pub fn context_menu_prev(
10885 &mut self,
10886 _: &ContextMenuPrevious,
10887 _window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10891 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10892 }
10893 }
10894
10895 pub fn context_menu_next(
10896 &mut self,
10897 _: &ContextMenuNext,
10898 _window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10902 context_menu.select_next(self.completion_provider.as_deref(), cx);
10903 }
10904 }
10905
10906 pub fn context_menu_last(
10907 &mut self,
10908 _: &ContextMenuLast,
10909 _window: &mut Window,
10910 cx: &mut Context<Self>,
10911 ) {
10912 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10913 context_menu.select_last(self.completion_provider.as_deref(), cx);
10914 }
10915 }
10916
10917 pub fn move_to_previous_word_start(
10918 &mut self,
10919 _: &MoveToPreviousWordStart,
10920 window: &mut Window,
10921 cx: &mut Context<Self>,
10922 ) {
10923 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10925 s.move_cursors_with(|map, head, _| {
10926 (
10927 movement::previous_word_start(map, head),
10928 SelectionGoal::None,
10929 )
10930 });
10931 })
10932 }
10933
10934 pub fn move_to_previous_subword_start(
10935 &mut self,
10936 _: &MoveToPreviousSubwordStart,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10942 s.move_cursors_with(|map, head, _| {
10943 (
10944 movement::previous_subword_start(map, head),
10945 SelectionGoal::None,
10946 )
10947 });
10948 })
10949 }
10950
10951 pub fn select_to_previous_word_start(
10952 &mut self,
10953 _: &SelectToPreviousWordStart,
10954 window: &mut Window,
10955 cx: &mut Context<Self>,
10956 ) {
10957 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10958 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10959 s.move_heads_with(|map, head, _| {
10960 (
10961 movement::previous_word_start(map, head),
10962 SelectionGoal::None,
10963 )
10964 });
10965 })
10966 }
10967
10968 pub fn select_to_previous_subword_start(
10969 &mut self,
10970 _: &SelectToPreviousSubwordStart,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10975 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10976 s.move_heads_with(|map, head, _| {
10977 (
10978 movement::previous_subword_start(map, head),
10979 SelectionGoal::None,
10980 )
10981 });
10982 })
10983 }
10984
10985 pub fn delete_to_previous_word_start(
10986 &mut self,
10987 action: &DeleteToPreviousWordStart,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) {
10991 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10992 self.transact(window, cx, |this, window, cx| {
10993 this.select_autoclose_pair(window, cx);
10994 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10995 s.move_with(|map, selection| {
10996 if selection.is_empty() {
10997 let cursor = if action.ignore_newlines {
10998 movement::previous_word_start(map, selection.head())
10999 } else {
11000 movement::previous_word_start_or_newline(map, selection.head())
11001 };
11002 selection.set_head(cursor, SelectionGoal::None);
11003 }
11004 });
11005 });
11006 this.insert("", window, cx);
11007 });
11008 }
11009
11010 pub fn delete_to_previous_subword_start(
11011 &mut self,
11012 _: &DeleteToPreviousSubwordStart,
11013 window: &mut Window,
11014 cx: &mut Context<Self>,
11015 ) {
11016 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11017 self.transact(window, cx, |this, window, cx| {
11018 this.select_autoclose_pair(window, cx);
11019 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11020 s.move_with(|map, selection| {
11021 if selection.is_empty() {
11022 let cursor = movement::previous_subword_start(map, selection.head());
11023 selection.set_head(cursor, SelectionGoal::None);
11024 }
11025 });
11026 });
11027 this.insert("", window, cx);
11028 });
11029 }
11030
11031 pub fn move_to_next_word_end(
11032 &mut self,
11033 _: &MoveToNextWordEnd,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) {
11037 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11039 s.move_cursors_with(|map, head, _| {
11040 (movement::next_word_end(map, head), SelectionGoal::None)
11041 });
11042 })
11043 }
11044
11045 pub fn move_to_next_subword_end(
11046 &mut self,
11047 _: &MoveToNextSubwordEnd,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 ) {
11051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11052 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11053 s.move_cursors_with(|map, head, _| {
11054 (movement::next_subword_end(map, head), SelectionGoal::None)
11055 });
11056 })
11057 }
11058
11059 pub fn select_to_next_word_end(
11060 &mut self,
11061 _: &SelectToNextWordEnd,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11067 s.move_heads_with(|map, head, _| {
11068 (movement::next_word_end(map, head), SelectionGoal::None)
11069 });
11070 })
11071 }
11072
11073 pub fn select_to_next_subword_end(
11074 &mut self,
11075 _: &SelectToNextSubwordEnd,
11076 window: &mut Window,
11077 cx: &mut Context<Self>,
11078 ) {
11079 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11080 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11081 s.move_heads_with(|map, head, _| {
11082 (movement::next_subword_end(map, head), SelectionGoal::None)
11083 });
11084 })
11085 }
11086
11087 pub fn delete_to_next_word_end(
11088 &mut self,
11089 action: &DeleteToNextWordEnd,
11090 window: &mut Window,
11091 cx: &mut Context<Self>,
11092 ) {
11093 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11094 self.transact(window, cx, |this, window, cx| {
11095 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11096 s.move_with(|map, selection| {
11097 if selection.is_empty() {
11098 let cursor = if action.ignore_newlines {
11099 movement::next_word_end(map, selection.head())
11100 } else {
11101 movement::next_word_end_or_newline(map, selection.head())
11102 };
11103 selection.set_head(cursor, SelectionGoal::None);
11104 }
11105 });
11106 });
11107 this.insert("", window, cx);
11108 });
11109 }
11110
11111 pub fn delete_to_next_subword_end(
11112 &mut self,
11113 _: &DeleteToNextSubwordEnd,
11114 window: &mut Window,
11115 cx: &mut Context<Self>,
11116 ) {
11117 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11118 self.transact(window, cx, |this, window, cx| {
11119 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11120 s.move_with(|map, selection| {
11121 if selection.is_empty() {
11122 let cursor = movement::next_subword_end(map, selection.head());
11123 selection.set_head(cursor, SelectionGoal::None);
11124 }
11125 });
11126 });
11127 this.insert("", window, cx);
11128 });
11129 }
11130
11131 pub fn move_to_beginning_of_line(
11132 &mut self,
11133 action: &MoveToBeginningOfLine,
11134 window: &mut Window,
11135 cx: &mut Context<Self>,
11136 ) {
11137 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11138 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11139 s.move_cursors_with(|map, head, _| {
11140 (
11141 movement::indented_line_beginning(
11142 map,
11143 head,
11144 action.stop_at_soft_wraps,
11145 action.stop_at_indent,
11146 ),
11147 SelectionGoal::None,
11148 )
11149 });
11150 })
11151 }
11152
11153 pub fn select_to_beginning_of_line(
11154 &mut self,
11155 action: &SelectToBeginningOfLine,
11156 window: &mut Window,
11157 cx: &mut Context<Self>,
11158 ) {
11159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11161 s.move_heads_with(|map, head, _| {
11162 (
11163 movement::indented_line_beginning(
11164 map,
11165 head,
11166 action.stop_at_soft_wraps,
11167 action.stop_at_indent,
11168 ),
11169 SelectionGoal::None,
11170 )
11171 });
11172 });
11173 }
11174
11175 pub fn delete_to_beginning_of_line(
11176 &mut self,
11177 action: &DeleteToBeginningOfLine,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11182 self.transact(window, cx, |this, window, cx| {
11183 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11184 s.move_with(|_, selection| {
11185 selection.reversed = true;
11186 });
11187 });
11188
11189 this.select_to_beginning_of_line(
11190 &SelectToBeginningOfLine {
11191 stop_at_soft_wraps: false,
11192 stop_at_indent: action.stop_at_indent,
11193 },
11194 window,
11195 cx,
11196 );
11197 this.backspace(&Backspace, window, cx);
11198 });
11199 }
11200
11201 pub fn move_to_end_of_line(
11202 &mut self,
11203 action: &MoveToEndOfLine,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11209 s.move_cursors_with(|map, head, _| {
11210 (
11211 movement::line_end(map, head, action.stop_at_soft_wraps),
11212 SelectionGoal::None,
11213 )
11214 });
11215 })
11216 }
11217
11218 pub fn select_to_end_of_line(
11219 &mut self,
11220 action: &SelectToEndOfLine,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11225 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11226 s.move_heads_with(|map, head, _| {
11227 (
11228 movement::line_end(map, head, action.stop_at_soft_wraps),
11229 SelectionGoal::None,
11230 )
11231 });
11232 })
11233 }
11234
11235 pub fn delete_to_end_of_line(
11236 &mut self,
11237 _: &DeleteToEndOfLine,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11242 self.transact(window, cx, |this, window, cx| {
11243 this.select_to_end_of_line(
11244 &SelectToEndOfLine {
11245 stop_at_soft_wraps: false,
11246 },
11247 window,
11248 cx,
11249 );
11250 this.delete(&Delete, window, cx);
11251 });
11252 }
11253
11254 pub fn cut_to_end_of_line(
11255 &mut self,
11256 _: &CutToEndOfLine,
11257 window: &mut Window,
11258 cx: &mut Context<Self>,
11259 ) {
11260 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11261 self.transact(window, cx, |this, window, cx| {
11262 this.select_to_end_of_line(
11263 &SelectToEndOfLine {
11264 stop_at_soft_wraps: false,
11265 },
11266 window,
11267 cx,
11268 );
11269 this.cut(&Cut, window, cx);
11270 });
11271 }
11272
11273 pub fn move_to_start_of_paragraph(
11274 &mut self,
11275 _: &MoveToStartOfParagraph,
11276 window: &mut Window,
11277 cx: &mut Context<Self>,
11278 ) {
11279 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11280 cx.propagate();
11281 return;
11282 }
11283 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11284 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11285 s.move_with(|map, selection| {
11286 selection.collapse_to(
11287 movement::start_of_paragraph(map, selection.head(), 1),
11288 SelectionGoal::None,
11289 )
11290 });
11291 })
11292 }
11293
11294 pub fn move_to_end_of_paragraph(
11295 &mut self,
11296 _: &MoveToEndOfParagraph,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11301 cx.propagate();
11302 return;
11303 }
11304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11305 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11306 s.move_with(|map, selection| {
11307 selection.collapse_to(
11308 movement::end_of_paragraph(map, selection.head(), 1),
11309 SelectionGoal::None,
11310 )
11311 });
11312 })
11313 }
11314
11315 pub fn select_to_start_of_paragraph(
11316 &mut self,
11317 _: &SelectToStartOfParagraph,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11322 cx.propagate();
11323 return;
11324 }
11325 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11326 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11327 s.move_heads_with(|map, head, _| {
11328 (
11329 movement::start_of_paragraph(map, head, 1),
11330 SelectionGoal::None,
11331 )
11332 });
11333 })
11334 }
11335
11336 pub fn select_to_end_of_paragraph(
11337 &mut self,
11338 _: &SelectToEndOfParagraph,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11343 cx.propagate();
11344 return;
11345 }
11346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11347 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11348 s.move_heads_with(|map, head, _| {
11349 (
11350 movement::end_of_paragraph(map, head, 1),
11351 SelectionGoal::None,
11352 )
11353 });
11354 })
11355 }
11356
11357 pub fn move_to_start_of_excerpt(
11358 &mut self,
11359 _: &MoveToStartOfExcerpt,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11364 cx.propagate();
11365 return;
11366 }
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.move_with(|map, selection| {
11370 selection.collapse_to(
11371 movement::start_of_excerpt(
11372 map,
11373 selection.head(),
11374 workspace::searchable::Direction::Prev,
11375 ),
11376 SelectionGoal::None,
11377 )
11378 });
11379 })
11380 }
11381
11382 pub fn move_to_start_of_next_excerpt(
11383 &mut self,
11384 _: &MoveToStartOfNextExcerpt,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11389 cx.propagate();
11390 return;
11391 }
11392
11393 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11394 s.move_with(|map, selection| {
11395 selection.collapse_to(
11396 movement::start_of_excerpt(
11397 map,
11398 selection.head(),
11399 workspace::searchable::Direction::Next,
11400 ),
11401 SelectionGoal::None,
11402 )
11403 });
11404 })
11405 }
11406
11407 pub fn move_to_end_of_excerpt(
11408 &mut self,
11409 _: &MoveToEndOfExcerpt,
11410 window: &mut Window,
11411 cx: &mut Context<Self>,
11412 ) {
11413 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11414 cx.propagate();
11415 return;
11416 }
11417 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11418 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11419 s.move_with(|map, selection| {
11420 selection.collapse_to(
11421 movement::end_of_excerpt(
11422 map,
11423 selection.head(),
11424 workspace::searchable::Direction::Next,
11425 ),
11426 SelectionGoal::None,
11427 )
11428 });
11429 })
11430 }
11431
11432 pub fn move_to_end_of_previous_excerpt(
11433 &mut self,
11434 _: &MoveToEndOfPreviousExcerpt,
11435 window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) {
11438 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11439 cx.propagate();
11440 return;
11441 }
11442 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11444 s.move_with(|map, selection| {
11445 selection.collapse_to(
11446 movement::end_of_excerpt(
11447 map,
11448 selection.head(),
11449 workspace::searchable::Direction::Prev,
11450 ),
11451 SelectionGoal::None,
11452 )
11453 });
11454 })
11455 }
11456
11457 pub fn select_to_start_of_excerpt(
11458 &mut self,
11459 _: &SelectToStartOfExcerpt,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11464 cx.propagate();
11465 return;
11466 }
11467 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11468 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11469 s.move_heads_with(|map, head, _| {
11470 (
11471 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11472 SelectionGoal::None,
11473 )
11474 });
11475 })
11476 }
11477
11478 pub fn select_to_start_of_next_excerpt(
11479 &mut self,
11480 _: &SelectToStartOfNextExcerpt,
11481 window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11485 cx.propagate();
11486 return;
11487 }
11488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11490 s.move_heads_with(|map, head, _| {
11491 (
11492 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11493 SelectionGoal::None,
11494 )
11495 });
11496 })
11497 }
11498
11499 pub fn select_to_end_of_excerpt(
11500 &mut self,
11501 _: &SelectToEndOfExcerpt,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11506 cx.propagate();
11507 return;
11508 }
11509 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11510 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11511 s.move_heads_with(|map, head, _| {
11512 (
11513 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11514 SelectionGoal::None,
11515 )
11516 });
11517 })
11518 }
11519
11520 pub fn select_to_end_of_previous_excerpt(
11521 &mut self,
11522 _: &SelectToEndOfPreviousExcerpt,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11527 cx.propagate();
11528 return;
11529 }
11530 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11532 s.move_heads_with(|map, head, _| {
11533 (
11534 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11535 SelectionGoal::None,
11536 )
11537 });
11538 })
11539 }
11540
11541 pub fn move_to_beginning(
11542 &mut self,
11543 _: &MoveToBeginning,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11548 cx.propagate();
11549 return;
11550 }
11551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11552 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11553 s.select_ranges(vec![0..0]);
11554 });
11555 }
11556
11557 pub fn select_to_beginning(
11558 &mut self,
11559 _: &SelectToBeginning,
11560 window: &mut Window,
11561 cx: &mut Context<Self>,
11562 ) {
11563 let mut selection = self.selections.last::<Point>(cx);
11564 selection.set_head(Point::zero(), SelectionGoal::None);
11565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11566 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11567 s.select(vec![selection]);
11568 });
11569 }
11570
11571 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11572 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11573 cx.propagate();
11574 return;
11575 }
11576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11577 let cursor = self.buffer.read(cx).read(cx).len();
11578 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11579 s.select_ranges(vec![cursor..cursor])
11580 });
11581 }
11582
11583 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11584 self.nav_history = nav_history;
11585 }
11586
11587 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11588 self.nav_history.as_ref()
11589 }
11590
11591 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11592 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11593 }
11594
11595 fn push_to_nav_history(
11596 &mut self,
11597 cursor_anchor: Anchor,
11598 new_position: Option<Point>,
11599 is_deactivate: bool,
11600 cx: &mut Context<Self>,
11601 ) {
11602 if let Some(nav_history) = self.nav_history.as_mut() {
11603 let buffer = self.buffer.read(cx).read(cx);
11604 let cursor_position = cursor_anchor.to_point(&buffer);
11605 let scroll_state = self.scroll_manager.anchor();
11606 let scroll_top_row = scroll_state.top_row(&buffer);
11607 drop(buffer);
11608
11609 if let Some(new_position) = new_position {
11610 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11611 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11612 return;
11613 }
11614 }
11615
11616 nav_history.push(
11617 Some(NavigationData {
11618 cursor_anchor,
11619 cursor_position,
11620 scroll_anchor: scroll_state,
11621 scroll_top_row,
11622 }),
11623 cx,
11624 );
11625 cx.emit(EditorEvent::PushedToNavHistory {
11626 anchor: cursor_anchor,
11627 is_deactivate,
11628 })
11629 }
11630 }
11631
11632 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11633 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11634 let buffer = self.buffer.read(cx).snapshot(cx);
11635 let mut selection = self.selections.first::<usize>(cx);
11636 selection.set_head(buffer.len(), SelectionGoal::None);
11637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11638 s.select(vec![selection]);
11639 });
11640 }
11641
11642 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11643 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11644 let end = self.buffer.read(cx).read(cx).len();
11645 self.change_selections(None, window, cx, |s| {
11646 s.select_ranges(vec![0..end]);
11647 });
11648 }
11649
11650 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11653 let mut selections = self.selections.all::<Point>(cx);
11654 let max_point = display_map.buffer_snapshot.max_point();
11655 for selection in &mut selections {
11656 let rows = selection.spanned_rows(true, &display_map);
11657 selection.start = Point::new(rows.start.0, 0);
11658 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11659 selection.reversed = false;
11660 }
11661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11662 s.select(selections);
11663 });
11664 }
11665
11666 pub fn split_selection_into_lines(
11667 &mut self,
11668 _: &SplitSelectionIntoLines,
11669 window: &mut Window,
11670 cx: &mut Context<Self>,
11671 ) {
11672 let selections = self
11673 .selections
11674 .all::<Point>(cx)
11675 .into_iter()
11676 .map(|selection| selection.start..selection.end)
11677 .collect::<Vec<_>>();
11678 self.unfold_ranges(&selections, true, true, cx);
11679
11680 let mut new_selection_ranges = Vec::new();
11681 {
11682 let buffer = self.buffer.read(cx).read(cx);
11683 for selection in selections {
11684 for row in selection.start.row..selection.end.row {
11685 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11686 new_selection_ranges.push(cursor..cursor);
11687 }
11688
11689 let is_multiline_selection = selection.start.row != selection.end.row;
11690 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11691 // so this action feels more ergonomic when paired with other selection operations
11692 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11693 if !should_skip_last {
11694 new_selection_ranges.push(selection.end..selection.end);
11695 }
11696 }
11697 }
11698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11699 s.select_ranges(new_selection_ranges);
11700 });
11701 }
11702
11703 pub fn add_selection_above(
11704 &mut self,
11705 _: &AddSelectionAbove,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 self.add_selection(true, window, cx);
11710 }
11711
11712 pub fn add_selection_below(
11713 &mut self,
11714 _: &AddSelectionBelow,
11715 window: &mut Window,
11716 cx: &mut Context<Self>,
11717 ) {
11718 self.add_selection(false, window, cx);
11719 }
11720
11721 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723
11724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11725 let mut selections = self.selections.all::<Point>(cx);
11726 let text_layout_details = self.text_layout_details(window);
11727 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11728 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11729 let range = oldest_selection.display_range(&display_map).sorted();
11730
11731 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11732 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11733 let positions = start_x.min(end_x)..start_x.max(end_x);
11734
11735 selections.clear();
11736 let mut stack = Vec::new();
11737 for row in range.start.row().0..=range.end.row().0 {
11738 if let Some(selection) = self.selections.build_columnar_selection(
11739 &display_map,
11740 DisplayRow(row),
11741 &positions,
11742 oldest_selection.reversed,
11743 &text_layout_details,
11744 ) {
11745 stack.push(selection.id);
11746 selections.push(selection);
11747 }
11748 }
11749
11750 if above {
11751 stack.reverse();
11752 }
11753
11754 AddSelectionsState { above, stack }
11755 });
11756
11757 let last_added_selection = *state.stack.last().unwrap();
11758 let mut new_selections = Vec::new();
11759 if above == state.above {
11760 let end_row = if above {
11761 DisplayRow(0)
11762 } else {
11763 display_map.max_point().row()
11764 };
11765
11766 'outer: for selection in selections {
11767 if selection.id == last_added_selection {
11768 let range = selection.display_range(&display_map).sorted();
11769 debug_assert_eq!(range.start.row(), range.end.row());
11770 let mut row = range.start.row();
11771 let positions =
11772 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11773 px(start)..px(end)
11774 } else {
11775 let start_x =
11776 display_map.x_for_display_point(range.start, &text_layout_details);
11777 let end_x =
11778 display_map.x_for_display_point(range.end, &text_layout_details);
11779 start_x.min(end_x)..start_x.max(end_x)
11780 };
11781
11782 while row != end_row {
11783 if above {
11784 row.0 -= 1;
11785 } else {
11786 row.0 += 1;
11787 }
11788
11789 if let Some(new_selection) = self.selections.build_columnar_selection(
11790 &display_map,
11791 row,
11792 &positions,
11793 selection.reversed,
11794 &text_layout_details,
11795 ) {
11796 state.stack.push(new_selection.id);
11797 if above {
11798 new_selections.push(new_selection);
11799 new_selections.push(selection);
11800 } else {
11801 new_selections.push(selection);
11802 new_selections.push(new_selection);
11803 }
11804
11805 continue 'outer;
11806 }
11807 }
11808 }
11809
11810 new_selections.push(selection);
11811 }
11812 } else {
11813 new_selections = selections;
11814 new_selections.retain(|s| s.id != last_added_selection);
11815 state.stack.pop();
11816 }
11817
11818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11819 s.select(new_selections);
11820 });
11821 if state.stack.len() > 1 {
11822 self.add_selections_state = Some(state);
11823 }
11824 }
11825
11826 pub fn select_next_match_internal(
11827 &mut self,
11828 display_map: &DisplaySnapshot,
11829 replace_newest: bool,
11830 autoscroll: Option<Autoscroll>,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) -> Result<()> {
11834 fn select_next_match_ranges(
11835 this: &mut Editor,
11836 range: Range<usize>,
11837 reversed: bool,
11838 replace_newest: bool,
11839 auto_scroll: Option<Autoscroll>,
11840 window: &mut Window,
11841 cx: &mut Context<Editor>,
11842 ) {
11843 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11844 this.change_selections(auto_scroll, window, cx, |s| {
11845 if replace_newest {
11846 s.delete(s.newest_anchor().id);
11847 }
11848 if reversed {
11849 s.insert_range(range.end..range.start);
11850 } else {
11851 s.insert_range(range);
11852 }
11853 });
11854 }
11855
11856 let buffer = &display_map.buffer_snapshot;
11857 let mut selections = self.selections.all::<usize>(cx);
11858 if let Some(mut select_next_state) = self.select_next_state.take() {
11859 let query = &select_next_state.query;
11860 if !select_next_state.done {
11861 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11862 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11863 let mut next_selected_range = None;
11864
11865 let bytes_after_last_selection =
11866 buffer.bytes_in_range(last_selection.end..buffer.len());
11867 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11868 let query_matches = query
11869 .stream_find_iter(bytes_after_last_selection)
11870 .map(|result| (last_selection.end, result))
11871 .chain(
11872 query
11873 .stream_find_iter(bytes_before_first_selection)
11874 .map(|result| (0, result)),
11875 );
11876
11877 for (start_offset, query_match) in query_matches {
11878 let query_match = query_match.unwrap(); // can only fail due to I/O
11879 let offset_range =
11880 start_offset + query_match.start()..start_offset + query_match.end();
11881 let display_range = offset_range.start.to_display_point(display_map)
11882 ..offset_range.end.to_display_point(display_map);
11883
11884 if !select_next_state.wordwise
11885 || (!movement::is_inside_word(display_map, display_range.start)
11886 && !movement::is_inside_word(display_map, display_range.end))
11887 {
11888 // TODO: This is n^2, because we might check all the selections
11889 if !selections
11890 .iter()
11891 .any(|selection| selection.range().overlaps(&offset_range))
11892 {
11893 next_selected_range = Some(offset_range);
11894 break;
11895 }
11896 }
11897 }
11898
11899 if let Some(next_selected_range) = next_selected_range {
11900 select_next_match_ranges(
11901 self,
11902 next_selected_range,
11903 last_selection.reversed,
11904 replace_newest,
11905 autoscroll,
11906 window,
11907 cx,
11908 );
11909 } else {
11910 select_next_state.done = true;
11911 }
11912 }
11913
11914 self.select_next_state = Some(select_next_state);
11915 } else {
11916 let mut only_carets = true;
11917 let mut same_text_selected = true;
11918 let mut selected_text = None;
11919
11920 let mut selections_iter = selections.iter().peekable();
11921 while let Some(selection) = selections_iter.next() {
11922 if selection.start != selection.end {
11923 only_carets = false;
11924 }
11925
11926 if same_text_selected {
11927 if selected_text.is_none() {
11928 selected_text =
11929 Some(buffer.text_for_range(selection.range()).collect::<String>());
11930 }
11931
11932 if let Some(next_selection) = selections_iter.peek() {
11933 if next_selection.range().len() == selection.range().len() {
11934 let next_selected_text = buffer
11935 .text_for_range(next_selection.range())
11936 .collect::<String>();
11937 if Some(next_selected_text) != selected_text {
11938 same_text_selected = false;
11939 selected_text = None;
11940 }
11941 } else {
11942 same_text_selected = false;
11943 selected_text = None;
11944 }
11945 }
11946 }
11947 }
11948
11949 if only_carets {
11950 for selection in &mut selections {
11951 let word_range = movement::surrounding_word(
11952 display_map,
11953 selection.start.to_display_point(display_map),
11954 );
11955 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11956 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11957 selection.goal = SelectionGoal::None;
11958 selection.reversed = false;
11959 select_next_match_ranges(
11960 self,
11961 selection.start..selection.end,
11962 selection.reversed,
11963 replace_newest,
11964 autoscroll,
11965 window,
11966 cx,
11967 );
11968 }
11969
11970 if selections.len() == 1 {
11971 let selection = selections
11972 .last()
11973 .expect("ensured that there's only one selection");
11974 let query = buffer
11975 .text_for_range(selection.start..selection.end)
11976 .collect::<String>();
11977 let is_empty = query.is_empty();
11978 let select_state = SelectNextState {
11979 query: AhoCorasick::new(&[query])?,
11980 wordwise: true,
11981 done: is_empty,
11982 };
11983 self.select_next_state = Some(select_state);
11984 } else {
11985 self.select_next_state = None;
11986 }
11987 } else if let Some(selected_text) = selected_text {
11988 self.select_next_state = Some(SelectNextState {
11989 query: AhoCorasick::new(&[selected_text])?,
11990 wordwise: false,
11991 done: false,
11992 });
11993 self.select_next_match_internal(
11994 display_map,
11995 replace_newest,
11996 autoscroll,
11997 window,
11998 cx,
11999 )?;
12000 }
12001 }
12002 Ok(())
12003 }
12004
12005 pub fn select_all_matches(
12006 &mut self,
12007 _action: &SelectAllMatches,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) -> Result<()> {
12011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12012
12013 self.push_to_selection_history();
12014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12015
12016 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12017 let Some(select_next_state) = self.select_next_state.as_mut() else {
12018 return Ok(());
12019 };
12020 if select_next_state.done {
12021 return Ok(());
12022 }
12023
12024 let mut new_selections = Vec::new();
12025
12026 let reversed = self.selections.oldest::<usize>(cx).reversed;
12027 let buffer = &display_map.buffer_snapshot;
12028 let query_matches = select_next_state
12029 .query
12030 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12031
12032 for query_match in query_matches.into_iter() {
12033 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12034 let offset_range = if reversed {
12035 query_match.end()..query_match.start()
12036 } else {
12037 query_match.start()..query_match.end()
12038 };
12039 let display_range = offset_range.start.to_display_point(&display_map)
12040 ..offset_range.end.to_display_point(&display_map);
12041
12042 if !select_next_state.wordwise
12043 || (!movement::is_inside_word(&display_map, display_range.start)
12044 && !movement::is_inside_word(&display_map, display_range.end))
12045 {
12046 new_selections.push(offset_range.start..offset_range.end);
12047 }
12048 }
12049
12050 select_next_state.done = true;
12051 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12052 self.change_selections(None, window, cx, |selections| {
12053 selections.select_ranges(new_selections)
12054 });
12055
12056 Ok(())
12057 }
12058
12059 pub fn select_next(
12060 &mut self,
12061 action: &SelectNext,
12062 window: &mut Window,
12063 cx: &mut Context<Self>,
12064 ) -> Result<()> {
12065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12066 self.push_to_selection_history();
12067 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12068 self.select_next_match_internal(
12069 &display_map,
12070 action.replace_newest,
12071 Some(Autoscroll::newest()),
12072 window,
12073 cx,
12074 )?;
12075 Ok(())
12076 }
12077
12078 pub fn select_previous(
12079 &mut self,
12080 action: &SelectPrevious,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) -> Result<()> {
12084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12085 self.push_to_selection_history();
12086 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12087 let buffer = &display_map.buffer_snapshot;
12088 let mut selections = self.selections.all::<usize>(cx);
12089 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12090 let query = &select_prev_state.query;
12091 if !select_prev_state.done {
12092 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12093 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12094 let mut next_selected_range = None;
12095 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12096 let bytes_before_last_selection =
12097 buffer.reversed_bytes_in_range(0..last_selection.start);
12098 let bytes_after_first_selection =
12099 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12100 let query_matches = query
12101 .stream_find_iter(bytes_before_last_selection)
12102 .map(|result| (last_selection.start, result))
12103 .chain(
12104 query
12105 .stream_find_iter(bytes_after_first_selection)
12106 .map(|result| (buffer.len(), result)),
12107 );
12108 for (end_offset, query_match) in query_matches {
12109 let query_match = query_match.unwrap(); // can only fail due to I/O
12110 let offset_range =
12111 end_offset - query_match.end()..end_offset - query_match.start();
12112 let display_range = offset_range.start.to_display_point(&display_map)
12113 ..offset_range.end.to_display_point(&display_map);
12114
12115 if !select_prev_state.wordwise
12116 || (!movement::is_inside_word(&display_map, display_range.start)
12117 && !movement::is_inside_word(&display_map, display_range.end))
12118 {
12119 next_selected_range = Some(offset_range);
12120 break;
12121 }
12122 }
12123
12124 if let Some(next_selected_range) = next_selected_range {
12125 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12126 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12127 if action.replace_newest {
12128 s.delete(s.newest_anchor().id);
12129 }
12130 if last_selection.reversed {
12131 s.insert_range(next_selected_range.end..next_selected_range.start);
12132 } else {
12133 s.insert_range(next_selected_range);
12134 }
12135 });
12136 } else {
12137 select_prev_state.done = true;
12138 }
12139 }
12140
12141 self.select_prev_state = Some(select_prev_state);
12142 } else {
12143 let mut only_carets = true;
12144 let mut same_text_selected = true;
12145 let mut selected_text = None;
12146
12147 let mut selections_iter = selections.iter().peekable();
12148 while let Some(selection) = selections_iter.next() {
12149 if selection.start != selection.end {
12150 only_carets = false;
12151 }
12152
12153 if same_text_selected {
12154 if selected_text.is_none() {
12155 selected_text =
12156 Some(buffer.text_for_range(selection.range()).collect::<String>());
12157 }
12158
12159 if let Some(next_selection) = selections_iter.peek() {
12160 if next_selection.range().len() == selection.range().len() {
12161 let next_selected_text = buffer
12162 .text_for_range(next_selection.range())
12163 .collect::<String>();
12164 if Some(next_selected_text) != selected_text {
12165 same_text_selected = false;
12166 selected_text = None;
12167 }
12168 } else {
12169 same_text_selected = false;
12170 selected_text = None;
12171 }
12172 }
12173 }
12174 }
12175
12176 if only_carets {
12177 for selection in &mut selections {
12178 let word_range = movement::surrounding_word(
12179 &display_map,
12180 selection.start.to_display_point(&display_map),
12181 );
12182 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12183 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12184 selection.goal = SelectionGoal::None;
12185 selection.reversed = false;
12186 }
12187 if selections.len() == 1 {
12188 let selection = selections
12189 .last()
12190 .expect("ensured that there's only one selection");
12191 let query = buffer
12192 .text_for_range(selection.start..selection.end)
12193 .collect::<String>();
12194 let is_empty = query.is_empty();
12195 let select_state = SelectNextState {
12196 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12197 wordwise: true,
12198 done: is_empty,
12199 };
12200 self.select_prev_state = Some(select_state);
12201 } else {
12202 self.select_prev_state = None;
12203 }
12204
12205 self.unfold_ranges(
12206 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12207 false,
12208 true,
12209 cx,
12210 );
12211 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12212 s.select(selections);
12213 });
12214 } else if let Some(selected_text) = selected_text {
12215 self.select_prev_state = Some(SelectNextState {
12216 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12217 wordwise: false,
12218 done: false,
12219 });
12220 self.select_previous(action, window, cx)?;
12221 }
12222 }
12223 Ok(())
12224 }
12225
12226 pub fn find_next_match(
12227 &mut self,
12228 _: &FindNextMatch,
12229 window: &mut Window,
12230 cx: &mut Context<Self>,
12231 ) -> Result<()> {
12232 let selections = self.selections.disjoint_anchors();
12233 match selections.first() {
12234 Some(first) if selections.len() >= 2 => {
12235 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12236 s.select_ranges([first.range()]);
12237 });
12238 }
12239 _ => self.select_next(
12240 &SelectNext {
12241 replace_newest: true,
12242 },
12243 window,
12244 cx,
12245 )?,
12246 }
12247 Ok(())
12248 }
12249
12250 pub fn find_previous_match(
12251 &mut self,
12252 _: &FindPreviousMatch,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 ) -> Result<()> {
12256 let selections = self.selections.disjoint_anchors();
12257 match selections.last() {
12258 Some(last) if selections.len() >= 2 => {
12259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12260 s.select_ranges([last.range()]);
12261 });
12262 }
12263 _ => self.select_previous(
12264 &SelectPrevious {
12265 replace_newest: true,
12266 },
12267 window,
12268 cx,
12269 )?,
12270 }
12271 Ok(())
12272 }
12273
12274 pub fn toggle_comments(
12275 &mut self,
12276 action: &ToggleComments,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 if self.read_only(cx) {
12281 return;
12282 }
12283 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12284 let text_layout_details = &self.text_layout_details(window);
12285 self.transact(window, cx, |this, window, cx| {
12286 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12287 let mut edits = Vec::new();
12288 let mut selection_edit_ranges = Vec::new();
12289 let mut last_toggled_row = None;
12290 let snapshot = this.buffer.read(cx).read(cx);
12291 let empty_str: Arc<str> = Arc::default();
12292 let mut suffixes_inserted = Vec::new();
12293 let ignore_indent = action.ignore_indent;
12294
12295 fn comment_prefix_range(
12296 snapshot: &MultiBufferSnapshot,
12297 row: MultiBufferRow,
12298 comment_prefix: &str,
12299 comment_prefix_whitespace: &str,
12300 ignore_indent: bool,
12301 ) -> Range<Point> {
12302 let indent_size = if ignore_indent {
12303 0
12304 } else {
12305 snapshot.indent_size_for_line(row).len
12306 };
12307
12308 let start = Point::new(row.0, indent_size);
12309
12310 let mut line_bytes = snapshot
12311 .bytes_in_range(start..snapshot.max_point())
12312 .flatten()
12313 .copied();
12314
12315 // If this line currently begins with the line comment prefix, then record
12316 // the range containing the prefix.
12317 if line_bytes
12318 .by_ref()
12319 .take(comment_prefix.len())
12320 .eq(comment_prefix.bytes())
12321 {
12322 // Include any whitespace that matches the comment prefix.
12323 let matching_whitespace_len = line_bytes
12324 .zip(comment_prefix_whitespace.bytes())
12325 .take_while(|(a, b)| a == b)
12326 .count() as u32;
12327 let end = Point::new(
12328 start.row,
12329 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12330 );
12331 start..end
12332 } else {
12333 start..start
12334 }
12335 }
12336
12337 fn comment_suffix_range(
12338 snapshot: &MultiBufferSnapshot,
12339 row: MultiBufferRow,
12340 comment_suffix: &str,
12341 comment_suffix_has_leading_space: bool,
12342 ) -> Range<Point> {
12343 let end = Point::new(row.0, snapshot.line_len(row));
12344 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12345
12346 let mut line_end_bytes = snapshot
12347 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12348 .flatten()
12349 .copied();
12350
12351 let leading_space_len = if suffix_start_column > 0
12352 && line_end_bytes.next() == Some(b' ')
12353 && comment_suffix_has_leading_space
12354 {
12355 1
12356 } else {
12357 0
12358 };
12359
12360 // If this line currently begins with the line comment prefix, then record
12361 // the range containing the prefix.
12362 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12363 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12364 start..end
12365 } else {
12366 end..end
12367 }
12368 }
12369
12370 // TODO: Handle selections that cross excerpts
12371 for selection in &mut selections {
12372 let start_column = snapshot
12373 .indent_size_for_line(MultiBufferRow(selection.start.row))
12374 .len;
12375 let language = if let Some(language) =
12376 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12377 {
12378 language
12379 } else {
12380 continue;
12381 };
12382
12383 selection_edit_ranges.clear();
12384
12385 // If multiple selections contain a given row, avoid processing that
12386 // row more than once.
12387 let mut start_row = MultiBufferRow(selection.start.row);
12388 if last_toggled_row == Some(start_row) {
12389 start_row = start_row.next_row();
12390 }
12391 let end_row =
12392 if selection.end.row > selection.start.row && selection.end.column == 0 {
12393 MultiBufferRow(selection.end.row - 1)
12394 } else {
12395 MultiBufferRow(selection.end.row)
12396 };
12397 last_toggled_row = Some(end_row);
12398
12399 if start_row > end_row {
12400 continue;
12401 }
12402
12403 // If the language has line comments, toggle those.
12404 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12405
12406 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12407 if ignore_indent {
12408 full_comment_prefixes = full_comment_prefixes
12409 .into_iter()
12410 .map(|s| Arc::from(s.trim_end()))
12411 .collect();
12412 }
12413
12414 if !full_comment_prefixes.is_empty() {
12415 let first_prefix = full_comment_prefixes
12416 .first()
12417 .expect("prefixes is non-empty");
12418 let prefix_trimmed_lengths = full_comment_prefixes
12419 .iter()
12420 .map(|p| p.trim_end_matches(' ').len())
12421 .collect::<SmallVec<[usize; 4]>>();
12422
12423 let mut all_selection_lines_are_comments = true;
12424
12425 for row in start_row.0..=end_row.0 {
12426 let row = MultiBufferRow(row);
12427 if start_row < end_row && snapshot.is_line_blank(row) {
12428 continue;
12429 }
12430
12431 let prefix_range = full_comment_prefixes
12432 .iter()
12433 .zip(prefix_trimmed_lengths.iter().copied())
12434 .map(|(prefix, trimmed_prefix_len)| {
12435 comment_prefix_range(
12436 snapshot.deref(),
12437 row,
12438 &prefix[..trimmed_prefix_len],
12439 &prefix[trimmed_prefix_len..],
12440 ignore_indent,
12441 )
12442 })
12443 .max_by_key(|range| range.end.column - range.start.column)
12444 .expect("prefixes is non-empty");
12445
12446 if prefix_range.is_empty() {
12447 all_selection_lines_are_comments = false;
12448 }
12449
12450 selection_edit_ranges.push(prefix_range);
12451 }
12452
12453 if all_selection_lines_are_comments {
12454 edits.extend(
12455 selection_edit_ranges
12456 .iter()
12457 .cloned()
12458 .map(|range| (range, empty_str.clone())),
12459 );
12460 } else {
12461 let min_column = selection_edit_ranges
12462 .iter()
12463 .map(|range| range.start.column)
12464 .min()
12465 .unwrap_or(0);
12466 edits.extend(selection_edit_ranges.iter().map(|range| {
12467 let position = Point::new(range.start.row, min_column);
12468 (position..position, first_prefix.clone())
12469 }));
12470 }
12471 } else if let Some((full_comment_prefix, comment_suffix)) =
12472 language.block_comment_delimiters()
12473 {
12474 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12475 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12476 let prefix_range = comment_prefix_range(
12477 snapshot.deref(),
12478 start_row,
12479 comment_prefix,
12480 comment_prefix_whitespace,
12481 ignore_indent,
12482 );
12483 let suffix_range = comment_suffix_range(
12484 snapshot.deref(),
12485 end_row,
12486 comment_suffix.trim_start_matches(' '),
12487 comment_suffix.starts_with(' '),
12488 );
12489
12490 if prefix_range.is_empty() || suffix_range.is_empty() {
12491 edits.push((
12492 prefix_range.start..prefix_range.start,
12493 full_comment_prefix.clone(),
12494 ));
12495 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12496 suffixes_inserted.push((end_row, comment_suffix.len()));
12497 } else {
12498 edits.push((prefix_range, empty_str.clone()));
12499 edits.push((suffix_range, empty_str.clone()));
12500 }
12501 } else {
12502 continue;
12503 }
12504 }
12505
12506 drop(snapshot);
12507 this.buffer.update(cx, |buffer, cx| {
12508 buffer.edit(edits, None, cx);
12509 });
12510
12511 // Adjust selections so that they end before any comment suffixes that
12512 // were inserted.
12513 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12514 let mut selections = this.selections.all::<Point>(cx);
12515 let snapshot = this.buffer.read(cx).read(cx);
12516 for selection in &mut selections {
12517 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12518 match row.cmp(&MultiBufferRow(selection.end.row)) {
12519 Ordering::Less => {
12520 suffixes_inserted.next();
12521 continue;
12522 }
12523 Ordering::Greater => break,
12524 Ordering::Equal => {
12525 if selection.end.column == snapshot.line_len(row) {
12526 if selection.is_empty() {
12527 selection.start.column -= suffix_len as u32;
12528 }
12529 selection.end.column -= suffix_len as u32;
12530 }
12531 break;
12532 }
12533 }
12534 }
12535 }
12536
12537 drop(snapshot);
12538 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12539 s.select(selections)
12540 });
12541
12542 let selections = this.selections.all::<Point>(cx);
12543 let selections_on_single_row = selections.windows(2).all(|selections| {
12544 selections[0].start.row == selections[1].start.row
12545 && selections[0].end.row == selections[1].end.row
12546 && selections[0].start.row == selections[0].end.row
12547 });
12548 let selections_selecting = selections
12549 .iter()
12550 .any(|selection| selection.start != selection.end);
12551 let advance_downwards = action.advance_downwards
12552 && selections_on_single_row
12553 && !selections_selecting
12554 && !matches!(this.mode, EditorMode::SingleLine { .. });
12555
12556 if advance_downwards {
12557 let snapshot = this.buffer.read(cx).snapshot(cx);
12558
12559 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12560 s.move_cursors_with(|display_snapshot, display_point, _| {
12561 let mut point = display_point.to_point(display_snapshot);
12562 point.row += 1;
12563 point = snapshot.clip_point(point, Bias::Left);
12564 let display_point = point.to_display_point(display_snapshot);
12565 let goal = SelectionGoal::HorizontalPosition(
12566 display_snapshot
12567 .x_for_display_point(display_point, text_layout_details)
12568 .into(),
12569 );
12570 (display_point, goal)
12571 })
12572 });
12573 }
12574 });
12575 }
12576
12577 pub fn select_enclosing_symbol(
12578 &mut self,
12579 _: &SelectEnclosingSymbol,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 ) {
12583 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12584
12585 let buffer = self.buffer.read(cx).snapshot(cx);
12586 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12587
12588 fn update_selection(
12589 selection: &Selection<usize>,
12590 buffer_snap: &MultiBufferSnapshot,
12591 ) -> Option<Selection<usize>> {
12592 let cursor = selection.head();
12593 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12594 for symbol in symbols.iter().rev() {
12595 let start = symbol.range.start.to_offset(buffer_snap);
12596 let end = symbol.range.end.to_offset(buffer_snap);
12597 let new_range = start..end;
12598 if start < selection.start || end > selection.end {
12599 return Some(Selection {
12600 id: selection.id,
12601 start: new_range.start,
12602 end: new_range.end,
12603 goal: SelectionGoal::None,
12604 reversed: selection.reversed,
12605 });
12606 }
12607 }
12608 None
12609 }
12610
12611 let mut selected_larger_symbol = false;
12612 let new_selections = old_selections
12613 .iter()
12614 .map(|selection| match update_selection(selection, &buffer) {
12615 Some(new_selection) => {
12616 if new_selection.range() != selection.range() {
12617 selected_larger_symbol = true;
12618 }
12619 new_selection
12620 }
12621 None => selection.clone(),
12622 })
12623 .collect::<Vec<_>>();
12624
12625 if selected_larger_symbol {
12626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12627 s.select(new_selections);
12628 });
12629 }
12630 }
12631
12632 pub fn select_larger_syntax_node(
12633 &mut self,
12634 _: &SelectLargerSyntaxNode,
12635 window: &mut Window,
12636 cx: &mut Context<Self>,
12637 ) {
12638 let Some(visible_row_count) = self.visible_row_count() else {
12639 return;
12640 };
12641 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12642 if old_selections.is_empty() {
12643 return;
12644 }
12645
12646 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12647
12648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12649 let buffer = self.buffer.read(cx).snapshot(cx);
12650
12651 let mut selected_larger_node = false;
12652 let mut new_selections = old_selections
12653 .iter()
12654 .map(|selection| {
12655 let old_range = selection.start..selection.end;
12656
12657 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12658 // manually select word at selection
12659 if ["string_content", "inline"].contains(&node.kind()) {
12660 let word_range = {
12661 let display_point = buffer
12662 .offset_to_point(old_range.start)
12663 .to_display_point(&display_map);
12664 let Range { start, end } =
12665 movement::surrounding_word(&display_map, display_point);
12666 start.to_point(&display_map).to_offset(&buffer)
12667 ..end.to_point(&display_map).to_offset(&buffer)
12668 };
12669 // ignore if word is already selected
12670 if !word_range.is_empty() && old_range != word_range {
12671 let last_word_range = {
12672 let display_point = buffer
12673 .offset_to_point(old_range.end)
12674 .to_display_point(&display_map);
12675 let Range { start, end } =
12676 movement::surrounding_word(&display_map, display_point);
12677 start.to_point(&display_map).to_offset(&buffer)
12678 ..end.to_point(&display_map).to_offset(&buffer)
12679 };
12680 // only select word if start and end point belongs to same word
12681 if word_range == last_word_range {
12682 selected_larger_node = true;
12683 return Selection {
12684 id: selection.id,
12685 start: word_range.start,
12686 end: word_range.end,
12687 goal: SelectionGoal::None,
12688 reversed: selection.reversed,
12689 };
12690 }
12691 }
12692 }
12693 }
12694
12695 let mut new_range = old_range.clone();
12696 let mut new_node = None;
12697 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12698 {
12699 new_node = Some(node);
12700 new_range = match containing_range {
12701 MultiOrSingleBufferOffsetRange::Single(_) => break,
12702 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12703 };
12704 if !display_map.intersects_fold(new_range.start)
12705 && !display_map.intersects_fold(new_range.end)
12706 {
12707 break;
12708 }
12709 }
12710
12711 if let Some(node) = new_node {
12712 // Log the ancestor, to support using this action as a way to explore TreeSitter
12713 // nodes. Parent and grandparent are also logged because this operation will not
12714 // visit nodes that have the same range as their parent.
12715 log::info!("Node: {node:?}");
12716 let parent = node.parent();
12717 log::info!("Parent: {parent:?}");
12718 let grandparent = parent.and_then(|x| x.parent());
12719 log::info!("Grandparent: {grandparent:?}");
12720 }
12721
12722 selected_larger_node |= new_range != old_range;
12723 Selection {
12724 id: selection.id,
12725 start: new_range.start,
12726 end: new_range.end,
12727 goal: SelectionGoal::None,
12728 reversed: selection.reversed,
12729 }
12730 })
12731 .collect::<Vec<_>>();
12732
12733 if !selected_larger_node {
12734 return; // don't put this call in the history
12735 }
12736
12737 // scroll based on transformation done to the last selection created by the user
12738 let (last_old, last_new) = old_selections
12739 .last()
12740 .zip(new_selections.last().cloned())
12741 .expect("old_selections isn't empty");
12742
12743 // revert selection
12744 let is_selection_reversed = {
12745 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12746 new_selections.last_mut().expect("checked above").reversed =
12747 should_newest_selection_be_reversed;
12748 should_newest_selection_be_reversed
12749 };
12750
12751 if selected_larger_node {
12752 self.select_syntax_node_history.disable_clearing = true;
12753 self.change_selections(None, window, cx, |s| {
12754 s.select(new_selections.clone());
12755 });
12756 self.select_syntax_node_history.disable_clearing = false;
12757 }
12758
12759 let start_row = last_new.start.to_display_point(&display_map).row().0;
12760 let end_row = last_new.end.to_display_point(&display_map).row().0;
12761 let selection_height = end_row - start_row + 1;
12762 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12763
12764 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12765 let scroll_behavior = if fits_on_the_screen {
12766 self.request_autoscroll(Autoscroll::fit(), cx);
12767 SelectSyntaxNodeScrollBehavior::FitSelection
12768 } else if is_selection_reversed {
12769 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12770 SelectSyntaxNodeScrollBehavior::CursorTop
12771 } else {
12772 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12773 SelectSyntaxNodeScrollBehavior::CursorBottom
12774 };
12775
12776 self.select_syntax_node_history.push((
12777 old_selections,
12778 scroll_behavior,
12779 is_selection_reversed,
12780 ));
12781 }
12782
12783 pub fn select_smaller_syntax_node(
12784 &mut self,
12785 _: &SelectSmallerSyntaxNode,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12790
12791 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12792 self.select_syntax_node_history.pop()
12793 {
12794 if let Some(selection) = selections.last_mut() {
12795 selection.reversed = is_selection_reversed;
12796 }
12797
12798 self.select_syntax_node_history.disable_clearing = true;
12799 self.change_selections(None, window, cx, |s| {
12800 s.select(selections.to_vec());
12801 });
12802 self.select_syntax_node_history.disable_clearing = false;
12803
12804 match scroll_behavior {
12805 SelectSyntaxNodeScrollBehavior::CursorTop => {
12806 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12807 }
12808 SelectSyntaxNodeScrollBehavior::FitSelection => {
12809 self.request_autoscroll(Autoscroll::fit(), cx);
12810 }
12811 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12812 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12813 }
12814 }
12815 }
12816 }
12817
12818 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12819 if !EditorSettings::get_global(cx).gutter.runnables {
12820 self.clear_tasks();
12821 return Task::ready(());
12822 }
12823 let project = self.project.as_ref().map(Entity::downgrade);
12824 let task_sources = self.lsp_task_sources(cx);
12825 cx.spawn_in(window, async move |editor, cx| {
12826 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12827 let Some(project) = project.and_then(|p| p.upgrade()) else {
12828 return;
12829 };
12830 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12831 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12832 }) else {
12833 return;
12834 };
12835
12836 let hide_runnables = project
12837 .update(cx, |project, cx| {
12838 // Do not display any test indicators in non-dev server remote projects.
12839 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12840 })
12841 .unwrap_or(true);
12842 if hide_runnables {
12843 return;
12844 }
12845 let new_rows =
12846 cx.background_spawn({
12847 let snapshot = display_snapshot.clone();
12848 async move {
12849 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12850 }
12851 })
12852 .await;
12853 let Ok(lsp_tasks) =
12854 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12855 else {
12856 return;
12857 };
12858 let lsp_tasks = lsp_tasks.await;
12859
12860 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12861 lsp_tasks
12862 .into_iter()
12863 .flat_map(|(kind, tasks)| {
12864 tasks.into_iter().filter_map(move |(location, task)| {
12865 Some((kind.clone(), location?, task))
12866 })
12867 })
12868 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12869 let buffer = location.target.buffer;
12870 let buffer_snapshot = buffer.read(cx).snapshot();
12871 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12872 |(excerpt_id, snapshot, _)| {
12873 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12874 display_snapshot
12875 .buffer_snapshot
12876 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12877 } else {
12878 None
12879 }
12880 },
12881 );
12882 if let Some(offset) = offset {
12883 let task_buffer_range =
12884 location.target.range.to_point(&buffer_snapshot);
12885 let context_buffer_range =
12886 task_buffer_range.to_offset(&buffer_snapshot);
12887 let context_range = BufferOffset(context_buffer_range.start)
12888 ..BufferOffset(context_buffer_range.end);
12889
12890 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12891 .or_insert_with(|| RunnableTasks {
12892 templates: Vec::new(),
12893 offset,
12894 column: task_buffer_range.start.column,
12895 extra_variables: HashMap::default(),
12896 context_range,
12897 })
12898 .templates
12899 .push((kind, task.original_task().clone()));
12900 }
12901
12902 acc
12903 })
12904 }) else {
12905 return;
12906 };
12907
12908 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12909 editor
12910 .update(cx, |editor, _| {
12911 editor.clear_tasks();
12912 for (key, mut value) in rows {
12913 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12914 value.templates.extend(lsp_tasks.templates);
12915 }
12916
12917 editor.insert_tasks(key, value);
12918 }
12919 for (key, value) in lsp_tasks_by_rows {
12920 editor.insert_tasks(key, value);
12921 }
12922 })
12923 .ok();
12924 })
12925 }
12926 fn fetch_runnable_ranges(
12927 snapshot: &DisplaySnapshot,
12928 range: Range<Anchor>,
12929 ) -> Vec<language::RunnableRange> {
12930 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12931 }
12932
12933 fn runnable_rows(
12934 project: Entity<Project>,
12935 snapshot: DisplaySnapshot,
12936 runnable_ranges: Vec<RunnableRange>,
12937 mut cx: AsyncWindowContext,
12938 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12939 runnable_ranges
12940 .into_iter()
12941 .filter_map(|mut runnable| {
12942 let tasks = cx
12943 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12944 .ok()?;
12945 if tasks.is_empty() {
12946 return None;
12947 }
12948
12949 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12950
12951 let row = snapshot
12952 .buffer_snapshot
12953 .buffer_line_for_row(MultiBufferRow(point.row))?
12954 .1
12955 .start
12956 .row;
12957
12958 let context_range =
12959 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12960 Some((
12961 (runnable.buffer_id, row),
12962 RunnableTasks {
12963 templates: tasks,
12964 offset: snapshot
12965 .buffer_snapshot
12966 .anchor_before(runnable.run_range.start),
12967 context_range,
12968 column: point.column,
12969 extra_variables: runnable.extra_captures,
12970 },
12971 ))
12972 })
12973 .collect()
12974 }
12975
12976 fn templates_with_tags(
12977 project: &Entity<Project>,
12978 runnable: &mut Runnable,
12979 cx: &mut App,
12980 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12981 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12982 let (worktree_id, file) = project
12983 .buffer_for_id(runnable.buffer, cx)
12984 .and_then(|buffer| buffer.read(cx).file())
12985 .map(|file| (file.worktree_id(cx), file.clone()))
12986 .unzip();
12987
12988 (
12989 project.task_store().read(cx).task_inventory().cloned(),
12990 worktree_id,
12991 file,
12992 )
12993 });
12994
12995 let mut templates_with_tags = mem::take(&mut runnable.tags)
12996 .into_iter()
12997 .flat_map(|RunnableTag(tag)| {
12998 inventory
12999 .as_ref()
13000 .into_iter()
13001 .flat_map(|inventory| {
13002 inventory.read(cx).list_tasks(
13003 file.clone(),
13004 Some(runnable.language.clone()),
13005 worktree_id,
13006 cx,
13007 )
13008 })
13009 .filter(move |(_, template)| {
13010 template.tags.iter().any(|source_tag| source_tag == &tag)
13011 })
13012 })
13013 .sorted_by_key(|(kind, _)| kind.to_owned())
13014 .collect::<Vec<_>>();
13015 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13016 // Strongest source wins; if we have worktree tag binding, prefer that to
13017 // global and language bindings;
13018 // if we have a global binding, prefer that to language binding.
13019 let first_mismatch = templates_with_tags
13020 .iter()
13021 .position(|(tag_source, _)| tag_source != leading_tag_source);
13022 if let Some(index) = first_mismatch {
13023 templates_with_tags.truncate(index);
13024 }
13025 }
13026
13027 templates_with_tags
13028 }
13029
13030 pub fn move_to_enclosing_bracket(
13031 &mut self,
13032 _: &MoveToEnclosingBracket,
13033 window: &mut Window,
13034 cx: &mut Context<Self>,
13035 ) {
13036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13038 s.move_offsets_with(|snapshot, selection| {
13039 let Some(enclosing_bracket_ranges) =
13040 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13041 else {
13042 return;
13043 };
13044
13045 let mut best_length = usize::MAX;
13046 let mut best_inside = false;
13047 let mut best_in_bracket_range = false;
13048 let mut best_destination = None;
13049 for (open, close) in enclosing_bracket_ranges {
13050 let close = close.to_inclusive();
13051 let length = close.end() - open.start;
13052 let inside = selection.start >= open.end && selection.end <= *close.start();
13053 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13054 || close.contains(&selection.head());
13055
13056 // If best is next to a bracket and current isn't, skip
13057 if !in_bracket_range && best_in_bracket_range {
13058 continue;
13059 }
13060
13061 // Prefer smaller lengths unless best is inside and current isn't
13062 if length > best_length && (best_inside || !inside) {
13063 continue;
13064 }
13065
13066 best_length = length;
13067 best_inside = inside;
13068 best_in_bracket_range = in_bracket_range;
13069 best_destination = Some(
13070 if close.contains(&selection.start) && close.contains(&selection.end) {
13071 if inside { open.end } else { open.start }
13072 } else if inside {
13073 *close.start()
13074 } else {
13075 *close.end()
13076 },
13077 );
13078 }
13079
13080 if let Some(destination) = best_destination {
13081 selection.collapse_to(destination, SelectionGoal::None);
13082 }
13083 })
13084 });
13085 }
13086
13087 pub fn undo_selection(
13088 &mut self,
13089 _: &UndoSelection,
13090 window: &mut Window,
13091 cx: &mut Context<Self>,
13092 ) {
13093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13094 self.end_selection(window, cx);
13095 self.selection_history.mode = SelectionHistoryMode::Undoing;
13096 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13097 self.change_selections(None, window, cx, |s| {
13098 s.select_anchors(entry.selections.to_vec())
13099 });
13100 self.select_next_state = entry.select_next_state;
13101 self.select_prev_state = entry.select_prev_state;
13102 self.add_selections_state = entry.add_selections_state;
13103 self.request_autoscroll(Autoscroll::newest(), cx);
13104 }
13105 self.selection_history.mode = SelectionHistoryMode::Normal;
13106 }
13107
13108 pub fn redo_selection(
13109 &mut self,
13110 _: &RedoSelection,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13115 self.end_selection(window, cx);
13116 self.selection_history.mode = SelectionHistoryMode::Redoing;
13117 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13118 self.change_selections(None, window, cx, |s| {
13119 s.select_anchors(entry.selections.to_vec())
13120 });
13121 self.select_next_state = entry.select_next_state;
13122 self.select_prev_state = entry.select_prev_state;
13123 self.add_selections_state = entry.add_selections_state;
13124 self.request_autoscroll(Autoscroll::newest(), cx);
13125 }
13126 self.selection_history.mode = SelectionHistoryMode::Normal;
13127 }
13128
13129 pub fn expand_excerpts(
13130 &mut self,
13131 action: &ExpandExcerpts,
13132 _: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13136 }
13137
13138 pub fn expand_excerpts_down(
13139 &mut self,
13140 action: &ExpandExcerptsDown,
13141 _: &mut Window,
13142 cx: &mut Context<Self>,
13143 ) {
13144 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13145 }
13146
13147 pub fn expand_excerpts_up(
13148 &mut self,
13149 action: &ExpandExcerptsUp,
13150 _: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13154 }
13155
13156 pub fn expand_excerpts_for_direction(
13157 &mut self,
13158 lines: u32,
13159 direction: ExpandExcerptDirection,
13160
13161 cx: &mut Context<Self>,
13162 ) {
13163 let selections = self.selections.disjoint_anchors();
13164
13165 let lines = if lines == 0 {
13166 EditorSettings::get_global(cx).expand_excerpt_lines
13167 } else {
13168 lines
13169 };
13170
13171 self.buffer.update(cx, |buffer, cx| {
13172 let snapshot = buffer.snapshot(cx);
13173 let mut excerpt_ids = selections
13174 .iter()
13175 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13176 .collect::<Vec<_>>();
13177 excerpt_ids.sort();
13178 excerpt_ids.dedup();
13179 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13180 })
13181 }
13182
13183 pub fn expand_excerpt(
13184 &mut self,
13185 excerpt: ExcerptId,
13186 direction: ExpandExcerptDirection,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 let current_scroll_position = self.scroll_position(cx);
13191 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13192 let mut should_scroll_up = false;
13193
13194 if direction == ExpandExcerptDirection::Down {
13195 let multi_buffer = self.buffer.read(cx);
13196 let snapshot = multi_buffer.snapshot(cx);
13197 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13198 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13199 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13200 let buffer_snapshot = buffer.read(cx).snapshot();
13201 let excerpt_end_row =
13202 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13203 let last_row = buffer_snapshot.max_point().row;
13204 let lines_below = last_row.saturating_sub(excerpt_end_row);
13205 should_scroll_up = lines_below >= lines_to_expand;
13206 }
13207 }
13208 }
13209 }
13210
13211 self.buffer.update(cx, |buffer, cx| {
13212 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13213 });
13214
13215 if should_scroll_up {
13216 let new_scroll_position =
13217 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13218 self.set_scroll_position(new_scroll_position, window, cx);
13219 }
13220 }
13221
13222 pub fn go_to_singleton_buffer_point(
13223 &mut self,
13224 point: Point,
13225 window: &mut Window,
13226 cx: &mut Context<Self>,
13227 ) {
13228 self.go_to_singleton_buffer_range(point..point, window, cx);
13229 }
13230
13231 pub fn go_to_singleton_buffer_range(
13232 &mut self,
13233 range: Range<Point>,
13234 window: &mut Window,
13235 cx: &mut Context<Self>,
13236 ) {
13237 let multibuffer = self.buffer().read(cx);
13238 let Some(buffer) = multibuffer.as_singleton() else {
13239 return;
13240 };
13241 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13242 return;
13243 };
13244 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13245 return;
13246 };
13247 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13248 s.select_anchor_ranges([start..end])
13249 });
13250 }
13251
13252 pub fn go_to_diagnostic(
13253 &mut self,
13254 _: &GoToDiagnostic,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13259 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13260 }
13261
13262 pub fn go_to_prev_diagnostic(
13263 &mut self,
13264 _: &GoToPreviousDiagnostic,
13265 window: &mut Window,
13266 cx: &mut Context<Self>,
13267 ) {
13268 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13269 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13270 }
13271
13272 pub fn go_to_diagnostic_impl(
13273 &mut self,
13274 direction: Direction,
13275 window: &mut Window,
13276 cx: &mut Context<Self>,
13277 ) {
13278 let buffer = self.buffer.read(cx).snapshot(cx);
13279 let selection = self.selections.newest::<usize>(cx);
13280
13281 let mut active_group_id = None;
13282 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13283 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13284 active_group_id = Some(active_group.group_id);
13285 }
13286 }
13287
13288 fn filtered(
13289 snapshot: EditorSnapshot,
13290 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13291 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13292 diagnostics
13293 .filter(|entry| entry.range.start != entry.range.end)
13294 .filter(|entry| !entry.diagnostic.is_unnecessary)
13295 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13296 }
13297
13298 let snapshot = self.snapshot(window, cx);
13299 let before = filtered(
13300 snapshot.clone(),
13301 buffer
13302 .diagnostics_in_range(0..selection.start)
13303 .filter(|entry| entry.range.start <= selection.start),
13304 );
13305 let after = filtered(
13306 snapshot,
13307 buffer
13308 .diagnostics_in_range(selection.start..buffer.len())
13309 .filter(|entry| entry.range.start >= selection.start),
13310 );
13311
13312 let mut found: Option<DiagnosticEntry<usize>> = None;
13313 if direction == Direction::Prev {
13314 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13315 {
13316 for diagnostic in prev_diagnostics.into_iter().rev() {
13317 if diagnostic.range.start != selection.start
13318 || active_group_id
13319 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13320 {
13321 found = Some(diagnostic);
13322 break 'outer;
13323 }
13324 }
13325 }
13326 } else {
13327 for diagnostic in after.chain(before) {
13328 if diagnostic.range.start != selection.start
13329 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13330 {
13331 found = Some(diagnostic);
13332 break;
13333 }
13334 }
13335 }
13336 let Some(next_diagnostic) = found else {
13337 return;
13338 };
13339
13340 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13341 return;
13342 };
13343 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13344 s.select_ranges(vec![
13345 next_diagnostic.range.start..next_diagnostic.range.start,
13346 ])
13347 });
13348 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13349 self.refresh_inline_completion(false, true, window, cx);
13350 }
13351
13352 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13353 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13354 let snapshot = self.snapshot(window, cx);
13355 let selection = self.selections.newest::<Point>(cx);
13356 self.go_to_hunk_before_or_after_position(
13357 &snapshot,
13358 selection.head(),
13359 Direction::Next,
13360 window,
13361 cx,
13362 );
13363 }
13364
13365 pub fn go_to_hunk_before_or_after_position(
13366 &mut self,
13367 snapshot: &EditorSnapshot,
13368 position: Point,
13369 direction: Direction,
13370 window: &mut Window,
13371 cx: &mut Context<Editor>,
13372 ) {
13373 let row = if direction == Direction::Next {
13374 self.hunk_after_position(snapshot, position)
13375 .map(|hunk| hunk.row_range.start)
13376 } else {
13377 self.hunk_before_position(snapshot, position)
13378 };
13379
13380 if let Some(row) = row {
13381 let destination = Point::new(row.0, 0);
13382 let autoscroll = Autoscroll::center();
13383
13384 self.unfold_ranges(&[destination..destination], false, false, cx);
13385 self.change_selections(Some(autoscroll), window, cx, |s| {
13386 s.select_ranges([destination..destination]);
13387 });
13388 }
13389 }
13390
13391 fn hunk_after_position(
13392 &mut self,
13393 snapshot: &EditorSnapshot,
13394 position: Point,
13395 ) -> Option<MultiBufferDiffHunk> {
13396 snapshot
13397 .buffer_snapshot
13398 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13399 .find(|hunk| hunk.row_range.start.0 > position.row)
13400 .or_else(|| {
13401 snapshot
13402 .buffer_snapshot
13403 .diff_hunks_in_range(Point::zero()..position)
13404 .find(|hunk| hunk.row_range.end.0 < position.row)
13405 })
13406 }
13407
13408 fn go_to_prev_hunk(
13409 &mut self,
13410 _: &GoToPreviousHunk,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13415 let snapshot = self.snapshot(window, cx);
13416 let selection = self.selections.newest::<Point>(cx);
13417 self.go_to_hunk_before_or_after_position(
13418 &snapshot,
13419 selection.head(),
13420 Direction::Prev,
13421 window,
13422 cx,
13423 );
13424 }
13425
13426 fn hunk_before_position(
13427 &mut self,
13428 snapshot: &EditorSnapshot,
13429 position: Point,
13430 ) -> Option<MultiBufferRow> {
13431 snapshot
13432 .buffer_snapshot
13433 .diff_hunk_before(position)
13434 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13435 }
13436
13437 fn go_to_next_change(
13438 &mut self,
13439 _: &GoToNextChange,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 if let Some(selections) = self
13444 .change_list
13445 .next_change(1, Direction::Next)
13446 .map(|s| s.to_vec())
13447 {
13448 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13449 let map = s.display_map();
13450 s.select_display_ranges(selections.iter().map(|a| {
13451 let point = a.to_display_point(&map);
13452 point..point
13453 }))
13454 })
13455 }
13456 }
13457
13458 fn go_to_previous_change(
13459 &mut self,
13460 _: &GoToPreviousChange,
13461 window: &mut Window,
13462 cx: &mut Context<Self>,
13463 ) {
13464 if let Some(selections) = self
13465 .change_list
13466 .next_change(1, Direction::Prev)
13467 .map(|s| s.to_vec())
13468 {
13469 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13470 let map = s.display_map();
13471 s.select_display_ranges(selections.iter().map(|a| {
13472 let point = a.to_display_point(&map);
13473 point..point
13474 }))
13475 })
13476 }
13477 }
13478
13479 fn go_to_line<T: 'static>(
13480 &mut self,
13481 position: Anchor,
13482 highlight_color: Option<Hsla>,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 let snapshot = self.snapshot(window, cx).display_snapshot;
13487 let position = position.to_point(&snapshot.buffer_snapshot);
13488 let start = snapshot
13489 .buffer_snapshot
13490 .clip_point(Point::new(position.row, 0), Bias::Left);
13491 let end = start + Point::new(1, 0);
13492 let start = snapshot.buffer_snapshot.anchor_before(start);
13493 let end = snapshot.buffer_snapshot.anchor_before(end);
13494
13495 self.highlight_rows::<T>(
13496 start..end,
13497 highlight_color
13498 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13499 Default::default(),
13500 cx,
13501 );
13502 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13503 }
13504
13505 pub fn go_to_definition(
13506 &mut self,
13507 _: &GoToDefinition,
13508 window: &mut Window,
13509 cx: &mut Context<Self>,
13510 ) -> Task<Result<Navigated>> {
13511 let definition =
13512 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13513 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13514 cx.spawn_in(window, async move |editor, cx| {
13515 if definition.await? == Navigated::Yes {
13516 return Ok(Navigated::Yes);
13517 }
13518 match fallback_strategy {
13519 GoToDefinitionFallback::None => Ok(Navigated::No),
13520 GoToDefinitionFallback::FindAllReferences => {
13521 match editor.update_in(cx, |editor, window, cx| {
13522 editor.find_all_references(&FindAllReferences, window, cx)
13523 })? {
13524 Some(references) => references.await,
13525 None => Ok(Navigated::No),
13526 }
13527 }
13528 }
13529 })
13530 }
13531
13532 pub fn go_to_declaration(
13533 &mut self,
13534 _: &GoToDeclaration,
13535 window: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) -> Task<Result<Navigated>> {
13538 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13539 }
13540
13541 pub fn go_to_declaration_split(
13542 &mut self,
13543 _: &GoToDeclaration,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) -> Task<Result<Navigated>> {
13547 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13548 }
13549
13550 pub fn go_to_implementation(
13551 &mut self,
13552 _: &GoToImplementation,
13553 window: &mut Window,
13554 cx: &mut Context<Self>,
13555 ) -> Task<Result<Navigated>> {
13556 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13557 }
13558
13559 pub fn go_to_implementation_split(
13560 &mut self,
13561 _: &GoToImplementationSplit,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) -> Task<Result<Navigated>> {
13565 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13566 }
13567
13568 pub fn go_to_type_definition(
13569 &mut self,
13570 _: &GoToTypeDefinition,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) -> Task<Result<Navigated>> {
13574 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13575 }
13576
13577 pub fn go_to_definition_split(
13578 &mut self,
13579 _: &GoToDefinitionSplit,
13580 window: &mut Window,
13581 cx: &mut Context<Self>,
13582 ) -> Task<Result<Navigated>> {
13583 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13584 }
13585
13586 pub fn go_to_type_definition_split(
13587 &mut self,
13588 _: &GoToTypeDefinitionSplit,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) -> Task<Result<Navigated>> {
13592 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13593 }
13594
13595 fn go_to_definition_of_kind(
13596 &mut self,
13597 kind: GotoDefinitionKind,
13598 split: bool,
13599 window: &mut Window,
13600 cx: &mut Context<Self>,
13601 ) -> Task<Result<Navigated>> {
13602 let Some(provider) = self.semantics_provider.clone() else {
13603 return Task::ready(Ok(Navigated::No));
13604 };
13605 let head = self.selections.newest::<usize>(cx).head();
13606 let buffer = self.buffer.read(cx);
13607 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13608 text_anchor
13609 } else {
13610 return Task::ready(Ok(Navigated::No));
13611 };
13612
13613 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13614 return Task::ready(Ok(Navigated::No));
13615 };
13616
13617 cx.spawn_in(window, async move |editor, cx| {
13618 let definitions = definitions.await?;
13619 let navigated = editor
13620 .update_in(cx, |editor, window, cx| {
13621 editor.navigate_to_hover_links(
13622 Some(kind),
13623 definitions
13624 .into_iter()
13625 .filter(|location| {
13626 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13627 })
13628 .map(HoverLink::Text)
13629 .collect::<Vec<_>>(),
13630 split,
13631 window,
13632 cx,
13633 )
13634 })?
13635 .await?;
13636 anyhow::Ok(navigated)
13637 })
13638 }
13639
13640 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13641 let selection = self.selections.newest_anchor();
13642 let head = selection.head();
13643 let tail = selection.tail();
13644
13645 let Some((buffer, start_position)) =
13646 self.buffer.read(cx).text_anchor_for_position(head, cx)
13647 else {
13648 return;
13649 };
13650
13651 let end_position = if head != tail {
13652 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13653 return;
13654 };
13655 Some(pos)
13656 } else {
13657 None
13658 };
13659
13660 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13661 let url = if let Some(end_pos) = end_position {
13662 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13663 } else {
13664 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13665 };
13666
13667 if let Some(url) = url {
13668 editor.update(cx, |_, cx| {
13669 cx.open_url(&url);
13670 })
13671 } else {
13672 Ok(())
13673 }
13674 });
13675
13676 url_finder.detach();
13677 }
13678
13679 pub fn open_selected_filename(
13680 &mut self,
13681 _: &OpenSelectedFilename,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 let Some(workspace) = self.workspace() else {
13686 return;
13687 };
13688
13689 let position = self.selections.newest_anchor().head();
13690
13691 let Some((buffer, buffer_position)) =
13692 self.buffer.read(cx).text_anchor_for_position(position, cx)
13693 else {
13694 return;
13695 };
13696
13697 let project = self.project.clone();
13698
13699 cx.spawn_in(window, async move |_, cx| {
13700 let result = find_file(&buffer, project, buffer_position, cx).await;
13701
13702 if let Some((_, path)) = result {
13703 workspace
13704 .update_in(cx, |workspace, window, cx| {
13705 workspace.open_resolved_path(path, window, cx)
13706 })?
13707 .await?;
13708 }
13709 anyhow::Ok(())
13710 })
13711 .detach();
13712 }
13713
13714 pub(crate) fn navigate_to_hover_links(
13715 &mut self,
13716 kind: Option<GotoDefinitionKind>,
13717 mut definitions: Vec<HoverLink>,
13718 split: bool,
13719 window: &mut Window,
13720 cx: &mut Context<Editor>,
13721 ) -> Task<Result<Navigated>> {
13722 // If there is one definition, just open it directly
13723 if definitions.len() == 1 {
13724 let definition = definitions.pop().unwrap();
13725
13726 enum TargetTaskResult {
13727 Location(Option<Location>),
13728 AlreadyNavigated,
13729 }
13730
13731 let target_task = match definition {
13732 HoverLink::Text(link) => {
13733 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13734 }
13735 HoverLink::InlayHint(lsp_location, server_id) => {
13736 let computation =
13737 self.compute_target_location(lsp_location, server_id, window, cx);
13738 cx.background_spawn(async move {
13739 let location = computation.await?;
13740 Ok(TargetTaskResult::Location(location))
13741 })
13742 }
13743 HoverLink::Url(url) => {
13744 cx.open_url(&url);
13745 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13746 }
13747 HoverLink::File(path) => {
13748 if let Some(workspace) = self.workspace() {
13749 cx.spawn_in(window, async move |_, cx| {
13750 workspace
13751 .update_in(cx, |workspace, window, cx| {
13752 workspace.open_resolved_path(path, window, cx)
13753 })?
13754 .await
13755 .map(|_| TargetTaskResult::AlreadyNavigated)
13756 })
13757 } else {
13758 Task::ready(Ok(TargetTaskResult::Location(None)))
13759 }
13760 }
13761 };
13762 cx.spawn_in(window, async move |editor, cx| {
13763 let target = match target_task.await.context("target resolution task")? {
13764 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13765 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13766 TargetTaskResult::Location(Some(target)) => target,
13767 };
13768
13769 editor.update_in(cx, |editor, window, cx| {
13770 let Some(workspace) = editor.workspace() else {
13771 return Navigated::No;
13772 };
13773 let pane = workspace.read(cx).active_pane().clone();
13774
13775 let range = target.range.to_point(target.buffer.read(cx));
13776 let range = editor.range_for_match(&range);
13777 let range = collapse_multiline_range(range);
13778
13779 if !split
13780 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13781 {
13782 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13783 } else {
13784 window.defer(cx, move |window, cx| {
13785 let target_editor: Entity<Self> =
13786 workspace.update(cx, |workspace, cx| {
13787 let pane = if split {
13788 workspace.adjacent_pane(window, cx)
13789 } else {
13790 workspace.active_pane().clone()
13791 };
13792
13793 workspace.open_project_item(
13794 pane,
13795 target.buffer.clone(),
13796 true,
13797 true,
13798 window,
13799 cx,
13800 )
13801 });
13802 target_editor.update(cx, |target_editor, cx| {
13803 // When selecting a definition in a different buffer, disable the nav history
13804 // to avoid creating a history entry at the previous cursor location.
13805 pane.update(cx, |pane, _| pane.disable_history());
13806 target_editor.go_to_singleton_buffer_range(range, window, cx);
13807 pane.update(cx, |pane, _| pane.enable_history());
13808 });
13809 });
13810 }
13811 Navigated::Yes
13812 })
13813 })
13814 } else if !definitions.is_empty() {
13815 cx.spawn_in(window, async move |editor, cx| {
13816 let (title, location_tasks, workspace) = editor
13817 .update_in(cx, |editor, window, cx| {
13818 let tab_kind = match kind {
13819 Some(GotoDefinitionKind::Implementation) => "Implementations",
13820 _ => "Definitions",
13821 };
13822 let title = definitions
13823 .iter()
13824 .find_map(|definition| match definition {
13825 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13826 let buffer = origin.buffer.read(cx);
13827 format!(
13828 "{} for {}",
13829 tab_kind,
13830 buffer
13831 .text_for_range(origin.range.clone())
13832 .collect::<String>()
13833 )
13834 }),
13835 HoverLink::InlayHint(_, _) => None,
13836 HoverLink::Url(_) => None,
13837 HoverLink::File(_) => None,
13838 })
13839 .unwrap_or(tab_kind.to_string());
13840 let location_tasks = definitions
13841 .into_iter()
13842 .map(|definition| match definition {
13843 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13844 HoverLink::InlayHint(lsp_location, server_id) => editor
13845 .compute_target_location(lsp_location, server_id, window, cx),
13846 HoverLink::Url(_) => Task::ready(Ok(None)),
13847 HoverLink::File(_) => Task::ready(Ok(None)),
13848 })
13849 .collect::<Vec<_>>();
13850 (title, location_tasks, editor.workspace().clone())
13851 })
13852 .context("location tasks preparation")?;
13853
13854 let locations = future::join_all(location_tasks)
13855 .await
13856 .into_iter()
13857 .filter_map(|location| location.transpose())
13858 .collect::<Result<_>>()
13859 .context("location tasks")?;
13860
13861 let Some(workspace) = workspace else {
13862 return Ok(Navigated::No);
13863 };
13864 let opened = workspace
13865 .update_in(cx, |workspace, window, cx| {
13866 Self::open_locations_in_multibuffer(
13867 workspace,
13868 locations,
13869 title,
13870 split,
13871 MultibufferSelectionMode::First,
13872 window,
13873 cx,
13874 )
13875 })
13876 .ok();
13877
13878 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13879 })
13880 } else {
13881 Task::ready(Ok(Navigated::No))
13882 }
13883 }
13884
13885 fn compute_target_location(
13886 &self,
13887 lsp_location: lsp::Location,
13888 server_id: LanguageServerId,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) -> Task<anyhow::Result<Option<Location>>> {
13892 let Some(project) = self.project.clone() else {
13893 return Task::ready(Ok(None));
13894 };
13895
13896 cx.spawn_in(window, async move |editor, cx| {
13897 let location_task = editor.update(cx, |_, cx| {
13898 project.update(cx, |project, cx| {
13899 let language_server_name = project
13900 .language_server_statuses(cx)
13901 .find(|(id, _)| server_id == *id)
13902 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13903 language_server_name.map(|language_server_name| {
13904 project.open_local_buffer_via_lsp(
13905 lsp_location.uri.clone(),
13906 server_id,
13907 language_server_name,
13908 cx,
13909 )
13910 })
13911 })
13912 })?;
13913 let location = match location_task {
13914 Some(task) => Some({
13915 let target_buffer_handle = task.await.context("open local buffer")?;
13916 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13917 let target_start = target_buffer
13918 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13919 let target_end = target_buffer
13920 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13921 target_buffer.anchor_after(target_start)
13922 ..target_buffer.anchor_before(target_end)
13923 })?;
13924 Location {
13925 buffer: target_buffer_handle,
13926 range,
13927 }
13928 }),
13929 None => None,
13930 };
13931 Ok(location)
13932 })
13933 }
13934
13935 pub fn find_all_references(
13936 &mut self,
13937 _: &FindAllReferences,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) -> Option<Task<Result<Navigated>>> {
13941 let selection = self.selections.newest::<usize>(cx);
13942 let multi_buffer = self.buffer.read(cx);
13943 let head = selection.head();
13944
13945 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13946 let head_anchor = multi_buffer_snapshot.anchor_at(
13947 head,
13948 if head < selection.tail() {
13949 Bias::Right
13950 } else {
13951 Bias::Left
13952 },
13953 );
13954
13955 match self
13956 .find_all_references_task_sources
13957 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13958 {
13959 Ok(_) => {
13960 log::info!(
13961 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13962 );
13963 return None;
13964 }
13965 Err(i) => {
13966 self.find_all_references_task_sources.insert(i, head_anchor);
13967 }
13968 }
13969
13970 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13971 let workspace = self.workspace()?;
13972 let project = workspace.read(cx).project().clone();
13973 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13974 Some(cx.spawn_in(window, async move |editor, cx| {
13975 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13976 if let Ok(i) = editor
13977 .find_all_references_task_sources
13978 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13979 {
13980 editor.find_all_references_task_sources.remove(i);
13981 }
13982 });
13983
13984 let locations = references.await?;
13985 if locations.is_empty() {
13986 return anyhow::Ok(Navigated::No);
13987 }
13988
13989 workspace.update_in(cx, |workspace, window, cx| {
13990 let title = locations
13991 .first()
13992 .as_ref()
13993 .map(|location| {
13994 let buffer = location.buffer.read(cx);
13995 format!(
13996 "References to `{}`",
13997 buffer
13998 .text_for_range(location.range.clone())
13999 .collect::<String>()
14000 )
14001 })
14002 .unwrap();
14003 Self::open_locations_in_multibuffer(
14004 workspace,
14005 locations,
14006 title,
14007 false,
14008 MultibufferSelectionMode::First,
14009 window,
14010 cx,
14011 );
14012 Navigated::Yes
14013 })
14014 }))
14015 }
14016
14017 /// Opens a multibuffer with the given project locations in it
14018 pub fn open_locations_in_multibuffer(
14019 workspace: &mut Workspace,
14020 mut locations: Vec<Location>,
14021 title: String,
14022 split: bool,
14023 multibuffer_selection_mode: MultibufferSelectionMode,
14024 window: &mut Window,
14025 cx: &mut Context<Workspace>,
14026 ) {
14027 // If there are multiple definitions, open them in a multibuffer
14028 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14029 let mut locations = locations.into_iter().peekable();
14030 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14031 let capability = workspace.project().read(cx).capability();
14032
14033 let excerpt_buffer = cx.new(|cx| {
14034 let mut multibuffer = MultiBuffer::new(capability);
14035 while let Some(location) = locations.next() {
14036 let buffer = location.buffer.read(cx);
14037 let mut ranges_for_buffer = Vec::new();
14038 let range = location.range.to_point(buffer);
14039 ranges_for_buffer.push(range.clone());
14040
14041 while let Some(next_location) = locations.peek() {
14042 if next_location.buffer == location.buffer {
14043 ranges_for_buffer.push(next_location.range.to_point(buffer));
14044 locations.next();
14045 } else {
14046 break;
14047 }
14048 }
14049
14050 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14051 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14052 PathKey::for_buffer(&location.buffer, cx),
14053 location.buffer.clone(),
14054 ranges_for_buffer,
14055 DEFAULT_MULTIBUFFER_CONTEXT,
14056 cx,
14057 );
14058 ranges.extend(new_ranges)
14059 }
14060
14061 multibuffer.with_title(title)
14062 });
14063
14064 let editor = cx.new(|cx| {
14065 Editor::for_multibuffer(
14066 excerpt_buffer,
14067 Some(workspace.project().clone()),
14068 window,
14069 cx,
14070 )
14071 });
14072 editor.update(cx, |editor, cx| {
14073 match multibuffer_selection_mode {
14074 MultibufferSelectionMode::First => {
14075 if let Some(first_range) = ranges.first() {
14076 editor.change_selections(None, window, cx, |selections| {
14077 selections.clear_disjoint();
14078 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14079 });
14080 }
14081 editor.highlight_background::<Self>(
14082 &ranges,
14083 |theme| theme.editor_highlighted_line_background,
14084 cx,
14085 );
14086 }
14087 MultibufferSelectionMode::All => {
14088 editor.change_selections(None, window, cx, |selections| {
14089 selections.clear_disjoint();
14090 selections.select_anchor_ranges(ranges);
14091 });
14092 }
14093 }
14094 editor.register_buffers_with_language_servers(cx);
14095 });
14096
14097 let item = Box::new(editor);
14098 let item_id = item.item_id();
14099
14100 if split {
14101 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14102 } else {
14103 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14104 let (preview_item_id, preview_item_idx) =
14105 workspace.active_pane().update(cx, |pane, _| {
14106 (pane.preview_item_id(), pane.preview_item_idx())
14107 });
14108
14109 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14110
14111 if let Some(preview_item_id) = preview_item_id {
14112 workspace.active_pane().update(cx, |pane, cx| {
14113 pane.remove_item(preview_item_id, false, false, window, cx);
14114 });
14115 }
14116 } else {
14117 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14118 }
14119 }
14120 workspace.active_pane().update(cx, |pane, cx| {
14121 pane.set_preview_item_id(Some(item_id), cx);
14122 });
14123 }
14124
14125 pub fn rename(
14126 &mut self,
14127 _: &Rename,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) -> Option<Task<Result<()>>> {
14131 use language::ToOffset as _;
14132
14133 let provider = self.semantics_provider.clone()?;
14134 let selection = self.selections.newest_anchor().clone();
14135 let (cursor_buffer, cursor_buffer_position) = self
14136 .buffer
14137 .read(cx)
14138 .text_anchor_for_position(selection.head(), cx)?;
14139 let (tail_buffer, cursor_buffer_position_end) = self
14140 .buffer
14141 .read(cx)
14142 .text_anchor_for_position(selection.tail(), cx)?;
14143 if tail_buffer != cursor_buffer {
14144 return None;
14145 }
14146
14147 let snapshot = cursor_buffer.read(cx).snapshot();
14148 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14149 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14150 let prepare_rename = provider
14151 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14152 .unwrap_or_else(|| Task::ready(Ok(None)));
14153 drop(snapshot);
14154
14155 Some(cx.spawn_in(window, async move |this, cx| {
14156 let rename_range = if let Some(range) = prepare_rename.await? {
14157 Some(range)
14158 } else {
14159 this.update(cx, |this, cx| {
14160 let buffer = this.buffer.read(cx).snapshot(cx);
14161 let mut buffer_highlights = this
14162 .document_highlights_for_position(selection.head(), &buffer)
14163 .filter(|highlight| {
14164 highlight.start.excerpt_id == selection.head().excerpt_id
14165 && highlight.end.excerpt_id == selection.head().excerpt_id
14166 });
14167 buffer_highlights
14168 .next()
14169 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14170 })?
14171 };
14172 if let Some(rename_range) = rename_range {
14173 this.update_in(cx, |this, window, cx| {
14174 let snapshot = cursor_buffer.read(cx).snapshot();
14175 let rename_buffer_range = rename_range.to_offset(&snapshot);
14176 let cursor_offset_in_rename_range =
14177 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14178 let cursor_offset_in_rename_range_end =
14179 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14180
14181 this.take_rename(false, window, cx);
14182 let buffer = this.buffer.read(cx).read(cx);
14183 let cursor_offset = selection.head().to_offset(&buffer);
14184 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14185 let rename_end = rename_start + rename_buffer_range.len();
14186 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14187 let mut old_highlight_id = None;
14188 let old_name: Arc<str> = buffer
14189 .chunks(rename_start..rename_end, true)
14190 .map(|chunk| {
14191 if old_highlight_id.is_none() {
14192 old_highlight_id = chunk.syntax_highlight_id;
14193 }
14194 chunk.text
14195 })
14196 .collect::<String>()
14197 .into();
14198
14199 drop(buffer);
14200
14201 // Position the selection in the rename editor so that it matches the current selection.
14202 this.show_local_selections = false;
14203 let rename_editor = cx.new(|cx| {
14204 let mut editor = Editor::single_line(window, cx);
14205 editor.buffer.update(cx, |buffer, cx| {
14206 buffer.edit([(0..0, old_name.clone())], None, cx)
14207 });
14208 let rename_selection_range = match cursor_offset_in_rename_range
14209 .cmp(&cursor_offset_in_rename_range_end)
14210 {
14211 Ordering::Equal => {
14212 editor.select_all(&SelectAll, window, cx);
14213 return editor;
14214 }
14215 Ordering::Less => {
14216 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14217 }
14218 Ordering::Greater => {
14219 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14220 }
14221 };
14222 if rename_selection_range.end > old_name.len() {
14223 editor.select_all(&SelectAll, window, cx);
14224 } else {
14225 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14226 s.select_ranges([rename_selection_range]);
14227 });
14228 }
14229 editor
14230 });
14231 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14232 if e == &EditorEvent::Focused {
14233 cx.emit(EditorEvent::FocusedIn)
14234 }
14235 })
14236 .detach();
14237
14238 let write_highlights =
14239 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14240 let read_highlights =
14241 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14242 let ranges = write_highlights
14243 .iter()
14244 .flat_map(|(_, ranges)| ranges.iter())
14245 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14246 .cloned()
14247 .collect();
14248
14249 this.highlight_text::<Rename>(
14250 ranges,
14251 HighlightStyle {
14252 fade_out: Some(0.6),
14253 ..Default::default()
14254 },
14255 cx,
14256 );
14257 let rename_focus_handle = rename_editor.focus_handle(cx);
14258 window.focus(&rename_focus_handle);
14259 let block_id = this.insert_blocks(
14260 [BlockProperties {
14261 style: BlockStyle::Flex,
14262 placement: BlockPlacement::Below(range.start),
14263 height: Some(1),
14264 render: Arc::new({
14265 let rename_editor = rename_editor.clone();
14266 move |cx: &mut BlockContext| {
14267 let mut text_style = cx.editor_style.text.clone();
14268 if let Some(highlight_style) = old_highlight_id
14269 .and_then(|h| h.style(&cx.editor_style.syntax))
14270 {
14271 text_style = text_style.highlight(highlight_style);
14272 }
14273 div()
14274 .block_mouse_down()
14275 .pl(cx.anchor_x)
14276 .child(EditorElement::new(
14277 &rename_editor,
14278 EditorStyle {
14279 background: cx.theme().system().transparent,
14280 local_player: cx.editor_style.local_player,
14281 text: text_style,
14282 scrollbar_width: cx.editor_style.scrollbar_width,
14283 syntax: cx.editor_style.syntax.clone(),
14284 status: cx.editor_style.status.clone(),
14285 inlay_hints_style: HighlightStyle {
14286 font_weight: Some(FontWeight::BOLD),
14287 ..make_inlay_hints_style(cx.app)
14288 },
14289 inline_completion_styles: make_suggestion_styles(
14290 cx.app,
14291 ),
14292 ..EditorStyle::default()
14293 },
14294 ))
14295 .into_any_element()
14296 }
14297 }),
14298 priority: 0,
14299 }],
14300 Some(Autoscroll::fit()),
14301 cx,
14302 )[0];
14303 this.pending_rename = Some(RenameState {
14304 range,
14305 old_name,
14306 editor: rename_editor,
14307 block_id,
14308 });
14309 })?;
14310 }
14311
14312 Ok(())
14313 }))
14314 }
14315
14316 pub fn confirm_rename(
14317 &mut self,
14318 _: &ConfirmRename,
14319 window: &mut Window,
14320 cx: &mut Context<Self>,
14321 ) -> Option<Task<Result<()>>> {
14322 let rename = self.take_rename(false, window, cx)?;
14323 let workspace = self.workspace()?.downgrade();
14324 let (buffer, start) = self
14325 .buffer
14326 .read(cx)
14327 .text_anchor_for_position(rename.range.start, cx)?;
14328 let (end_buffer, _) = self
14329 .buffer
14330 .read(cx)
14331 .text_anchor_for_position(rename.range.end, cx)?;
14332 if buffer != end_buffer {
14333 return None;
14334 }
14335
14336 let old_name = rename.old_name;
14337 let new_name = rename.editor.read(cx).text(cx);
14338
14339 let rename = self.semantics_provider.as_ref()?.perform_rename(
14340 &buffer,
14341 start,
14342 new_name.clone(),
14343 cx,
14344 )?;
14345
14346 Some(cx.spawn_in(window, async move |editor, cx| {
14347 let project_transaction = rename.await?;
14348 Self::open_project_transaction(
14349 &editor,
14350 workspace,
14351 project_transaction,
14352 format!("Rename: {} → {}", old_name, new_name),
14353 cx,
14354 )
14355 .await?;
14356
14357 editor.update(cx, |editor, cx| {
14358 editor.refresh_document_highlights(cx);
14359 })?;
14360 Ok(())
14361 }))
14362 }
14363
14364 fn take_rename(
14365 &mut self,
14366 moving_cursor: bool,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) -> Option<RenameState> {
14370 let rename = self.pending_rename.take()?;
14371 if rename.editor.focus_handle(cx).is_focused(window) {
14372 window.focus(&self.focus_handle);
14373 }
14374
14375 self.remove_blocks(
14376 [rename.block_id].into_iter().collect(),
14377 Some(Autoscroll::fit()),
14378 cx,
14379 );
14380 self.clear_highlights::<Rename>(cx);
14381 self.show_local_selections = true;
14382
14383 if moving_cursor {
14384 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14385 editor.selections.newest::<usize>(cx).head()
14386 });
14387
14388 // Update the selection to match the position of the selection inside
14389 // the rename editor.
14390 let snapshot = self.buffer.read(cx).read(cx);
14391 let rename_range = rename.range.to_offset(&snapshot);
14392 let cursor_in_editor = snapshot
14393 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14394 .min(rename_range.end);
14395 drop(snapshot);
14396
14397 self.change_selections(None, window, cx, |s| {
14398 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14399 });
14400 } else {
14401 self.refresh_document_highlights(cx);
14402 }
14403
14404 Some(rename)
14405 }
14406
14407 pub fn pending_rename(&self) -> Option<&RenameState> {
14408 self.pending_rename.as_ref()
14409 }
14410
14411 fn format(
14412 &mut self,
14413 _: &Format,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) -> Option<Task<Result<()>>> {
14417 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14418
14419 let project = match &self.project {
14420 Some(project) => project.clone(),
14421 None => return None,
14422 };
14423
14424 Some(self.perform_format(
14425 project,
14426 FormatTrigger::Manual,
14427 FormatTarget::Buffers,
14428 window,
14429 cx,
14430 ))
14431 }
14432
14433 fn format_selections(
14434 &mut self,
14435 _: &FormatSelections,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) -> Option<Task<Result<()>>> {
14439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14440
14441 let project = match &self.project {
14442 Some(project) => project.clone(),
14443 None => return None,
14444 };
14445
14446 let ranges = self
14447 .selections
14448 .all_adjusted(cx)
14449 .into_iter()
14450 .map(|selection| selection.range())
14451 .collect_vec();
14452
14453 Some(self.perform_format(
14454 project,
14455 FormatTrigger::Manual,
14456 FormatTarget::Ranges(ranges),
14457 window,
14458 cx,
14459 ))
14460 }
14461
14462 fn perform_format(
14463 &mut self,
14464 project: Entity<Project>,
14465 trigger: FormatTrigger,
14466 target: FormatTarget,
14467 window: &mut Window,
14468 cx: &mut Context<Self>,
14469 ) -> Task<Result<()>> {
14470 let buffer = self.buffer.clone();
14471 let (buffers, target) = match target {
14472 FormatTarget::Buffers => {
14473 let mut buffers = buffer.read(cx).all_buffers();
14474 if trigger == FormatTrigger::Save {
14475 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14476 }
14477 (buffers, LspFormatTarget::Buffers)
14478 }
14479 FormatTarget::Ranges(selection_ranges) => {
14480 let multi_buffer = buffer.read(cx);
14481 let snapshot = multi_buffer.read(cx);
14482 let mut buffers = HashSet::default();
14483 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14484 BTreeMap::new();
14485 for selection_range in selection_ranges {
14486 for (buffer, buffer_range, _) in
14487 snapshot.range_to_buffer_ranges(selection_range)
14488 {
14489 let buffer_id = buffer.remote_id();
14490 let start = buffer.anchor_before(buffer_range.start);
14491 let end = buffer.anchor_after(buffer_range.end);
14492 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14493 buffer_id_to_ranges
14494 .entry(buffer_id)
14495 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14496 .or_insert_with(|| vec![start..end]);
14497 }
14498 }
14499 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14500 }
14501 };
14502
14503 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14504 let selections_prev = transaction_id_prev
14505 .and_then(|transaction_id_prev| {
14506 // default to selections as they were after the last edit, if we have them,
14507 // instead of how they are now.
14508 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14509 // will take you back to where you made the last edit, instead of staying where you scrolled
14510 self.selection_history
14511 .transaction(transaction_id_prev)
14512 .map(|t| t.0.clone())
14513 })
14514 .unwrap_or_else(|| {
14515 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14516 self.selections.disjoint_anchors()
14517 });
14518
14519 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14520 let format = project.update(cx, |project, cx| {
14521 project.format(buffers, target, true, trigger, cx)
14522 });
14523
14524 cx.spawn_in(window, async move |editor, cx| {
14525 let transaction = futures::select_biased! {
14526 transaction = format.log_err().fuse() => transaction,
14527 () = timeout => {
14528 log::warn!("timed out waiting for formatting");
14529 None
14530 }
14531 };
14532
14533 buffer
14534 .update(cx, |buffer, cx| {
14535 if let Some(transaction) = transaction {
14536 if !buffer.is_singleton() {
14537 buffer.push_transaction(&transaction.0, cx);
14538 }
14539 }
14540 cx.notify();
14541 })
14542 .ok();
14543
14544 if let Some(transaction_id_now) =
14545 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14546 {
14547 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14548 if has_new_transaction {
14549 _ = editor.update(cx, |editor, _| {
14550 editor
14551 .selection_history
14552 .insert_transaction(transaction_id_now, selections_prev);
14553 });
14554 }
14555 }
14556
14557 Ok(())
14558 })
14559 }
14560
14561 fn organize_imports(
14562 &mut self,
14563 _: &OrganizeImports,
14564 window: &mut Window,
14565 cx: &mut Context<Self>,
14566 ) -> Option<Task<Result<()>>> {
14567 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14568 let project = match &self.project {
14569 Some(project) => project.clone(),
14570 None => return None,
14571 };
14572 Some(self.perform_code_action_kind(
14573 project,
14574 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14575 window,
14576 cx,
14577 ))
14578 }
14579
14580 fn perform_code_action_kind(
14581 &mut self,
14582 project: Entity<Project>,
14583 kind: CodeActionKind,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) -> Task<Result<()>> {
14587 let buffer = self.buffer.clone();
14588 let buffers = buffer.read(cx).all_buffers();
14589 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14590 let apply_action = project.update(cx, |project, cx| {
14591 project.apply_code_action_kind(buffers, kind, true, cx)
14592 });
14593 cx.spawn_in(window, async move |_, cx| {
14594 let transaction = futures::select_biased! {
14595 () = timeout => {
14596 log::warn!("timed out waiting for executing code action");
14597 None
14598 }
14599 transaction = apply_action.log_err().fuse() => transaction,
14600 };
14601 buffer
14602 .update(cx, |buffer, cx| {
14603 // check if we need this
14604 if let Some(transaction) = transaction {
14605 if !buffer.is_singleton() {
14606 buffer.push_transaction(&transaction.0, cx);
14607 }
14608 }
14609 cx.notify();
14610 })
14611 .ok();
14612 Ok(())
14613 })
14614 }
14615
14616 fn restart_language_server(
14617 &mut self,
14618 _: &RestartLanguageServer,
14619 _: &mut Window,
14620 cx: &mut Context<Self>,
14621 ) {
14622 if let Some(project) = self.project.clone() {
14623 self.buffer.update(cx, |multi_buffer, cx| {
14624 project.update(cx, |project, cx| {
14625 project.restart_language_servers_for_buffers(
14626 multi_buffer.all_buffers().into_iter().collect(),
14627 cx,
14628 );
14629 });
14630 })
14631 }
14632 }
14633
14634 fn stop_language_server(
14635 &mut self,
14636 _: &StopLanguageServer,
14637 _: &mut Window,
14638 cx: &mut Context<Self>,
14639 ) {
14640 if let Some(project) = self.project.clone() {
14641 self.buffer.update(cx, |multi_buffer, cx| {
14642 project.update(cx, |project, cx| {
14643 project.stop_language_servers_for_buffers(
14644 multi_buffer.all_buffers().into_iter().collect(),
14645 cx,
14646 );
14647 cx.emit(project::Event::RefreshInlayHints);
14648 });
14649 });
14650 }
14651 }
14652
14653 fn cancel_language_server_work(
14654 workspace: &mut Workspace,
14655 _: &actions::CancelLanguageServerWork,
14656 _: &mut Window,
14657 cx: &mut Context<Workspace>,
14658 ) {
14659 let project = workspace.project();
14660 let buffers = workspace
14661 .active_item(cx)
14662 .and_then(|item| item.act_as::<Editor>(cx))
14663 .map_or(HashSet::default(), |editor| {
14664 editor.read(cx).buffer.read(cx).all_buffers()
14665 });
14666 project.update(cx, |project, cx| {
14667 project.cancel_language_server_work_for_buffers(buffers, cx);
14668 });
14669 }
14670
14671 fn show_character_palette(
14672 &mut self,
14673 _: &ShowCharacterPalette,
14674 window: &mut Window,
14675 _: &mut Context<Self>,
14676 ) {
14677 window.show_character_palette();
14678 }
14679
14680 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14681 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14682 let buffer = self.buffer.read(cx).snapshot(cx);
14683 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14684 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14685 let is_valid = buffer
14686 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14687 .any(|entry| {
14688 entry.diagnostic.is_primary
14689 && !entry.range.is_empty()
14690 && entry.range.start == primary_range_start
14691 && entry.diagnostic.message == active_diagnostics.active_message
14692 });
14693
14694 if !is_valid {
14695 self.dismiss_diagnostics(cx);
14696 }
14697 }
14698 }
14699
14700 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14701 match &self.active_diagnostics {
14702 ActiveDiagnostic::Group(group) => Some(group),
14703 _ => None,
14704 }
14705 }
14706
14707 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14708 self.dismiss_diagnostics(cx);
14709 self.active_diagnostics = ActiveDiagnostic::All;
14710 }
14711
14712 fn activate_diagnostics(
14713 &mut self,
14714 buffer_id: BufferId,
14715 diagnostic: DiagnosticEntry<usize>,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) {
14719 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14720 return;
14721 }
14722 self.dismiss_diagnostics(cx);
14723 let snapshot = self.snapshot(window, cx);
14724 let Some(diagnostic_renderer) = cx
14725 .try_global::<GlobalDiagnosticRenderer>()
14726 .map(|g| g.0.clone())
14727 else {
14728 return;
14729 };
14730 let buffer = self.buffer.read(cx).snapshot(cx);
14731
14732 let diagnostic_group = buffer
14733 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14734 .collect::<Vec<_>>();
14735
14736 let blocks = diagnostic_renderer.render_group(
14737 diagnostic_group,
14738 buffer_id,
14739 snapshot,
14740 cx.weak_entity(),
14741 cx,
14742 );
14743
14744 let blocks = self.display_map.update(cx, |display_map, cx| {
14745 display_map.insert_blocks(blocks, cx).into_iter().collect()
14746 });
14747 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14748 active_range: buffer.anchor_before(diagnostic.range.start)
14749 ..buffer.anchor_after(diagnostic.range.end),
14750 active_message: diagnostic.diagnostic.message.clone(),
14751 group_id: diagnostic.diagnostic.group_id,
14752 blocks,
14753 });
14754 cx.notify();
14755 }
14756
14757 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14758 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14759 return;
14760 };
14761
14762 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14763 if let ActiveDiagnostic::Group(group) = prev {
14764 self.display_map.update(cx, |display_map, cx| {
14765 display_map.remove_blocks(group.blocks, cx);
14766 });
14767 cx.notify();
14768 }
14769 }
14770
14771 /// Disable inline diagnostics rendering for this editor.
14772 pub fn disable_inline_diagnostics(&mut self) {
14773 self.inline_diagnostics_enabled = false;
14774 self.inline_diagnostics_update = Task::ready(());
14775 self.inline_diagnostics.clear();
14776 }
14777
14778 pub fn inline_diagnostics_enabled(&self) -> bool {
14779 self.inline_diagnostics_enabled
14780 }
14781
14782 pub fn show_inline_diagnostics(&self) -> bool {
14783 self.show_inline_diagnostics
14784 }
14785
14786 pub fn toggle_inline_diagnostics(
14787 &mut self,
14788 _: &ToggleInlineDiagnostics,
14789 window: &mut Window,
14790 cx: &mut Context<Editor>,
14791 ) {
14792 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14793 self.refresh_inline_diagnostics(false, window, cx);
14794 }
14795
14796 fn refresh_inline_diagnostics(
14797 &mut self,
14798 debounce: bool,
14799 window: &mut Window,
14800 cx: &mut Context<Self>,
14801 ) {
14802 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14803 self.inline_diagnostics_update = Task::ready(());
14804 self.inline_diagnostics.clear();
14805 return;
14806 }
14807
14808 let debounce_ms = ProjectSettings::get_global(cx)
14809 .diagnostics
14810 .inline
14811 .update_debounce_ms;
14812 let debounce = if debounce && debounce_ms > 0 {
14813 Some(Duration::from_millis(debounce_ms))
14814 } else {
14815 None
14816 };
14817 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14818 let editor = editor.upgrade().unwrap();
14819
14820 if let Some(debounce) = debounce {
14821 cx.background_executor().timer(debounce).await;
14822 }
14823 let Some(snapshot) = editor
14824 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14825 .ok()
14826 else {
14827 return;
14828 };
14829
14830 let new_inline_diagnostics = cx
14831 .background_spawn(async move {
14832 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14833 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14834 let message = diagnostic_entry
14835 .diagnostic
14836 .message
14837 .split_once('\n')
14838 .map(|(line, _)| line)
14839 .map(SharedString::new)
14840 .unwrap_or_else(|| {
14841 SharedString::from(diagnostic_entry.diagnostic.message)
14842 });
14843 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14844 let (Ok(i) | Err(i)) = inline_diagnostics
14845 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14846 inline_diagnostics.insert(
14847 i,
14848 (
14849 start_anchor,
14850 InlineDiagnostic {
14851 message,
14852 group_id: diagnostic_entry.diagnostic.group_id,
14853 start: diagnostic_entry.range.start.to_point(&snapshot),
14854 is_primary: diagnostic_entry.diagnostic.is_primary,
14855 severity: diagnostic_entry.diagnostic.severity,
14856 },
14857 ),
14858 );
14859 }
14860 inline_diagnostics
14861 })
14862 .await;
14863
14864 editor
14865 .update(cx, |editor, cx| {
14866 editor.inline_diagnostics = new_inline_diagnostics;
14867 cx.notify();
14868 })
14869 .ok();
14870 });
14871 }
14872
14873 pub fn set_selections_from_remote(
14874 &mut self,
14875 selections: Vec<Selection<Anchor>>,
14876 pending_selection: Option<Selection<Anchor>>,
14877 window: &mut Window,
14878 cx: &mut Context<Self>,
14879 ) {
14880 let old_cursor_position = self.selections.newest_anchor().head();
14881 self.selections.change_with(cx, |s| {
14882 s.select_anchors(selections);
14883 if let Some(pending_selection) = pending_selection {
14884 s.set_pending(pending_selection, SelectMode::Character);
14885 } else {
14886 s.clear_pending();
14887 }
14888 });
14889 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14890 }
14891
14892 fn push_to_selection_history(&mut self) {
14893 self.selection_history.push(SelectionHistoryEntry {
14894 selections: self.selections.disjoint_anchors(),
14895 select_next_state: self.select_next_state.clone(),
14896 select_prev_state: self.select_prev_state.clone(),
14897 add_selections_state: self.add_selections_state.clone(),
14898 });
14899 }
14900
14901 pub fn transact(
14902 &mut self,
14903 window: &mut Window,
14904 cx: &mut Context<Self>,
14905 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14906 ) -> Option<TransactionId> {
14907 self.start_transaction_at(Instant::now(), window, cx);
14908 update(self, window, cx);
14909 self.end_transaction_at(Instant::now(), cx)
14910 }
14911
14912 pub fn start_transaction_at(
14913 &mut self,
14914 now: Instant,
14915 window: &mut Window,
14916 cx: &mut Context<Self>,
14917 ) {
14918 self.end_selection(window, cx);
14919 if let Some(tx_id) = self
14920 .buffer
14921 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14922 {
14923 self.selection_history
14924 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14925 cx.emit(EditorEvent::TransactionBegun {
14926 transaction_id: tx_id,
14927 })
14928 }
14929 }
14930
14931 pub fn end_transaction_at(
14932 &mut self,
14933 now: Instant,
14934 cx: &mut Context<Self>,
14935 ) -> Option<TransactionId> {
14936 if let Some(transaction_id) = self
14937 .buffer
14938 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14939 {
14940 if let Some((_, end_selections)) =
14941 self.selection_history.transaction_mut(transaction_id)
14942 {
14943 *end_selections = Some(self.selections.disjoint_anchors());
14944 } else {
14945 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14946 }
14947
14948 cx.emit(EditorEvent::Edited { transaction_id });
14949 Some(transaction_id)
14950 } else {
14951 None
14952 }
14953 }
14954
14955 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14956 if self.selection_mark_mode {
14957 self.change_selections(None, window, cx, |s| {
14958 s.move_with(|_, sel| {
14959 sel.collapse_to(sel.head(), SelectionGoal::None);
14960 });
14961 })
14962 }
14963 self.selection_mark_mode = true;
14964 cx.notify();
14965 }
14966
14967 pub fn swap_selection_ends(
14968 &mut self,
14969 _: &actions::SwapSelectionEnds,
14970 window: &mut Window,
14971 cx: &mut Context<Self>,
14972 ) {
14973 self.change_selections(None, window, cx, |s| {
14974 s.move_with(|_, sel| {
14975 if sel.start != sel.end {
14976 sel.reversed = !sel.reversed
14977 }
14978 });
14979 });
14980 self.request_autoscroll(Autoscroll::newest(), cx);
14981 cx.notify();
14982 }
14983
14984 pub fn toggle_fold(
14985 &mut self,
14986 _: &actions::ToggleFold,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) {
14990 if self.is_singleton(cx) {
14991 let selection = self.selections.newest::<Point>(cx);
14992
14993 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14994 let range = if selection.is_empty() {
14995 let point = selection.head().to_display_point(&display_map);
14996 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14997 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14998 .to_point(&display_map);
14999 start..end
15000 } else {
15001 selection.range()
15002 };
15003 if display_map.folds_in_range(range).next().is_some() {
15004 self.unfold_lines(&Default::default(), window, cx)
15005 } else {
15006 self.fold(&Default::default(), window, cx)
15007 }
15008 } else {
15009 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15010 let buffer_ids: HashSet<_> = self
15011 .selections
15012 .disjoint_anchor_ranges()
15013 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15014 .collect();
15015
15016 let should_unfold = buffer_ids
15017 .iter()
15018 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15019
15020 for buffer_id in buffer_ids {
15021 if should_unfold {
15022 self.unfold_buffer(buffer_id, cx);
15023 } else {
15024 self.fold_buffer(buffer_id, cx);
15025 }
15026 }
15027 }
15028 }
15029
15030 pub fn toggle_fold_recursive(
15031 &mut self,
15032 _: &actions::ToggleFoldRecursive,
15033 window: &mut Window,
15034 cx: &mut Context<Self>,
15035 ) {
15036 let selection = self.selections.newest::<Point>(cx);
15037
15038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15039 let range = if selection.is_empty() {
15040 let point = selection.head().to_display_point(&display_map);
15041 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15042 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15043 .to_point(&display_map);
15044 start..end
15045 } else {
15046 selection.range()
15047 };
15048 if display_map.folds_in_range(range).next().is_some() {
15049 self.unfold_recursive(&Default::default(), window, cx)
15050 } else {
15051 self.fold_recursive(&Default::default(), window, cx)
15052 }
15053 }
15054
15055 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15056 if self.is_singleton(cx) {
15057 let mut to_fold = Vec::new();
15058 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15059 let selections = self.selections.all_adjusted(cx);
15060
15061 for selection in selections {
15062 let range = selection.range().sorted();
15063 let buffer_start_row = range.start.row;
15064
15065 if range.start.row != range.end.row {
15066 let mut found = false;
15067 let mut row = range.start.row;
15068 while row <= range.end.row {
15069 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15070 {
15071 found = true;
15072 row = crease.range().end.row + 1;
15073 to_fold.push(crease);
15074 } else {
15075 row += 1
15076 }
15077 }
15078 if found {
15079 continue;
15080 }
15081 }
15082
15083 for row in (0..=range.start.row).rev() {
15084 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15085 if crease.range().end.row >= buffer_start_row {
15086 to_fold.push(crease);
15087 if row <= range.start.row {
15088 break;
15089 }
15090 }
15091 }
15092 }
15093 }
15094
15095 self.fold_creases(to_fold, true, window, cx);
15096 } else {
15097 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15098 let buffer_ids = self
15099 .selections
15100 .disjoint_anchor_ranges()
15101 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15102 .collect::<HashSet<_>>();
15103 for buffer_id in buffer_ids {
15104 self.fold_buffer(buffer_id, cx);
15105 }
15106 }
15107 }
15108
15109 fn fold_at_level(
15110 &mut self,
15111 fold_at: &FoldAtLevel,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) {
15115 if !self.buffer.read(cx).is_singleton() {
15116 return;
15117 }
15118
15119 let fold_at_level = fold_at.0;
15120 let snapshot = self.buffer.read(cx).snapshot(cx);
15121 let mut to_fold = Vec::new();
15122 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15123
15124 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15125 while start_row < end_row {
15126 match self
15127 .snapshot(window, cx)
15128 .crease_for_buffer_row(MultiBufferRow(start_row))
15129 {
15130 Some(crease) => {
15131 let nested_start_row = crease.range().start.row + 1;
15132 let nested_end_row = crease.range().end.row;
15133
15134 if current_level < fold_at_level {
15135 stack.push((nested_start_row, nested_end_row, current_level + 1));
15136 } else if current_level == fold_at_level {
15137 to_fold.push(crease);
15138 }
15139
15140 start_row = nested_end_row + 1;
15141 }
15142 None => start_row += 1,
15143 }
15144 }
15145 }
15146
15147 self.fold_creases(to_fold, true, window, cx);
15148 }
15149
15150 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15151 if self.buffer.read(cx).is_singleton() {
15152 let mut fold_ranges = Vec::new();
15153 let snapshot = self.buffer.read(cx).snapshot(cx);
15154
15155 for row in 0..snapshot.max_row().0 {
15156 if let Some(foldable_range) = self
15157 .snapshot(window, cx)
15158 .crease_for_buffer_row(MultiBufferRow(row))
15159 {
15160 fold_ranges.push(foldable_range);
15161 }
15162 }
15163
15164 self.fold_creases(fold_ranges, true, window, cx);
15165 } else {
15166 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15167 editor
15168 .update_in(cx, |editor, _, cx| {
15169 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15170 editor.fold_buffer(buffer_id, cx);
15171 }
15172 })
15173 .ok();
15174 });
15175 }
15176 }
15177
15178 pub fn fold_function_bodies(
15179 &mut self,
15180 _: &actions::FoldFunctionBodies,
15181 window: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 let snapshot = self.buffer.read(cx).snapshot(cx);
15185
15186 let ranges = snapshot
15187 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15188 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15189 .collect::<Vec<_>>();
15190
15191 let creases = ranges
15192 .into_iter()
15193 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15194 .collect();
15195
15196 self.fold_creases(creases, true, window, cx);
15197 }
15198
15199 pub fn fold_recursive(
15200 &mut self,
15201 _: &actions::FoldRecursive,
15202 window: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 let mut to_fold = Vec::new();
15206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15207 let selections = self.selections.all_adjusted(cx);
15208
15209 for selection in selections {
15210 let range = selection.range().sorted();
15211 let buffer_start_row = range.start.row;
15212
15213 if range.start.row != range.end.row {
15214 let mut found = false;
15215 for row in range.start.row..=range.end.row {
15216 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15217 found = true;
15218 to_fold.push(crease);
15219 }
15220 }
15221 if found {
15222 continue;
15223 }
15224 }
15225
15226 for row in (0..=range.start.row).rev() {
15227 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15228 if crease.range().end.row >= buffer_start_row {
15229 to_fold.push(crease);
15230 } else {
15231 break;
15232 }
15233 }
15234 }
15235 }
15236
15237 self.fold_creases(to_fold, true, window, cx);
15238 }
15239
15240 pub fn fold_at(
15241 &mut self,
15242 buffer_row: MultiBufferRow,
15243 window: &mut Window,
15244 cx: &mut Context<Self>,
15245 ) {
15246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15247
15248 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15249 let autoscroll = self
15250 .selections
15251 .all::<Point>(cx)
15252 .iter()
15253 .any(|selection| crease.range().overlaps(&selection.range()));
15254
15255 self.fold_creases(vec![crease], autoscroll, window, cx);
15256 }
15257 }
15258
15259 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15260 if self.is_singleton(cx) {
15261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15262 let buffer = &display_map.buffer_snapshot;
15263 let selections = self.selections.all::<Point>(cx);
15264 let ranges = selections
15265 .iter()
15266 .map(|s| {
15267 let range = s.display_range(&display_map).sorted();
15268 let mut start = range.start.to_point(&display_map);
15269 let mut end = range.end.to_point(&display_map);
15270 start.column = 0;
15271 end.column = buffer.line_len(MultiBufferRow(end.row));
15272 start..end
15273 })
15274 .collect::<Vec<_>>();
15275
15276 self.unfold_ranges(&ranges, true, true, cx);
15277 } else {
15278 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15279 let buffer_ids = self
15280 .selections
15281 .disjoint_anchor_ranges()
15282 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15283 .collect::<HashSet<_>>();
15284 for buffer_id in buffer_ids {
15285 self.unfold_buffer(buffer_id, cx);
15286 }
15287 }
15288 }
15289
15290 pub fn unfold_recursive(
15291 &mut self,
15292 _: &UnfoldRecursive,
15293 _window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15297 let selections = self.selections.all::<Point>(cx);
15298 let ranges = selections
15299 .iter()
15300 .map(|s| {
15301 let mut range = s.display_range(&display_map).sorted();
15302 *range.start.column_mut() = 0;
15303 *range.end.column_mut() = display_map.line_len(range.end.row());
15304 let start = range.start.to_point(&display_map);
15305 let end = range.end.to_point(&display_map);
15306 start..end
15307 })
15308 .collect::<Vec<_>>();
15309
15310 self.unfold_ranges(&ranges, true, true, cx);
15311 }
15312
15313 pub fn unfold_at(
15314 &mut self,
15315 buffer_row: MultiBufferRow,
15316 _window: &mut Window,
15317 cx: &mut Context<Self>,
15318 ) {
15319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15320
15321 let intersection_range = Point::new(buffer_row.0, 0)
15322 ..Point::new(
15323 buffer_row.0,
15324 display_map.buffer_snapshot.line_len(buffer_row),
15325 );
15326
15327 let autoscroll = self
15328 .selections
15329 .all::<Point>(cx)
15330 .iter()
15331 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15332
15333 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15334 }
15335
15336 pub fn unfold_all(
15337 &mut self,
15338 _: &actions::UnfoldAll,
15339 _window: &mut Window,
15340 cx: &mut Context<Self>,
15341 ) {
15342 if self.buffer.read(cx).is_singleton() {
15343 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15344 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15345 } else {
15346 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15347 editor
15348 .update(cx, |editor, cx| {
15349 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15350 editor.unfold_buffer(buffer_id, cx);
15351 }
15352 })
15353 .ok();
15354 });
15355 }
15356 }
15357
15358 pub fn fold_selected_ranges(
15359 &mut self,
15360 _: &FoldSelectedRanges,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 let selections = self.selections.all_adjusted(cx);
15365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15366 let ranges = selections
15367 .into_iter()
15368 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15369 .collect::<Vec<_>>();
15370 self.fold_creases(ranges, true, window, cx);
15371 }
15372
15373 pub fn fold_ranges<T: ToOffset + Clone>(
15374 &mut self,
15375 ranges: Vec<Range<T>>,
15376 auto_scroll: bool,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15381 let ranges = ranges
15382 .into_iter()
15383 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15384 .collect::<Vec<_>>();
15385 self.fold_creases(ranges, auto_scroll, window, cx);
15386 }
15387
15388 pub fn fold_creases<T: ToOffset + Clone>(
15389 &mut self,
15390 creases: Vec<Crease<T>>,
15391 auto_scroll: bool,
15392 _window: &mut Window,
15393 cx: &mut Context<Self>,
15394 ) {
15395 if creases.is_empty() {
15396 return;
15397 }
15398
15399 let mut buffers_affected = HashSet::default();
15400 let multi_buffer = self.buffer().read(cx);
15401 for crease in &creases {
15402 if let Some((_, buffer, _)) =
15403 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15404 {
15405 buffers_affected.insert(buffer.read(cx).remote_id());
15406 };
15407 }
15408
15409 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15410
15411 if auto_scroll {
15412 self.request_autoscroll(Autoscroll::fit(), cx);
15413 }
15414
15415 cx.notify();
15416
15417 self.scrollbar_marker_state.dirty = true;
15418 self.folds_did_change(cx);
15419 }
15420
15421 /// Removes any folds whose ranges intersect any of the given ranges.
15422 pub fn unfold_ranges<T: ToOffset + Clone>(
15423 &mut self,
15424 ranges: &[Range<T>],
15425 inclusive: bool,
15426 auto_scroll: bool,
15427 cx: &mut Context<Self>,
15428 ) {
15429 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15430 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15431 });
15432 self.folds_did_change(cx);
15433 }
15434
15435 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15436 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15437 return;
15438 }
15439 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15440 self.display_map.update(cx, |display_map, cx| {
15441 display_map.fold_buffers([buffer_id], cx)
15442 });
15443 cx.emit(EditorEvent::BufferFoldToggled {
15444 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15445 folded: true,
15446 });
15447 cx.notify();
15448 }
15449
15450 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15451 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15452 return;
15453 }
15454 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15455 self.display_map.update(cx, |display_map, cx| {
15456 display_map.unfold_buffers([buffer_id], cx);
15457 });
15458 cx.emit(EditorEvent::BufferFoldToggled {
15459 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15460 folded: false,
15461 });
15462 cx.notify();
15463 }
15464
15465 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15466 self.display_map.read(cx).is_buffer_folded(buffer)
15467 }
15468
15469 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15470 self.display_map.read(cx).folded_buffers()
15471 }
15472
15473 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15474 self.display_map.update(cx, |display_map, cx| {
15475 display_map.disable_header_for_buffer(buffer_id, cx);
15476 });
15477 cx.notify();
15478 }
15479
15480 /// Removes any folds with the given ranges.
15481 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15482 &mut self,
15483 ranges: &[Range<T>],
15484 type_id: TypeId,
15485 auto_scroll: bool,
15486 cx: &mut Context<Self>,
15487 ) {
15488 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15489 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15490 });
15491 self.folds_did_change(cx);
15492 }
15493
15494 fn remove_folds_with<T: ToOffset + Clone>(
15495 &mut self,
15496 ranges: &[Range<T>],
15497 auto_scroll: bool,
15498 cx: &mut Context<Self>,
15499 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15500 ) {
15501 if ranges.is_empty() {
15502 return;
15503 }
15504
15505 let mut buffers_affected = HashSet::default();
15506 let multi_buffer = self.buffer().read(cx);
15507 for range in ranges {
15508 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15509 buffers_affected.insert(buffer.read(cx).remote_id());
15510 };
15511 }
15512
15513 self.display_map.update(cx, update);
15514
15515 if auto_scroll {
15516 self.request_autoscroll(Autoscroll::fit(), cx);
15517 }
15518
15519 cx.notify();
15520 self.scrollbar_marker_state.dirty = true;
15521 self.active_indent_guides_state.dirty = true;
15522 }
15523
15524 pub fn update_fold_widths(
15525 &mut self,
15526 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15527 cx: &mut Context<Self>,
15528 ) -> bool {
15529 self.display_map
15530 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15531 }
15532
15533 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15534 self.display_map.read(cx).fold_placeholder.clone()
15535 }
15536
15537 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15538 self.buffer.update(cx, |buffer, cx| {
15539 buffer.set_all_diff_hunks_expanded(cx);
15540 });
15541 }
15542
15543 pub fn expand_all_diff_hunks(
15544 &mut self,
15545 _: &ExpandAllDiffHunks,
15546 _window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 self.buffer.update(cx, |buffer, cx| {
15550 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15551 });
15552 }
15553
15554 pub fn toggle_selected_diff_hunks(
15555 &mut self,
15556 _: &ToggleSelectedDiffHunks,
15557 _window: &mut Window,
15558 cx: &mut Context<Self>,
15559 ) {
15560 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15561 self.toggle_diff_hunks_in_ranges(ranges, cx);
15562 }
15563
15564 pub fn diff_hunks_in_ranges<'a>(
15565 &'a self,
15566 ranges: &'a [Range<Anchor>],
15567 buffer: &'a MultiBufferSnapshot,
15568 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15569 ranges.iter().flat_map(move |range| {
15570 let end_excerpt_id = range.end.excerpt_id;
15571 let range = range.to_point(buffer);
15572 let mut peek_end = range.end;
15573 if range.end.row < buffer.max_row().0 {
15574 peek_end = Point::new(range.end.row + 1, 0);
15575 }
15576 buffer
15577 .diff_hunks_in_range(range.start..peek_end)
15578 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15579 })
15580 }
15581
15582 pub fn has_stageable_diff_hunks_in_ranges(
15583 &self,
15584 ranges: &[Range<Anchor>],
15585 snapshot: &MultiBufferSnapshot,
15586 ) -> bool {
15587 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15588 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15589 }
15590
15591 pub fn toggle_staged_selected_diff_hunks(
15592 &mut self,
15593 _: &::git::ToggleStaged,
15594 _: &mut Window,
15595 cx: &mut Context<Self>,
15596 ) {
15597 let snapshot = self.buffer.read(cx).snapshot(cx);
15598 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15599 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15600 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15601 }
15602
15603 pub fn set_render_diff_hunk_controls(
15604 &mut self,
15605 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15606 cx: &mut Context<Self>,
15607 ) {
15608 self.render_diff_hunk_controls = render_diff_hunk_controls;
15609 cx.notify();
15610 }
15611
15612 pub fn stage_and_next(
15613 &mut self,
15614 _: &::git::StageAndNext,
15615 window: &mut Window,
15616 cx: &mut Context<Self>,
15617 ) {
15618 self.do_stage_or_unstage_and_next(true, window, cx);
15619 }
15620
15621 pub fn unstage_and_next(
15622 &mut self,
15623 _: &::git::UnstageAndNext,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) {
15627 self.do_stage_or_unstage_and_next(false, window, cx);
15628 }
15629
15630 pub fn stage_or_unstage_diff_hunks(
15631 &mut self,
15632 stage: bool,
15633 ranges: Vec<Range<Anchor>>,
15634 cx: &mut Context<Self>,
15635 ) {
15636 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15637 cx.spawn(async move |this, cx| {
15638 task.await?;
15639 this.update(cx, |this, cx| {
15640 let snapshot = this.buffer.read(cx).snapshot(cx);
15641 let chunk_by = this
15642 .diff_hunks_in_ranges(&ranges, &snapshot)
15643 .chunk_by(|hunk| hunk.buffer_id);
15644 for (buffer_id, hunks) in &chunk_by {
15645 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15646 }
15647 })
15648 })
15649 .detach_and_log_err(cx);
15650 }
15651
15652 fn save_buffers_for_ranges_if_needed(
15653 &mut self,
15654 ranges: &[Range<Anchor>],
15655 cx: &mut Context<Editor>,
15656 ) -> Task<Result<()>> {
15657 let multibuffer = self.buffer.read(cx);
15658 let snapshot = multibuffer.read(cx);
15659 let buffer_ids: HashSet<_> = ranges
15660 .iter()
15661 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15662 .collect();
15663 drop(snapshot);
15664
15665 let mut buffers = HashSet::default();
15666 for buffer_id in buffer_ids {
15667 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15668 let buffer = buffer_entity.read(cx);
15669 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15670 {
15671 buffers.insert(buffer_entity);
15672 }
15673 }
15674 }
15675
15676 if let Some(project) = &self.project {
15677 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15678 } else {
15679 Task::ready(Ok(()))
15680 }
15681 }
15682
15683 fn do_stage_or_unstage_and_next(
15684 &mut self,
15685 stage: bool,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) {
15689 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15690
15691 if ranges.iter().any(|range| range.start != range.end) {
15692 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15693 return;
15694 }
15695
15696 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15697 let snapshot = self.snapshot(window, cx);
15698 let position = self.selections.newest::<Point>(cx).head();
15699 let mut row = snapshot
15700 .buffer_snapshot
15701 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15702 .find(|hunk| hunk.row_range.start.0 > position.row)
15703 .map(|hunk| hunk.row_range.start);
15704
15705 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15706 // Outside of the project diff editor, wrap around to the beginning.
15707 if !all_diff_hunks_expanded {
15708 row = row.or_else(|| {
15709 snapshot
15710 .buffer_snapshot
15711 .diff_hunks_in_range(Point::zero()..position)
15712 .find(|hunk| hunk.row_range.end.0 < position.row)
15713 .map(|hunk| hunk.row_range.start)
15714 });
15715 }
15716
15717 if let Some(row) = row {
15718 let destination = Point::new(row.0, 0);
15719 let autoscroll = Autoscroll::center();
15720
15721 self.unfold_ranges(&[destination..destination], false, false, cx);
15722 self.change_selections(Some(autoscroll), window, cx, |s| {
15723 s.select_ranges([destination..destination]);
15724 });
15725 }
15726 }
15727
15728 fn do_stage_or_unstage(
15729 &self,
15730 stage: bool,
15731 buffer_id: BufferId,
15732 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15733 cx: &mut App,
15734 ) -> Option<()> {
15735 let project = self.project.as_ref()?;
15736 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15737 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15738 let buffer_snapshot = buffer.read(cx).snapshot();
15739 let file_exists = buffer_snapshot
15740 .file()
15741 .is_some_and(|file| file.disk_state().exists());
15742 diff.update(cx, |diff, cx| {
15743 diff.stage_or_unstage_hunks(
15744 stage,
15745 &hunks
15746 .map(|hunk| buffer_diff::DiffHunk {
15747 buffer_range: hunk.buffer_range,
15748 diff_base_byte_range: hunk.diff_base_byte_range,
15749 secondary_status: hunk.secondary_status,
15750 range: Point::zero()..Point::zero(), // unused
15751 })
15752 .collect::<Vec<_>>(),
15753 &buffer_snapshot,
15754 file_exists,
15755 cx,
15756 )
15757 });
15758 None
15759 }
15760
15761 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15762 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15763 self.buffer
15764 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15765 }
15766
15767 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15768 self.buffer.update(cx, |buffer, cx| {
15769 let ranges = vec![Anchor::min()..Anchor::max()];
15770 if !buffer.all_diff_hunks_expanded()
15771 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15772 {
15773 buffer.collapse_diff_hunks(ranges, cx);
15774 true
15775 } else {
15776 false
15777 }
15778 })
15779 }
15780
15781 fn toggle_diff_hunks_in_ranges(
15782 &mut self,
15783 ranges: Vec<Range<Anchor>>,
15784 cx: &mut Context<Editor>,
15785 ) {
15786 self.buffer.update(cx, |buffer, cx| {
15787 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15788 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15789 })
15790 }
15791
15792 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15793 self.buffer.update(cx, |buffer, cx| {
15794 let snapshot = buffer.snapshot(cx);
15795 let excerpt_id = range.end.excerpt_id;
15796 let point_range = range.to_point(&snapshot);
15797 let expand = !buffer.single_hunk_is_expanded(range, cx);
15798 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15799 })
15800 }
15801
15802 pub(crate) fn apply_all_diff_hunks(
15803 &mut self,
15804 _: &ApplyAllDiffHunks,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15809
15810 let buffers = self.buffer.read(cx).all_buffers();
15811 for branch_buffer in buffers {
15812 branch_buffer.update(cx, |branch_buffer, cx| {
15813 branch_buffer.merge_into_base(Vec::new(), cx);
15814 });
15815 }
15816
15817 if let Some(project) = self.project.clone() {
15818 self.save(true, project, window, cx).detach_and_log_err(cx);
15819 }
15820 }
15821
15822 pub(crate) fn apply_selected_diff_hunks(
15823 &mut self,
15824 _: &ApplyDiffHunk,
15825 window: &mut Window,
15826 cx: &mut Context<Self>,
15827 ) {
15828 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15829 let snapshot = self.snapshot(window, cx);
15830 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15831 let mut ranges_by_buffer = HashMap::default();
15832 self.transact(window, cx, |editor, _window, cx| {
15833 for hunk in hunks {
15834 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15835 ranges_by_buffer
15836 .entry(buffer.clone())
15837 .or_insert_with(Vec::new)
15838 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15839 }
15840 }
15841
15842 for (buffer, ranges) in ranges_by_buffer {
15843 buffer.update(cx, |buffer, cx| {
15844 buffer.merge_into_base(ranges, cx);
15845 });
15846 }
15847 });
15848
15849 if let Some(project) = self.project.clone() {
15850 self.save(true, project, window, cx).detach_and_log_err(cx);
15851 }
15852 }
15853
15854 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15855 if hovered != self.gutter_hovered {
15856 self.gutter_hovered = hovered;
15857 cx.notify();
15858 }
15859 }
15860
15861 pub fn insert_blocks(
15862 &mut self,
15863 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15864 autoscroll: Option<Autoscroll>,
15865 cx: &mut Context<Self>,
15866 ) -> Vec<CustomBlockId> {
15867 let blocks = self
15868 .display_map
15869 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15870 if let Some(autoscroll) = autoscroll {
15871 self.request_autoscroll(autoscroll, cx);
15872 }
15873 cx.notify();
15874 blocks
15875 }
15876
15877 pub fn resize_blocks(
15878 &mut self,
15879 heights: HashMap<CustomBlockId, u32>,
15880 autoscroll: Option<Autoscroll>,
15881 cx: &mut Context<Self>,
15882 ) {
15883 self.display_map
15884 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15885 if let Some(autoscroll) = autoscroll {
15886 self.request_autoscroll(autoscroll, cx);
15887 }
15888 cx.notify();
15889 }
15890
15891 pub fn replace_blocks(
15892 &mut self,
15893 renderers: HashMap<CustomBlockId, RenderBlock>,
15894 autoscroll: Option<Autoscroll>,
15895 cx: &mut Context<Self>,
15896 ) {
15897 self.display_map
15898 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15899 if let Some(autoscroll) = autoscroll {
15900 self.request_autoscroll(autoscroll, cx);
15901 }
15902 cx.notify();
15903 }
15904
15905 pub fn remove_blocks(
15906 &mut self,
15907 block_ids: HashSet<CustomBlockId>,
15908 autoscroll: Option<Autoscroll>,
15909 cx: &mut Context<Self>,
15910 ) {
15911 self.display_map.update(cx, |display_map, cx| {
15912 display_map.remove_blocks(block_ids, cx)
15913 });
15914 if let Some(autoscroll) = autoscroll {
15915 self.request_autoscroll(autoscroll, cx);
15916 }
15917 cx.notify();
15918 }
15919
15920 pub fn row_for_block(
15921 &self,
15922 block_id: CustomBlockId,
15923 cx: &mut Context<Self>,
15924 ) -> Option<DisplayRow> {
15925 self.display_map
15926 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15927 }
15928
15929 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15930 self.focused_block = Some(focused_block);
15931 }
15932
15933 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15934 self.focused_block.take()
15935 }
15936
15937 pub fn insert_creases(
15938 &mut self,
15939 creases: impl IntoIterator<Item = Crease<Anchor>>,
15940 cx: &mut Context<Self>,
15941 ) -> Vec<CreaseId> {
15942 self.display_map
15943 .update(cx, |map, cx| map.insert_creases(creases, cx))
15944 }
15945
15946 pub fn remove_creases(
15947 &mut self,
15948 ids: impl IntoIterator<Item = CreaseId>,
15949 cx: &mut Context<Self>,
15950 ) {
15951 self.display_map
15952 .update(cx, |map, cx| map.remove_creases(ids, cx));
15953 }
15954
15955 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15956 self.display_map
15957 .update(cx, |map, cx| map.snapshot(cx))
15958 .longest_row()
15959 }
15960
15961 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15962 self.display_map
15963 .update(cx, |map, cx| map.snapshot(cx))
15964 .max_point()
15965 }
15966
15967 pub fn text(&self, cx: &App) -> String {
15968 self.buffer.read(cx).read(cx).text()
15969 }
15970
15971 pub fn is_empty(&self, cx: &App) -> bool {
15972 self.buffer.read(cx).read(cx).is_empty()
15973 }
15974
15975 pub fn text_option(&self, cx: &App) -> Option<String> {
15976 let text = self.text(cx);
15977 let text = text.trim();
15978
15979 if text.is_empty() {
15980 return None;
15981 }
15982
15983 Some(text.to_string())
15984 }
15985
15986 pub fn set_text(
15987 &mut self,
15988 text: impl Into<Arc<str>>,
15989 window: &mut Window,
15990 cx: &mut Context<Self>,
15991 ) {
15992 self.transact(window, cx, |this, _, cx| {
15993 this.buffer
15994 .read(cx)
15995 .as_singleton()
15996 .expect("you can only call set_text on editors for singleton buffers")
15997 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15998 });
15999 }
16000
16001 pub fn display_text(&self, cx: &mut App) -> String {
16002 self.display_map
16003 .update(cx, |map, cx| map.snapshot(cx))
16004 .text()
16005 }
16006
16007 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16008 let mut wrap_guides = smallvec::smallvec![];
16009
16010 if self.show_wrap_guides == Some(false) {
16011 return wrap_guides;
16012 }
16013
16014 let settings = self.buffer.read(cx).language_settings(cx);
16015 if settings.show_wrap_guides {
16016 match self.soft_wrap_mode(cx) {
16017 SoftWrap::Column(soft_wrap) => {
16018 wrap_guides.push((soft_wrap as usize, true));
16019 }
16020 SoftWrap::Bounded(soft_wrap) => {
16021 wrap_guides.push((soft_wrap as usize, true));
16022 }
16023 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16024 }
16025 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16026 }
16027
16028 wrap_guides
16029 }
16030
16031 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16032 let settings = self.buffer.read(cx).language_settings(cx);
16033 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16034 match mode {
16035 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16036 SoftWrap::None
16037 }
16038 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16039 language_settings::SoftWrap::PreferredLineLength => {
16040 SoftWrap::Column(settings.preferred_line_length)
16041 }
16042 language_settings::SoftWrap::Bounded => {
16043 SoftWrap::Bounded(settings.preferred_line_length)
16044 }
16045 }
16046 }
16047
16048 pub fn set_soft_wrap_mode(
16049 &mut self,
16050 mode: language_settings::SoftWrap,
16051
16052 cx: &mut Context<Self>,
16053 ) {
16054 self.soft_wrap_mode_override = Some(mode);
16055 cx.notify();
16056 }
16057
16058 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16059 self.hard_wrap = hard_wrap;
16060 cx.notify();
16061 }
16062
16063 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16064 self.text_style_refinement = Some(style);
16065 }
16066
16067 /// called by the Element so we know what style we were most recently rendered with.
16068 pub(crate) fn set_style(
16069 &mut self,
16070 style: EditorStyle,
16071 window: &mut Window,
16072 cx: &mut Context<Self>,
16073 ) {
16074 let rem_size = window.rem_size();
16075 self.display_map.update(cx, |map, cx| {
16076 map.set_font(
16077 style.text.font(),
16078 style.text.font_size.to_pixels(rem_size),
16079 cx,
16080 )
16081 });
16082 self.style = Some(style);
16083 }
16084
16085 pub fn style(&self) -> Option<&EditorStyle> {
16086 self.style.as_ref()
16087 }
16088
16089 // Called by the element. This method is not designed to be called outside of the editor
16090 // element's layout code because it does not notify when rewrapping is computed synchronously.
16091 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16092 self.display_map
16093 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16094 }
16095
16096 pub fn set_soft_wrap(&mut self) {
16097 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16098 }
16099
16100 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16101 if self.soft_wrap_mode_override.is_some() {
16102 self.soft_wrap_mode_override.take();
16103 } else {
16104 let soft_wrap = match self.soft_wrap_mode(cx) {
16105 SoftWrap::GitDiff => return,
16106 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16107 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16108 language_settings::SoftWrap::None
16109 }
16110 };
16111 self.soft_wrap_mode_override = Some(soft_wrap);
16112 }
16113 cx.notify();
16114 }
16115
16116 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16117 let Some(workspace) = self.workspace() else {
16118 return;
16119 };
16120 let fs = workspace.read(cx).app_state().fs.clone();
16121 let current_show = TabBarSettings::get_global(cx).show;
16122 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16123 setting.show = Some(!current_show);
16124 });
16125 }
16126
16127 pub fn toggle_indent_guides(
16128 &mut self,
16129 _: &ToggleIndentGuides,
16130 _: &mut Window,
16131 cx: &mut Context<Self>,
16132 ) {
16133 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16134 self.buffer
16135 .read(cx)
16136 .language_settings(cx)
16137 .indent_guides
16138 .enabled
16139 });
16140 self.show_indent_guides = Some(!currently_enabled);
16141 cx.notify();
16142 }
16143
16144 fn should_show_indent_guides(&self) -> Option<bool> {
16145 self.show_indent_guides
16146 }
16147
16148 pub fn toggle_line_numbers(
16149 &mut self,
16150 _: &ToggleLineNumbers,
16151 _: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 let mut editor_settings = EditorSettings::get_global(cx).clone();
16155 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16156 EditorSettings::override_global(editor_settings, cx);
16157 }
16158
16159 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16160 if let Some(show_line_numbers) = self.show_line_numbers {
16161 return show_line_numbers;
16162 }
16163 EditorSettings::get_global(cx).gutter.line_numbers
16164 }
16165
16166 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16167 self.use_relative_line_numbers
16168 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16169 }
16170
16171 pub fn toggle_relative_line_numbers(
16172 &mut self,
16173 _: &ToggleRelativeLineNumbers,
16174 _: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 let is_relative = self.should_use_relative_line_numbers(cx);
16178 self.set_relative_line_number(Some(!is_relative), cx)
16179 }
16180
16181 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16182 self.use_relative_line_numbers = is_relative;
16183 cx.notify();
16184 }
16185
16186 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16187 self.show_gutter = show_gutter;
16188 cx.notify();
16189 }
16190
16191 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16192 self.show_scrollbars = show_scrollbars;
16193 cx.notify();
16194 }
16195
16196 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16197 self.disable_scrolling = true;
16198 cx.notify();
16199 }
16200
16201 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16202 self.show_line_numbers = Some(show_line_numbers);
16203 cx.notify();
16204 }
16205
16206 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16207 self.disable_expand_excerpt_buttons = true;
16208 cx.notify();
16209 }
16210
16211 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16212 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16213 cx.notify();
16214 }
16215
16216 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16217 self.show_code_actions = Some(show_code_actions);
16218 cx.notify();
16219 }
16220
16221 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16222 self.show_runnables = Some(show_runnables);
16223 cx.notify();
16224 }
16225
16226 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16227 self.show_breakpoints = Some(show_breakpoints);
16228 cx.notify();
16229 }
16230
16231 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16232 if self.display_map.read(cx).masked != masked {
16233 self.display_map.update(cx, |map, _| map.masked = masked);
16234 }
16235 cx.notify()
16236 }
16237
16238 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16239 self.show_wrap_guides = Some(show_wrap_guides);
16240 cx.notify();
16241 }
16242
16243 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16244 self.show_indent_guides = Some(show_indent_guides);
16245 cx.notify();
16246 }
16247
16248 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16249 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16250 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16251 if let Some(dir) = file.abs_path(cx).parent() {
16252 return Some(dir.to_owned());
16253 }
16254 }
16255
16256 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16257 return Some(project_path.path.to_path_buf());
16258 }
16259 }
16260
16261 None
16262 }
16263
16264 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16265 self.active_excerpt(cx)?
16266 .1
16267 .read(cx)
16268 .file()
16269 .and_then(|f| f.as_local())
16270 }
16271
16272 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16273 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16274 let buffer = buffer.read(cx);
16275 if let Some(project_path) = buffer.project_path(cx) {
16276 let project = self.project.as_ref()?.read(cx);
16277 project.absolute_path(&project_path, cx)
16278 } else {
16279 buffer
16280 .file()
16281 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16282 }
16283 })
16284 }
16285
16286 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16287 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16288 let project_path = buffer.read(cx).project_path(cx)?;
16289 let project = self.project.as_ref()?.read(cx);
16290 let entry = project.entry_for_path(&project_path, cx)?;
16291 let path = entry.path.to_path_buf();
16292 Some(path)
16293 })
16294 }
16295
16296 pub fn reveal_in_finder(
16297 &mut self,
16298 _: &RevealInFileManager,
16299 _window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) {
16302 if let Some(target) = self.target_file(cx) {
16303 cx.reveal_path(&target.abs_path(cx));
16304 }
16305 }
16306
16307 pub fn copy_path(
16308 &mut self,
16309 _: &zed_actions::workspace::CopyPath,
16310 _window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) {
16313 if let Some(path) = self.target_file_abs_path(cx) {
16314 if let Some(path) = path.to_str() {
16315 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16316 }
16317 }
16318 }
16319
16320 pub fn copy_relative_path(
16321 &mut self,
16322 _: &zed_actions::workspace::CopyRelativePath,
16323 _window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 if let Some(path) = self.target_file_path(cx) {
16327 if let Some(path) = path.to_str() {
16328 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16329 }
16330 }
16331 }
16332
16333 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16334 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16335 buffer.read(cx).project_path(cx)
16336 } else {
16337 None
16338 }
16339 }
16340
16341 // Returns true if the editor handled a go-to-line request
16342 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16343 maybe!({
16344 let breakpoint_store = self.breakpoint_store.as_ref()?;
16345
16346 let Some((_, _, active_position)) =
16347 breakpoint_store.read(cx).active_position().cloned()
16348 else {
16349 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16350 return None;
16351 };
16352
16353 let snapshot = self
16354 .project
16355 .as_ref()?
16356 .read(cx)
16357 .buffer_for_id(active_position.buffer_id?, cx)?
16358 .read(cx)
16359 .snapshot();
16360
16361 let mut handled = false;
16362 for (id, ExcerptRange { context, .. }) in self
16363 .buffer
16364 .read(cx)
16365 .excerpts_for_buffer(active_position.buffer_id?, cx)
16366 {
16367 if context.start.cmp(&active_position, &snapshot).is_ge()
16368 || context.end.cmp(&active_position, &snapshot).is_lt()
16369 {
16370 continue;
16371 }
16372 let snapshot = self.buffer.read(cx).snapshot(cx);
16373 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16374
16375 handled = true;
16376 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16377 self.go_to_line::<DebugCurrentRowHighlight>(
16378 multibuffer_anchor,
16379 Some(cx.theme().colors().editor_debugger_active_line_background),
16380 window,
16381 cx,
16382 );
16383
16384 cx.notify();
16385 }
16386 handled.then_some(())
16387 })
16388 .is_some()
16389 }
16390
16391 pub fn copy_file_name_without_extension(
16392 &mut self,
16393 _: &CopyFileNameWithoutExtension,
16394 _: &mut Window,
16395 cx: &mut Context<Self>,
16396 ) {
16397 if let Some(file) = self.target_file(cx) {
16398 if let Some(file_stem) = file.path().file_stem() {
16399 if let Some(name) = file_stem.to_str() {
16400 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16401 }
16402 }
16403 }
16404 }
16405
16406 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16407 if let Some(file) = self.target_file(cx) {
16408 if let Some(file_name) = file.path().file_name() {
16409 if let Some(name) = file_name.to_str() {
16410 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16411 }
16412 }
16413 }
16414 }
16415
16416 pub fn toggle_git_blame(
16417 &mut self,
16418 _: &::git::Blame,
16419 window: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16423
16424 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16425 self.start_git_blame(true, window, cx);
16426 }
16427
16428 cx.notify();
16429 }
16430
16431 pub fn toggle_git_blame_inline(
16432 &mut self,
16433 _: &ToggleGitBlameInline,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) {
16437 self.toggle_git_blame_inline_internal(true, window, cx);
16438 cx.notify();
16439 }
16440
16441 pub fn open_git_blame_commit(
16442 &mut self,
16443 _: &OpenGitBlameCommit,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) {
16447 self.open_git_blame_commit_internal(window, cx);
16448 }
16449
16450 fn open_git_blame_commit_internal(
16451 &mut self,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) -> Option<()> {
16455 let blame = self.blame.as_ref()?;
16456 let snapshot = self.snapshot(window, cx);
16457 let cursor = self.selections.newest::<Point>(cx).head();
16458 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16459 let blame_entry = blame
16460 .update(cx, |blame, cx| {
16461 blame
16462 .blame_for_rows(
16463 &[RowInfo {
16464 buffer_id: Some(buffer.remote_id()),
16465 buffer_row: Some(point.row),
16466 ..Default::default()
16467 }],
16468 cx,
16469 )
16470 .next()
16471 })
16472 .flatten()?;
16473 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16474 let repo = blame.read(cx).repository(cx)?;
16475 let workspace = self.workspace()?.downgrade();
16476 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16477 None
16478 }
16479
16480 pub fn git_blame_inline_enabled(&self) -> bool {
16481 self.git_blame_inline_enabled
16482 }
16483
16484 pub fn toggle_selection_menu(
16485 &mut self,
16486 _: &ToggleSelectionMenu,
16487 _: &mut Window,
16488 cx: &mut Context<Self>,
16489 ) {
16490 self.show_selection_menu = self
16491 .show_selection_menu
16492 .map(|show_selections_menu| !show_selections_menu)
16493 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16494
16495 cx.notify();
16496 }
16497
16498 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16499 self.show_selection_menu
16500 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16501 }
16502
16503 fn start_git_blame(
16504 &mut self,
16505 user_triggered: bool,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) {
16509 if let Some(project) = self.project.as_ref() {
16510 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16511 return;
16512 };
16513
16514 if buffer.read(cx).file().is_none() {
16515 return;
16516 }
16517
16518 let focused = self.focus_handle(cx).contains_focused(window, cx);
16519
16520 let project = project.clone();
16521 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16522 self.blame_subscription =
16523 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16524 self.blame = Some(blame);
16525 }
16526 }
16527
16528 fn toggle_git_blame_inline_internal(
16529 &mut self,
16530 user_triggered: bool,
16531 window: &mut Window,
16532 cx: &mut Context<Self>,
16533 ) {
16534 if self.git_blame_inline_enabled {
16535 self.git_blame_inline_enabled = false;
16536 self.show_git_blame_inline = false;
16537 self.show_git_blame_inline_delay_task.take();
16538 } else {
16539 self.git_blame_inline_enabled = true;
16540 self.start_git_blame_inline(user_triggered, window, cx);
16541 }
16542
16543 cx.notify();
16544 }
16545
16546 fn start_git_blame_inline(
16547 &mut self,
16548 user_triggered: bool,
16549 window: &mut Window,
16550 cx: &mut Context<Self>,
16551 ) {
16552 self.start_git_blame(user_triggered, window, cx);
16553
16554 if ProjectSettings::get_global(cx)
16555 .git
16556 .inline_blame_delay()
16557 .is_some()
16558 {
16559 self.start_inline_blame_timer(window, cx);
16560 } else {
16561 self.show_git_blame_inline = true
16562 }
16563 }
16564
16565 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16566 self.blame.as_ref()
16567 }
16568
16569 pub fn show_git_blame_gutter(&self) -> bool {
16570 self.show_git_blame_gutter
16571 }
16572
16573 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16574 self.show_git_blame_gutter && self.has_blame_entries(cx)
16575 }
16576
16577 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16578 self.show_git_blame_inline
16579 && (self.focus_handle.is_focused(window)
16580 || self
16581 .git_blame_inline_tooltip
16582 .as_ref()
16583 .and_then(|t| t.upgrade())
16584 .is_some())
16585 && !self.newest_selection_head_on_empty_line(cx)
16586 && self.has_blame_entries(cx)
16587 }
16588
16589 fn has_blame_entries(&self, cx: &App) -> bool {
16590 self.blame()
16591 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16592 }
16593
16594 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16595 let cursor_anchor = self.selections.newest_anchor().head();
16596
16597 let snapshot = self.buffer.read(cx).snapshot(cx);
16598 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16599
16600 snapshot.line_len(buffer_row) == 0
16601 }
16602
16603 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16604 let buffer_and_selection = maybe!({
16605 let selection = self.selections.newest::<Point>(cx);
16606 let selection_range = selection.range();
16607
16608 let multi_buffer = self.buffer().read(cx);
16609 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16610 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16611
16612 let (buffer, range, _) = if selection.reversed {
16613 buffer_ranges.first()
16614 } else {
16615 buffer_ranges.last()
16616 }?;
16617
16618 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16619 ..text::ToPoint::to_point(&range.end, &buffer).row;
16620 Some((
16621 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16622 selection,
16623 ))
16624 });
16625
16626 let Some((buffer, selection)) = buffer_and_selection else {
16627 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16628 };
16629
16630 let Some(project) = self.project.as_ref() else {
16631 return Task::ready(Err(anyhow!("editor does not have project")));
16632 };
16633
16634 project.update(cx, |project, cx| {
16635 project.get_permalink_to_line(&buffer, selection, cx)
16636 })
16637 }
16638
16639 pub fn copy_permalink_to_line(
16640 &mut self,
16641 _: &CopyPermalinkToLine,
16642 window: &mut Window,
16643 cx: &mut Context<Self>,
16644 ) {
16645 let permalink_task = self.get_permalink_to_line(cx);
16646 let workspace = self.workspace();
16647
16648 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16649 Ok(permalink) => {
16650 cx.update(|_, cx| {
16651 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16652 })
16653 .ok();
16654 }
16655 Err(err) => {
16656 let message = format!("Failed to copy permalink: {err}");
16657
16658 Err::<(), anyhow::Error>(err).log_err();
16659
16660 if let Some(workspace) = workspace {
16661 workspace
16662 .update_in(cx, |workspace, _, cx| {
16663 struct CopyPermalinkToLine;
16664
16665 workspace.show_toast(
16666 Toast::new(
16667 NotificationId::unique::<CopyPermalinkToLine>(),
16668 message,
16669 ),
16670 cx,
16671 )
16672 })
16673 .ok();
16674 }
16675 }
16676 })
16677 .detach();
16678 }
16679
16680 pub fn copy_file_location(
16681 &mut self,
16682 _: &CopyFileLocation,
16683 _: &mut Window,
16684 cx: &mut Context<Self>,
16685 ) {
16686 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16687 if let Some(file) = self.target_file(cx) {
16688 if let Some(path) = file.path().to_str() {
16689 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16690 }
16691 }
16692 }
16693
16694 pub fn open_permalink_to_line(
16695 &mut self,
16696 _: &OpenPermalinkToLine,
16697 window: &mut Window,
16698 cx: &mut Context<Self>,
16699 ) {
16700 let permalink_task = self.get_permalink_to_line(cx);
16701 let workspace = self.workspace();
16702
16703 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16704 Ok(permalink) => {
16705 cx.update(|_, cx| {
16706 cx.open_url(permalink.as_ref());
16707 })
16708 .ok();
16709 }
16710 Err(err) => {
16711 let message = format!("Failed to open permalink: {err}");
16712
16713 Err::<(), anyhow::Error>(err).log_err();
16714
16715 if let Some(workspace) = workspace {
16716 workspace
16717 .update(cx, |workspace, cx| {
16718 struct OpenPermalinkToLine;
16719
16720 workspace.show_toast(
16721 Toast::new(
16722 NotificationId::unique::<OpenPermalinkToLine>(),
16723 message,
16724 ),
16725 cx,
16726 )
16727 })
16728 .ok();
16729 }
16730 }
16731 })
16732 .detach();
16733 }
16734
16735 pub fn insert_uuid_v4(
16736 &mut self,
16737 _: &InsertUuidV4,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 self.insert_uuid(UuidVersion::V4, window, cx);
16742 }
16743
16744 pub fn insert_uuid_v7(
16745 &mut self,
16746 _: &InsertUuidV7,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.insert_uuid(UuidVersion::V7, window, cx);
16751 }
16752
16753 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16754 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16755 self.transact(window, cx, |this, window, cx| {
16756 let edits = this
16757 .selections
16758 .all::<Point>(cx)
16759 .into_iter()
16760 .map(|selection| {
16761 let uuid = match version {
16762 UuidVersion::V4 => uuid::Uuid::new_v4(),
16763 UuidVersion::V7 => uuid::Uuid::now_v7(),
16764 };
16765
16766 (selection.range(), uuid.to_string())
16767 });
16768 this.edit(edits, cx);
16769 this.refresh_inline_completion(true, false, window, cx);
16770 });
16771 }
16772
16773 pub fn open_selections_in_multibuffer(
16774 &mut self,
16775 _: &OpenSelectionsInMultibuffer,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) {
16779 let multibuffer = self.buffer.read(cx);
16780
16781 let Some(buffer) = multibuffer.as_singleton() else {
16782 return;
16783 };
16784
16785 let Some(workspace) = self.workspace() else {
16786 return;
16787 };
16788
16789 let locations = self
16790 .selections
16791 .disjoint_anchors()
16792 .iter()
16793 .map(|range| Location {
16794 buffer: buffer.clone(),
16795 range: range.start.text_anchor..range.end.text_anchor,
16796 })
16797 .collect::<Vec<_>>();
16798
16799 let title = multibuffer.title(cx).to_string();
16800
16801 cx.spawn_in(window, async move |_, cx| {
16802 workspace.update_in(cx, |workspace, window, cx| {
16803 Self::open_locations_in_multibuffer(
16804 workspace,
16805 locations,
16806 format!("Selections for '{title}'"),
16807 false,
16808 MultibufferSelectionMode::All,
16809 window,
16810 cx,
16811 );
16812 })
16813 })
16814 .detach();
16815 }
16816
16817 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16818 /// last highlight added will be used.
16819 ///
16820 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16821 pub fn highlight_rows<T: 'static>(
16822 &mut self,
16823 range: Range<Anchor>,
16824 color: Hsla,
16825 options: RowHighlightOptions,
16826 cx: &mut Context<Self>,
16827 ) {
16828 let snapshot = self.buffer().read(cx).snapshot(cx);
16829 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16830 let ix = row_highlights.binary_search_by(|highlight| {
16831 Ordering::Equal
16832 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16833 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16834 });
16835
16836 if let Err(mut ix) = ix {
16837 let index = post_inc(&mut self.highlight_order);
16838
16839 // If this range intersects with the preceding highlight, then merge it with
16840 // the preceding highlight. Otherwise insert a new highlight.
16841 let mut merged = false;
16842 if ix > 0 {
16843 let prev_highlight = &mut row_highlights[ix - 1];
16844 if prev_highlight
16845 .range
16846 .end
16847 .cmp(&range.start, &snapshot)
16848 .is_ge()
16849 {
16850 ix -= 1;
16851 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16852 prev_highlight.range.end = range.end;
16853 }
16854 merged = true;
16855 prev_highlight.index = index;
16856 prev_highlight.color = color;
16857 prev_highlight.options = options;
16858 }
16859 }
16860
16861 if !merged {
16862 row_highlights.insert(
16863 ix,
16864 RowHighlight {
16865 range: range.clone(),
16866 index,
16867 color,
16868 options,
16869 type_id: TypeId::of::<T>(),
16870 },
16871 );
16872 }
16873
16874 // If any of the following highlights intersect with this one, merge them.
16875 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16876 let highlight = &row_highlights[ix];
16877 if next_highlight
16878 .range
16879 .start
16880 .cmp(&highlight.range.end, &snapshot)
16881 .is_le()
16882 {
16883 if next_highlight
16884 .range
16885 .end
16886 .cmp(&highlight.range.end, &snapshot)
16887 .is_gt()
16888 {
16889 row_highlights[ix].range.end = next_highlight.range.end;
16890 }
16891 row_highlights.remove(ix + 1);
16892 } else {
16893 break;
16894 }
16895 }
16896 }
16897 }
16898
16899 /// Remove any highlighted row ranges of the given type that intersect the
16900 /// given ranges.
16901 pub fn remove_highlighted_rows<T: 'static>(
16902 &mut self,
16903 ranges_to_remove: Vec<Range<Anchor>>,
16904 cx: &mut Context<Self>,
16905 ) {
16906 let snapshot = self.buffer().read(cx).snapshot(cx);
16907 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16908 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16909 row_highlights.retain(|highlight| {
16910 while let Some(range_to_remove) = ranges_to_remove.peek() {
16911 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16912 Ordering::Less | Ordering::Equal => {
16913 ranges_to_remove.next();
16914 }
16915 Ordering::Greater => {
16916 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16917 Ordering::Less | Ordering::Equal => {
16918 return false;
16919 }
16920 Ordering::Greater => break,
16921 }
16922 }
16923 }
16924 }
16925
16926 true
16927 })
16928 }
16929
16930 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16931 pub fn clear_row_highlights<T: 'static>(&mut self) {
16932 self.highlighted_rows.remove(&TypeId::of::<T>());
16933 }
16934
16935 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16936 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16937 self.highlighted_rows
16938 .get(&TypeId::of::<T>())
16939 .map_or(&[] as &[_], |vec| vec.as_slice())
16940 .iter()
16941 .map(|highlight| (highlight.range.clone(), highlight.color))
16942 }
16943
16944 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16945 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16946 /// Allows to ignore certain kinds of highlights.
16947 pub fn highlighted_display_rows(
16948 &self,
16949 window: &mut Window,
16950 cx: &mut App,
16951 ) -> BTreeMap<DisplayRow, LineHighlight> {
16952 let snapshot = self.snapshot(window, cx);
16953 let mut used_highlight_orders = HashMap::default();
16954 self.highlighted_rows
16955 .iter()
16956 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16957 .fold(
16958 BTreeMap::<DisplayRow, LineHighlight>::new(),
16959 |mut unique_rows, highlight| {
16960 let start = highlight.range.start.to_display_point(&snapshot);
16961 let end = highlight.range.end.to_display_point(&snapshot);
16962 let start_row = start.row().0;
16963 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16964 && end.column() == 0
16965 {
16966 end.row().0.saturating_sub(1)
16967 } else {
16968 end.row().0
16969 };
16970 for row in start_row..=end_row {
16971 let used_index =
16972 used_highlight_orders.entry(row).or_insert(highlight.index);
16973 if highlight.index >= *used_index {
16974 *used_index = highlight.index;
16975 unique_rows.insert(
16976 DisplayRow(row),
16977 LineHighlight {
16978 include_gutter: highlight.options.include_gutter,
16979 border: None,
16980 background: highlight.color.into(),
16981 type_id: Some(highlight.type_id),
16982 },
16983 );
16984 }
16985 }
16986 unique_rows
16987 },
16988 )
16989 }
16990
16991 pub fn highlighted_display_row_for_autoscroll(
16992 &self,
16993 snapshot: &DisplaySnapshot,
16994 ) -> Option<DisplayRow> {
16995 self.highlighted_rows
16996 .values()
16997 .flat_map(|highlighted_rows| highlighted_rows.iter())
16998 .filter_map(|highlight| {
16999 if highlight.options.autoscroll {
17000 Some(highlight.range.start.to_display_point(snapshot).row())
17001 } else {
17002 None
17003 }
17004 })
17005 .min()
17006 }
17007
17008 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17009 self.highlight_background::<SearchWithinRange>(
17010 ranges,
17011 |colors| colors.editor_document_highlight_read_background,
17012 cx,
17013 )
17014 }
17015
17016 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17017 self.breadcrumb_header = Some(new_header);
17018 }
17019
17020 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17021 self.clear_background_highlights::<SearchWithinRange>(cx);
17022 }
17023
17024 pub fn highlight_background<T: 'static>(
17025 &mut self,
17026 ranges: &[Range<Anchor>],
17027 color_fetcher: fn(&ThemeColors) -> Hsla,
17028 cx: &mut Context<Self>,
17029 ) {
17030 self.background_highlights
17031 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17032 self.scrollbar_marker_state.dirty = true;
17033 cx.notify();
17034 }
17035
17036 pub fn clear_background_highlights<T: 'static>(
17037 &mut self,
17038 cx: &mut Context<Self>,
17039 ) -> Option<BackgroundHighlight> {
17040 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17041 if !text_highlights.1.is_empty() {
17042 self.scrollbar_marker_state.dirty = true;
17043 cx.notify();
17044 }
17045 Some(text_highlights)
17046 }
17047
17048 pub fn highlight_gutter<T: 'static>(
17049 &mut self,
17050 ranges: &[Range<Anchor>],
17051 color_fetcher: fn(&App) -> Hsla,
17052 cx: &mut Context<Self>,
17053 ) {
17054 self.gutter_highlights
17055 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17056 cx.notify();
17057 }
17058
17059 pub fn clear_gutter_highlights<T: 'static>(
17060 &mut self,
17061 cx: &mut Context<Self>,
17062 ) -> Option<GutterHighlight> {
17063 cx.notify();
17064 self.gutter_highlights.remove(&TypeId::of::<T>())
17065 }
17066
17067 #[cfg(feature = "test-support")]
17068 pub fn all_text_background_highlights(
17069 &self,
17070 window: &mut Window,
17071 cx: &mut Context<Self>,
17072 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17073 let snapshot = self.snapshot(window, cx);
17074 let buffer = &snapshot.buffer_snapshot;
17075 let start = buffer.anchor_before(0);
17076 let end = buffer.anchor_after(buffer.len());
17077 let theme = cx.theme().colors();
17078 self.background_highlights_in_range(start..end, &snapshot, theme)
17079 }
17080
17081 #[cfg(feature = "test-support")]
17082 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17083 let snapshot = self.buffer().read(cx).snapshot(cx);
17084
17085 let highlights = self
17086 .background_highlights
17087 .get(&TypeId::of::<items::BufferSearchHighlights>());
17088
17089 if let Some((_color, ranges)) = highlights {
17090 ranges
17091 .iter()
17092 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17093 .collect_vec()
17094 } else {
17095 vec![]
17096 }
17097 }
17098
17099 fn document_highlights_for_position<'a>(
17100 &'a self,
17101 position: Anchor,
17102 buffer: &'a MultiBufferSnapshot,
17103 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17104 let read_highlights = self
17105 .background_highlights
17106 .get(&TypeId::of::<DocumentHighlightRead>())
17107 .map(|h| &h.1);
17108 let write_highlights = self
17109 .background_highlights
17110 .get(&TypeId::of::<DocumentHighlightWrite>())
17111 .map(|h| &h.1);
17112 let left_position = position.bias_left(buffer);
17113 let right_position = position.bias_right(buffer);
17114 read_highlights
17115 .into_iter()
17116 .chain(write_highlights)
17117 .flat_map(move |ranges| {
17118 let start_ix = match ranges.binary_search_by(|probe| {
17119 let cmp = probe.end.cmp(&left_position, buffer);
17120 if cmp.is_ge() {
17121 Ordering::Greater
17122 } else {
17123 Ordering::Less
17124 }
17125 }) {
17126 Ok(i) | Err(i) => i,
17127 };
17128
17129 ranges[start_ix..]
17130 .iter()
17131 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17132 })
17133 }
17134
17135 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17136 self.background_highlights
17137 .get(&TypeId::of::<T>())
17138 .map_or(false, |(_, highlights)| !highlights.is_empty())
17139 }
17140
17141 pub fn background_highlights_in_range(
17142 &self,
17143 search_range: Range<Anchor>,
17144 display_snapshot: &DisplaySnapshot,
17145 theme: &ThemeColors,
17146 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17147 let mut results = Vec::new();
17148 for (color_fetcher, ranges) in self.background_highlights.values() {
17149 let color = color_fetcher(theme);
17150 let start_ix = match ranges.binary_search_by(|probe| {
17151 let cmp = probe
17152 .end
17153 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17154 if cmp.is_gt() {
17155 Ordering::Greater
17156 } else {
17157 Ordering::Less
17158 }
17159 }) {
17160 Ok(i) | Err(i) => i,
17161 };
17162 for range in &ranges[start_ix..] {
17163 if range
17164 .start
17165 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17166 .is_ge()
17167 {
17168 break;
17169 }
17170
17171 let start = range.start.to_display_point(display_snapshot);
17172 let end = range.end.to_display_point(display_snapshot);
17173 results.push((start..end, color))
17174 }
17175 }
17176 results
17177 }
17178
17179 pub fn background_highlight_row_ranges<T: 'static>(
17180 &self,
17181 search_range: Range<Anchor>,
17182 display_snapshot: &DisplaySnapshot,
17183 count: usize,
17184 ) -> Vec<RangeInclusive<DisplayPoint>> {
17185 let mut results = Vec::new();
17186 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17187 return vec![];
17188 };
17189
17190 let start_ix = match ranges.binary_search_by(|probe| {
17191 let cmp = probe
17192 .end
17193 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17194 if cmp.is_gt() {
17195 Ordering::Greater
17196 } else {
17197 Ordering::Less
17198 }
17199 }) {
17200 Ok(i) | Err(i) => i,
17201 };
17202 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17203 if let (Some(start_display), Some(end_display)) = (start, end) {
17204 results.push(
17205 start_display.to_display_point(display_snapshot)
17206 ..=end_display.to_display_point(display_snapshot),
17207 );
17208 }
17209 };
17210 let mut start_row: Option<Point> = None;
17211 let mut end_row: Option<Point> = None;
17212 if ranges.len() > count {
17213 return Vec::new();
17214 }
17215 for range in &ranges[start_ix..] {
17216 if range
17217 .start
17218 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17219 .is_ge()
17220 {
17221 break;
17222 }
17223 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17224 if let Some(current_row) = &end_row {
17225 if end.row == current_row.row {
17226 continue;
17227 }
17228 }
17229 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17230 if start_row.is_none() {
17231 assert_eq!(end_row, None);
17232 start_row = Some(start);
17233 end_row = Some(end);
17234 continue;
17235 }
17236 if let Some(current_end) = end_row.as_mut() {
17237 if start.row > current_end.row + 1 {
17238 push_region(start_row, end_row);
17239 start_row = Some(start);
17240 end_row = Some(end);
17241 } else {
17242 // Merge two hunks.
17243 *current_end = end;
17244 }
17245 } else {
17246 unreachable!();
17247 }
17248 }
17249 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17250 push_region(start_row, end_row);
17251 results
17252 }
17253
17254 pub fn gutter_highlights_in_range(
17255 &self,
17256 search_range: Range<Anchor>,
17257 display_snapshot: &DisplaySnapshot,
17258 cx: &App,
17259 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17260 let mut results = Vec::new();
17261 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17262 let color = color_fetcher(cx);
17263 let start_ix = match ranges.binary_search_by(|probe| {
17264 let cmp = probe
17265 .end
17266 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17267 if cmp.is_gt() {
17268 Ordering::Greater
17269 } else {
17270 Ordering::Less
17271 }
17272 }) {
17273 Ok(i) | Err(i) => i,
17274 };
17275 for range in &ranges[start_ix..] {
17276 if range
17277 .start
17278 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17279 .is_ge()
17280 {
17281 break;
17282 }
17283
17284 let start = range.start.to_display_point(display_snapshot);
17285 let end = range.end.to_display_point(display_snapshot);
17286 results.push((start..end, color))
17287 }
17288 }
17289 results
17290 }
17291
17292 /// Get the text ranges corresponding to the redaction query
17293 pub fn redacted_ranges(
17294 &self,
17295 search_range: Range<Anchor>,
17296 display_snapshot: &DisplaySnapshot,
17297 cx: &App,
17298 ) -> Vec<Range<DisplayPoint>> {
17299 display_snapshot
17300 .buffer_snapshot
17301 .redacted_ranges(search_range, |file| {
17302 if let Some(file) = file {
17303 file.is_private()
17304 && EditorSettings::get(
17305 Some(SettingsLocation {
17306 worktree_id: file.worktree_id(cx),
17307 path: file.path().as_ref(),
17308 }),
17309 cx,
17310 )
17311 .redact_private_values
17312 } else {
17313 false
17314 }
17315 })
17316 .map(|range| {
17317 range.start.to_display_point(display_snapshot)
17318 ..range.end.to_display_point(display_snapshot)
17319 })
17320 .collect()
17321 }
17322
17323 pub fn highlight_text<T: 'static>(
17324 &mut self,
17325 ranges: Vec<Range<Anchor>>,
17326 style: HighlightStyle,
17327 cx: &mut Context<Self>,
17328 ) {
17329 self.display_map.update(cx, |map, _| {
17330 map.highlight_text(TypeId::of::<T>(), ranges, style)
17331 });
17332 cx.notify();
17333 }
17334
17335 pub(crate) fn highlight_inlays<T: 'static>(
17336 &mut self,
17337 highlights: Vec<InlayHighlight>,
17338 style: HighlightStyle,
17339 cx: &mut Context<Self>,
17340 ) {
17341 self.display_map.update(cx, |map, _| {
17342 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17343 });
17344 cx.notify();
17345 }
17346
17347 pub fn text_highlights<'a, T: 'static>(
17348 &'a self,
17349 cx: &'a App,
17350 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17351 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17352 }
17353
17354 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17355 let cleared = self
17356 .display_map
17357 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17358 if cleared {
17359 cx.notify();
17360 }
17361 }
17362
17363 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17364 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17365 && self.focus_handle.is_focused(window)
17366 }
17367
17368 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17369 self.show_cursor_when_unfocused = is_enabled;
17370 cx.notify();
17371 }
17372
17373 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17374 cx.notify();
17375 }
17376
17377 fn on_buffer_event(
17378 &mut self,
17379 multibuffer: &Entity<MultiBuffer>,
17380 event: &multi_buffer::Event,
17381 window: &mut Window,
17382 cx: &mut Context<Self>,
17383 ) {
17384 match event {
17385 multi_buffer::Event::Edited {
17386 singleton_buffer_edited,
17387 edited_buffer: buffer_edited,
17388 } => {
17389 self.scrollbar_marker_state.dirty = true;
17390 self.active_indent_guides_state.dirty = true;
17391 self.refresh_active_diagnostics(cx);
17392 self.refresh_code_actions(window, cx);
17393 if self.has_active_inline_completion() {
17394 self.update_visible_inline_completion(window, cx);
17395 }
17396 if let Some(buffer) = buffer_edited {
17397 let buffer_id = buffer.read(cx).remote_id();
17398 if !self.registered_buffers.contains_key(&buffer_id) {
17399 if let Some(project) = self.project.as_ref() {
17400 project.update(cx, |project, cx| {
17401 self.registered_buffers.insert(
17402 buffer_id,
17403 project.register_buffer_with_language_servers(&buffer, cx),
17404 );
17405 })
17406 }
17407 }
17408 }
17409 cx.emit(EditorEvent::BufferEdited);
17410 cx.emit(SearchEvent::MatchesInvalidated);
17411 if *singleton_buffer_edited {
17412 if let Some(project) = &self.project {
17413 #[allow(clippy::mutable_key_type)]
17414 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17415 multibuffer
17416 .all_buffers()
17417 .into_iter()
17418 .filter_map(|buffer| {
17419 buffer.update(cx, |buffer, cx| {
17420 let language = buffer.language()?;
17421 let should_discard = project.update(cx, |project, cx| {
17422 project.is_local()
17423 && !project.has_language_servers_for(buffer, cx)
17424 });
17425 should_discard.not().then_some(language.clone())
17426 })
17427 })
17428 .collect::<HashSet<_>>()
17429 });
17430 if !languages_affected.is_empty() {
17431 self.refresh_inlay_hints(
17432 InlayHintRefreshReason::BufferEdited(languages_affected),
17433 cx,
17434 );
17435 }
17436 }
17437 }
17438
17439 let Some(project) = &self.project else { return };
17440 let (telemetry, is_via_ssh) = {
17441 let project = project.read(cx);
17442 let telemetry = project.client().telemetry().clone();
17443 let is_via_ssh = project.is_via_ssh();
17444 (telemetry, is_via_ssh)
17445 };
17446 refresh_linked_ranges(self, window, cx);
17447 telemetry.log_edit_event("editor", is_via_ssh);
17448 }
17449 multi_buffer::Event::ExcerptsAdded {
17450 buffer,
17451 predecessor,
17452 excerpts,
17453 } => {
17454 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17455 let buffer_id = buffer.read(cx).remote_id();
17456 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17457 if let Some(project) = &self.project {
17458 get_uncommitted_diff_for_buffer(
17459 project,
17460 [buffer.clone()],
17461 self.buffer.clone(),
17462 cx,
17463 )
17464 .detach();
17465 }
17466 }
17467 cx.emit(EditorEvent::ExcerptsAdded {
17468 buffer: buffer.clone(),
17469 predecessor: *predecessor,
17470 excerpts: excerpts.clone(),
17471 });
17472 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17473 }
17474 multi_buffer::Event::ExcerptsRemoved {
17475 ids,
17476 removed_buffer_ids,
17477 } => {
17478 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17479 let buffer = self.buffer.read(cx);
17480 self.registered_buffers
17481 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17482 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17483 cx.emit(EditorEvent::ExcerptsRemoved {
17484 ids: ids.clone(),
17485 removed_buffer_ids: removed_buffer_ids.clone(),
17486 })
17487 }
17488 multi_buffer::Event::ExcerptsEdited {
17489 excerpt_ids,
17490 buffer_ids,
17491 } => {
17492 self.display_map.update(cx, |map, cx| {
17493 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17494 });
17495 cx.emit(EditorEvent::ExcerptsEdited {
17496 ids: excerpt_ids.clone(),
17497 })
17498 }
17499 multi_buffer::Event::ExcerptsExpanded { ids } => {
17500 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17501 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17502 }
17503 multi_buffer::Event::Reparsed(buffer_id) => {
17504 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17505 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17506
17507 cx.emit(EditorEvent::Reparsed(*buffer_id));
17508 }
17509 multi_buffer::Event::DiffHunksToggled => {
17510 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17511 }
17512 multi_buffer::Event::LanguageChanged(buffer_id) => {
17513 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17514 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17515 cx.emit(EditorEvent::Reparsed(*buffer_id));
17516 cx.notify();
17517 }
17518 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17519 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17520 multi_buffer::Event::FileHandleChanged
17521 | multi_buffer::Event::Reloaded
17522 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17523 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17524 multi_buffer::Event::DiagnosticsUpdated => {
17525 self.refresh_active_diagnostics(cx);
17526 self.refresh_inline_diagnostics(true, window, cx);
17527 self.scrollbar_marker_state.dirty = true;
17528 cx.notify();
17529 }
17530 _ => {}
17531 };
17532 }
17533
17534 fn on_display_map_changed(
17535 &mut self,
17536 _: Entity<DisplayMap>,
17537 _: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 cx.notify();
17541 }
17542
17543 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17544 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17545 self.update_edit_prediction_settings(cx);
17546 self.refresh_inline_completion(true, false, window, cx);
17547 self.refresh_inlay_hints(
17548 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17549 self.selections.newest_anchor().head(),
17550 &self.buffer.read(cx).snapshot(cx),
17551 cx,
17552 )),
17553 cx,
17554 );
17555
17556 let old_cursor_shape = self.cursor_shape;
17557
17558 {
17559 let editor_settings = EditorSettings::get_global(cx);
17560 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17561 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17562 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17563 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17564 }
17565
17566 if old_cursor_shape != self.cursor_shape {
17567 cx.emit(EditorEvent::CursorShapeChanged);
17568 }
17569
17570 let project_settings = ProjectSettings::get_global(cx);
17571 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17572
17573 if self.mode.is_full() {
17574 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17575 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17576 if self.show_inline_diagnostics != show_inline_diagnostics {
17577 self.show_inline_diagnostics = show_inline_diagnostics;
17578 self.refresh_inline_diagnostics(false, window, cx);
17579 }
17580
17581 if self.git_blame_inline_enabled != inline_blame_enabled {
17582 self.toggle_git_blame_inline_internal(false, window, cx);
17583 }
17584 }
17585
17586 cx.notify();
17587 }
17588
17589 pub fn set_searchable(&mut self, searchable: bool) {
17590 self.searchable = searchable;
17591 }
17592
17593 pub fn searchable(&self) -> bool {
17594 self.searchable
17595 }
17596
17597 fn open_proposed_changes_editor(
17598 &mut self,
17599 _: &OpenProposedChangesEditor,
17600 window: &mut Window,
17601 cx: &mut Context<Self>,
17602 ) {
17603 let Some(workspace) = self.workspace() else {
17604 cx.propagate();
17605 return;
17606 };
17607
17608 let selections = self.selections.all::<usize>(cx);
17609 let multi_buffer = self.buffer.read(cx);
17610 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17611 let mut new_selections_by_buffer = HashMap::default();
17612 for selection in selections {
17613 for (buffer, range, _) in
17614 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17615 {
17616 let mut range = range.to_point(buffer);
17617 range.start.column = 0;
17618 range.end.column = buffer.line_len(range.end.row);
17619 new_selections_by_buffer
17620 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17621 .or_insert(Vec::new())
17622 .push(range)
17623 }
17624 }
17625
17626 let proposed_changes_buffers = new_selections_by_buffer
17627 .into_iter()
17628 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17629 .collect::<Vec<_>>();
17630 let proposed_changes_editor = cx.new(|cx| {
17631 ProposedChangesEditor::new(
17632 "Proposed changes",
17633 proposed_changes_buffers,
17634 self.project.clone(),
17635 window,
17636 cx,
17637 )
17638 });
17639
17640 window.defer(cx, move |window, cx| {
17641 workspace.update(cx, |workspace, cx| {
17642 workspace.active_pane().update(cx, |pane, cx| {
17643 pane.add_item(
17644 Box::new(proposed_changes_editor),
17645 true,
17646 true,
17647 None,
17648 window,
17649 cx,
17650 );
17651 });
17652 });
17653 });
17654 }
17655
17656 pub fn open_excerpts_in_split(
17657 &mut self,
17658 _: &OpenExcerptsSplit,
17659 window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) {
17662 self.open_excerpts_common(None, true, window, cx)
17663 }
17664
17665 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17666 self.open_excerpts_common(None, false, window, cx)
17667 }
17668
17669 fn open_excerpts_common(
17670 &mut self,
17671 jump_data: Option<JumpData>,
17672 split: bool,
17673 window: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let Some(workspace) = self.workspace() else {
17677 cx.propagate();
17678 return;
17679 };
17680
17681 if self.buffer.read(cx).is_singleton() {
17682 cx.propagate();
17683 return;
17684 }
17685
17686 let mut new_selections_by_buffer = HashMap::default();
17687 match &jump_data {
17688 Some(JumpData::MultiBufferPoint {
17689 excerpt_id,
17690 position,
17691 anchor,
17692 line_offset_from_top,
17693 }) => {
17694 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17695 if let Some(buffer) = multi_buffer_snapshot
17696 .buffer_id_for_excerpt(*excerpt_id)
17697 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17698 {
17699 let buffer_snapshot = buffer.read(cx).snapshot();
17700 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17701 language::ToPoint::to_point(anchor, &buffer_snapshot)
17702 } else {
17703 buffer_snapshot.clip_point(*position, Bias::Left)
17704 };
17705 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17706 new_selections_by_buffer.insert(
17707 buffer,
17708 (
17709 vec![jump_to_offset..jump_to_offset],
17710 Some(*line_offset_from_top),
17711 ),
17712 );
17713 }
17714 }
17715 Some(JumpData::MultiBufferRow {
17716 row,
17717 line_offset_from_top,
17718 }) => {
17719 let point = MultiBufferPoint::new(row.0, 0);
17720 if let Some((buffer, buffer_point, _)) =
17721 self.buffer.read(cx).point_to_buffer_point(point, cx)
17722 {
17723 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17724 new_selections_by_buffer
17725 .entry(buffer)
17726 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17727 .0
17728 .push(buffer_offset..buffer_offset)
17729 }
17730 }
17731 None => {
17732 let selections = self.selections.all::<usize>(cx);
17733 let multi_buffer = self.buffer.read(cx);
17734 for selection in selections {
17735 for (snapshot, range, _, anchor) in multi_buffer
17736 .snapshot(cx)
17737 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17738 {
17739 if let Some(anchor) = anchor {
17740 // selection is in a deleted hunk
17741 let Some(buffer_id) = anchor.buffer_id else {
17742 continue;
17743 };
17744 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17745 continue;
17746 };
17747 let offset = text::ToOffset::to_offset(
17748 &anchor.text_anchor,
17749 &buffer_handle.read(cx).snapshot(),
17750 );
17751 let range = offset..offset;
17752 new_selections_by_buffer
17753 .entry(buffer_handle)
17754 .or_insert((Vec::new(), None))
17755 .0
17756 .push(range)
17757 } else {
17758 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17759 else {
17760 continue;
17761 };
17762 new_selections_by_buffer
17763 .entry(buffer_handle)
17764 .or_insert((Vec::new(), None))
17765 .0
17766 .push(range)
17767 }
17768 }
17769 }
17770 }
17771 }
17772
17773 new_selections_by_buffer
17774 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17775
17776 if new_selections_by_buffer.is_empty() {
17777 return;
17778 }
17779
17780 // We defer the pane interaction because we ourselves are a workspace item
17781 // and activating a new item causes the pane to call a method on us reentrantly,
17782 // which panics if we're on the stack.
17783 window.defer(cx, move |window, cx| {
17784 workspace.update(cx, |workspace, cx| {
17785 let pane = if split {
17786 workspace.adjacent_pane(window, cx)
17787 } else {
17788 workspace.active_pane().clone()
17789 };
17790
17791 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17792 let editor = buffer
17793 .read(cx)
17794 .file()
17795 .is_none()
17796 .then(|| {
17797 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17798 // so `workspace.open_project_item` will never find them, always opening a new editor.
17799 // Instead, we try to activate the existing editor in the pane first.
17800 let (editor, pane_item_index) =
17801 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17802 let editor = item.downcast::<Editor>()?;
17803 let singleton_buffer =
17804 editor.read(cx).buffer().read(cx).as_singleton()?;
17805 if singleton_buffer == buffer {
17806 Some((editor, i))
17807 } else {
17808 None
17809 }
17810 })?;
17811 pane.update(cx, |pane, cx| {
17812 pane.activate_item(pane_item_index, true, true, window, cx)
17813 });
17814 Some(editor)
17815 })
17816 .flatten()
17817 .unwrap_or_else(|| {
17818 workspace.open_project_item::<Self>(
17819 pane.clone(),
17820 buffer,
17821 true,
17822 true,
17823 window,
17824 cx,
17825 )
17826 });
17827
17828 editor.update(cx, |editor, cx| {
17829 let autoscroll = match scroll_offset {
17830 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17831 None => Autoscroll::newest(),
17832 };
17833 let nav_history = editor.nav_history.take();
17834 editor.change_selections(Some(autoscroll), window, cx, |s| {
17835 s.select_ranges(ranges);
17836 });
17837 editor.nav_history = nav_history;
17838 });
17839 }
17840 })
17841 });
17842 }
17843
17844 // For now, don't allow opening excerpts in buffers that aren't backed by
17845 // regular project files.
17846 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17847 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17848 }
17849
17850 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17851 let snapshot = self.buffer.read(cx).read(cx);
17852 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17853 Some(
17854 ranges
17855 .iter()
17856 .map(move |range| {
17857 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17858 })
17859 .collect(),
17860 )
17861 }
17862
17863 fn selection_replacement_ranges(
17864 &self,
17865 range: Range<OffsetUtf16>,
17866 cx: &mut App,
17867 ) -> Vec<Range<OffsetUtf16>> {
17868 let selections = self.selections.all::<OffsetUtf16>(cx);
17869 let newest_selection = selections
17870 .iter()
17871 .max_by_key(|selection| selection.id)
17872 .unwrap();
17873 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17874 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17875 let snapshot = self.buffer.read(cx).read(cx);
17876 selections
17877 .into_iter()
17878 .map(|mut selection| {
17879 selection.start.0 =
17880 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17881 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17882 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17883 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17884 })
17885 .collect()
17886 }
17887
17888 fn report_editor_event(
17889 &self,
17890 event_type: &'static str,
17891 file_extension: Option<String>,
17892 cx: &App,
17893 ) {
17894 if cfg!(any(test, feature = "test-support")) {
17895 return;
17896 }
17897
17898 let Some(project) = &self.project else { return };
17899
17900 // If None, we are in a file without an extension
17901 let file = self
17902 .buffer
17903 .read(cx)
17904 .as_singleton()
17905 .and_then(|b| b.read(cx).file());
17906 let file_extension = file_extension.or(file
17907 .as_ref()
17908 .and_then(|file| Path::new(file.file_name(cx)).extension())
17909 .and_then(|e| e.to_str())
17910 .map(|a| a.to_string()));
17911
17912 let vim_mode = vim_enabled(cx);
17913
17914 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17915 let copilot_enabled = edit_predictions_provider
17916 == language::language_settings::EditPredictionProvider::Copilot;
17917 let copilot_enabled_for_language = self
17918 .buffer
17919 .read(cx)
17920 .language_settings(cx)
17921 .show_edit_predictions;
17922
17923 let project = project.read(cx);
17924 telemetry::event!(
17925 event_type,
17926 file_extension,
17927 vim_mode,
17928 copilot_enabled,
17929 copilot_enabled_for_language,
17930 edit_predictions_provider,
17931 is_via_ssh = project.is_via_ssh(),
17932 );
17933 }
17934
17935 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17936 /// with each line being an array of {text, highlight} objects.
17937 fn copy_highlight_json(
17938 &mut self,
17939 _: &CopyHighlightJson,
17940 window: &mut Window,
17941 cx: &mut Context<Self>,
17942 ) {
17943 #[derive(Serialize)]
17944 struct Chunk<'a> {
17945 text: String,
17946 highlight: Option<&'a str>,
17947 }
17948
17949 let snapshot = self.buffer.read(cx).snapshot(cx);
17950 let range = self
17951 .selected_text_range(false, window, cx)
17952 .and_then(|selection| {
17953 if selection.range.is_empty() {
17954 None
17955 } else {
17956 Some(selection.range)
17957 }
17958 })
17959 .unwrap_or_else(|| 0..snapshot.len());
17960
17961 let chunks = snapshot.chunks(range, true);
17962 let mut lines = Vec::new();
17963 let mut line: VecDeque<Chunk> = VecDeque::new();
17964
17965 let Some(style) = self.style.as_ref() else {
17966 return;
17967 };
17968
17969 for chunk in chunks {
17970 let highlight = chunk
17971 .syntax_highlight_id
17972 .and_then(|id| id.name(&style.syntax));
17973 let mut chunk_lines = chunk.text.split('\n').peekable();
17974 while let Some(text) = chunk_lines.next() {
17975 let mut merged_with_last_token = false;
17976 if let Some(last_token) = line.back_mut() {
17977 if last_token.highlight == highlight {
17978 last_token.text.push_str(text);
17979 merged_with_last_token = true;
17980 }
17981 }
17982
17983 if !merged_with_last_token {
17984 line.push_back(Chunk {
17985 text: text.into(),
17986 highlight,
17987 });
17988 }
17989
17990 if chunk_lines.peek().is_some() {
17991 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17992 line.pop_front();
17993 }
17994 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17995 line.pop_back();
17996 }
17997
17998 lines.push(mem::take(&mut line));
17999 }
18000 }
18001 }
18002
18003 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18004 return;
18005 };
18006 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18007 }
18008
18009 pub fn open_context_menu(
18010 &mut self,
18011 _: &OpenContextMenu,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 ) {
18015 self.request_autoscroll(Autoscroll::newest(), cx);
18016 let position = self.selections.newest_display(cx).start;
18017 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18018 }
18019
18020 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18021 &self.inlay_hint_cache
18022 }
18023
18024 pub fn replay_insert_event(
18025 &mut self,
18026 text: &str,
18027 relative_utf16_range: Option<Range<isize>>,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) {
18031 if !self.input_enabled {
18032 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18033 return;
18034 }
18035 if let Some(relative_utf16_range) = relative_utf16_range {
18036 let selections = self.selections.all::<OffsetUtf16>(cx);
18037 self.change_selections(None, window, cx, |s| {
18038 let new_ranges = selections.into_iter().map(|range| {
18039 let start = OffsetUtf16(
18040 range
18041 .head()
18042 .0
18043 .saturating_add_signed(relative_utf16_range.start),
18044 );
18045 let end = OffsetUtf16(
18046 range
18047 .head()
18048 .0
18049 .saturating_add_signed(relative_utf16_range.end),
18050 );
18051 start..end
18052 });
18053 s.select_ranges(new_ranges);
18054 });
18055 }
18056
18057 self.handle_input(text, window, cx);
18058 }
18059
18060 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18061 let Some(provider) = self.semantics_provider.as_ref() else {
18062 return false;
18063 };
18064
18065 let mut supports = false;
18066 self.buffer().update(cx, |this, cx| {
18067 this.for_each_buffer(|buffer| {
18068 supports |= provider.supports_inlay_hints(buffer, cx);
18069 });
18070 });
18071
18072 supports
18073 }
18074
18075 pub fn is_focused(&self, window: &Window) -> bool {
18076 self.focus_handle.is_focused(window)
18077 }
18078
18079 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18080 cx.emit(EditorEvent::Focused);
18081
18082 if let Some(descendant) = self
18083 .last_focused_descendant
18084 .take()
18085 .and_then(|descendant| descendant.upgrade())
18086 {
18087 window.focus(&descendant);
18088 } else {
18089 if let Some(blame) = self.blame.as_ref() {
18090 blame.update(cx, GitBlame::focus)
18091 }
18092
18093 self.blink_manager.update(cx, BlinkManager::enable);
18094 self.show_cursor_names(window, cx);
18095 self.buffer.update(cx, |buffer, cx| {
18096 buffer.finalize_last_transaction(cx);
18097 if self.leader_peer_id.is_none() {
18098 buffer.set_active_selections(
18099 &self.selections.disjoint_anchors(),
18100 self.selections.line_mode,
18101 self.cursor_shape,
18102 cx,
18103 );
18104 }
18105 });
18106 }
18107 }
18108
18109 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18110 cx.emit(EditorEvent::FocusedIn)
18111 }
18112
18113 fn handle_focus_out(
18114 &mut self,
18115 event: FocusOutEvent,
18116 _window: &mut Window,
18117 cx: &mut Context<Self>,
18118 ) {
18119 if event.blurred != self.focus_handle {
18120 self.last_focused_descendant = Some(event.blurred);
18121 }
18122 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18123 }
18124
18125 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18126 self.blink_manager.update(cx, BlinkManager::disable);
18127 self.buffer
18128 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18129
18130 if let Some(blame) = self.blame.as_ref() {
18131 blame.update(cx, GitBlame::blur)
18132 }
18133 if !self.hover_state.focused(window, cx) {
18134 hide_hover(self, cx);
18135 }
18136 if !self
18137 .context_menu
18138 .borrow()
18139 .as_ref()
18140 .is_some_and(|context_menu| context_menu.focused(window, cx))
18141 {
18142 self.hide_context_menu(window, cx);
18143 }
18144 self.discard_inline_completion(false, cx);
18145 cx.emit(EditorEvent::Blurred);
18146 cx.notify();
18147 }
18148
18149 pub fn register_action<A: Action>(
18150 &mut self,
18151 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18152 ) -> Subscription {
18153 let id = self.next_editor_action_id.post_inc();
18154 let listener = Arc::new(listener);
18155 self.editor_actions.borrow_mut().insert(
18156 id,
18157 Box::new(move |window, _| {
18158 let listener = listener.clone();
18159 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18160 let action = action.downcast_ref().unwrap();
18161 if phase == DispatchPhase::Bubble {
18162 listener(action, window, cx)
18163 }
18164 })
18165 }),
18166 );
18167
18168 let editor_actions = self.editor_actions.clone();
18169 Subscription::new(move || {
18170 editor_actions.borrow_mut().remove(&id);
18171 })
18172 }
18173
18174 pub fn file_header_size(&self) -> u32 {
18175 FILE_HEADER_HEIGHT
18176 }
18177
18178 pub fn restore(
18179 &mut self,
18180 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18181 window: &mut Window,
18182 cx: &mut Context<Self>,
18183 ) {
18184 let workspace = self.workspace();
18185 let project = self.project.as_ref();
18186 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18187 let mut tasks = Vec::new();
18188 for (buffer_id, changes) in revert_changes {
18189 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18190 buffer.update(cx, |buffer, cx| {
18191 buffer.edit(
18192 changes
18193 .into_iter()
18194 .map(|(range, text)| (range, text.to_string())),
18195 None,
18196 cx,
18197 );
18198 });
18199
18200 if let Some(project) =
18201 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18202 {
18203 project.update(cx, |project, cx| {
18204 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18205 })
18206 }
18207 }
18208 }
18209 tasks
18210 });
18211 cx.spawn_in(window, async move |_, cx| {
18212 for (buffer, task) in save_tasks {
18213 let result = task.await;
18214 if result.is_err() {
18215 let Some(path) = buffer
18216 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18217 .ok()
18218 else {
18219 continue;
18220 };
18221 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18222 let Some(task) = cx
18223 .update_window_entity(&workspace, |workspace, window, cx| {
18224 workspace
18225 .open_path_preview(path, None, false, false, false, window, cx)
18226 })
18227 .ok()
18228 else {
18229 continue;
18230 };
18231 task.await.log_err();
18232 }
18233 }
18234 }
18235 })
18236 .detach();
18237 self.change_selections(None, window, cx, |selections| selections.refresh());
18238 }
18239
18240 pub fn to_pixel_point(
18241 &self,
18242 source: multi_buffer::Anchor,
18243 editor_snapshot: &EditorSnapshot,
18244 window: &mut Window,
18245 ) -> Option<gpui::Point<Pixels>> {
18246 let source_point = source.to_display_point(editor_snapshot);
18247 self.display_to_pixel_point(source_point, editor_snapshot, window)
18248 }
18249
18250 pub fn display_to_pixel_point(
18251 &self,
18252 source: DisplayPoint,
18253 editor_snapshot: &EditorSnapshot,
18254 window: &mut Window,
18255 ) -> Option<gpui::Point<Pixels>> {
18256 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18257 let text_layout_details = self.text_layout_details(window);
18258 let scroll_top = text_layout_details
18259 .scroll_anchor
18260 .scroll_position(editor_snapshot)
18261 .y;
18262
18263 if source.row().as_f32() < scroll_top.floor() {
18264 return None;
18265 }
18266 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18267 let source_y = line_height * (source.row().as_f32() - scroll_top);
18268 Some(gpui::Point::new(source_x, source_y))
18269 }
18270
18271 pub fn has_visible_completions_menu(&self) -> bool {
18272 !self.edit_prediction_preview_is_active()
18273 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18274 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18275 })
18276 }
18277
18278 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18279 self.addons
18280 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18281 }
18282
18283 pub fn unregister_addon<T: Addon>(&mut self) {
18284 self.addons.remove(&std::any::TypeId::of::<T>());
18285 }
18286
18287 pub fn addon<T: Addon>(&self) -> Option<&T> {
18288 let type_id = std::any::TypeId::of::<T>();
18289 self.addons
18290 .get(&type_id)
18291 .and_then(|item| item.to_any().downcast_ref::<T>())
18292 }
18293
18294 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18295 let type_id = std::any::TypeId::of::<T>();
18296 self.addons
18297 .get_mut(&type_id)
18298 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18299 }
18300
18301 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18302 let text_layout_details = self.text_layout_details(window);
18303 let style = &text_layout_details.editor_style;
18304 let font_id = window.text_system().resolve_font(&style.text.font());
18305 let font_size = style.text.font_size.to_pixels(window.rem_size());
18306 let line_height = style.text.line_height_in_pixels(window.rem_size());
18307 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18308
18309 gpui::Size::new(em_width, line_height)
18310 }
18311
18312 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18313 self.load_diff_task.clone()
18314 }
18315
18316 fn read_metadata_from_db(
18317 &mut self,
18318 item_id: u64,
18319 workspace_id: WorkspaceId,
18320 window: &mut Window,
18321 cx: &mut Context<Editor>,
18322 ) {
18323 if self.is_singleton(cx)
18324 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18325 {
18326 let buffer_snapshot = OnceCell::new();
18327
18328 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18329 if !folds.is_empty() {
18330 let snapshot =
18331 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18332 self.fold_ranges(
18333 folds
18334 .into_iter()
18335 .map(|(start, end)| {
18336 snapshot.clip_offset(start, Bias::Left)
18337 ..snapshot.clip_offset(end, Bias::Right)
18338 })
18339 .collect(),
18340 false,
18341 window,
18342 cx,
18343 );
18344 }
18345 }
18346
18347 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18348 if !selections.is_empty() {
18349 let snapshot =
18350 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18351 self.change_selections(None, window, cx, |s| {
18352 s.select_ranges(selections.into_iter().map(|(start, end)| {
18353 snapshot.clip_offset(start, Bias::Left)
18354 ..snapshot.clip_offset(end, Bias::Right)
18355 }));
18356 });
18357 }
18358 };
18359 }
18360
18361 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18362 }
18363}
18364
18365fn vim_enabled(cx: &App) -> bool {
18366 cx.global::<SettingsStore>()
18367 .raw_user_settings()
18368 .get("vim_mode")
18369 == Some(&serde_json::Value::Bool(true))
18370}
18371
18372// Consider user intent and default settings
18373fn choose_completion_range(
18374 completion: &Completion,
18375 intent: CompletionIntent,
18376 buffer: &Entity<Buffer>,
18377 cx: &mut Context<Editor>,
18378) -> Range<usize> {
18379 fn should_replace(
18380 completion: &Completion,
18381 insert_range: &Range<text::Anchor>,
18382 intent: CompletionIntent,
18383 completion_mode_setting: LspInsertMode,
18384 buffer: &Buffer,
18385 ) -> bool {
18386 // specific actions take precedence over settings
18387 match intent {
18388 CompletionIntent::CompleteWithInsert => return false,
18389 CompletionIntent::CompleteWithReplace => return true,
18390 CompletionIntent::Complete | CompletionIntent::Compose => {}
18391 }
18392
18393 match completion_mode_setting {
18394 LspInsertMode::Insert => false,
18395 LspInsertMode::Replace => true,
18396 LspInsertMode::ReplaceSubsequence => {
18397 let mut text_to_replace = buffer.chars_for_range(
18398 buffer.anchor_before(completion.replace_range.start)
18399 ..buffer.anchor_after(completion.replace_range.end),
18400 );
18401 let mut completion_text = completion.new_text.chars();
18402
18403 // is `text_to_replace` a subsequence of `completion_text`
18404 text_to_replace
18405 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18406 }
18407 LspInsertMode::ReplaceSuffix => {
18408 let range_after_cursor = insert_range.end..completion.replace_range.end;
18409
18410 let text_after_cursor = buffer
18411 .text_for_range(
18412 buffer.anchor_before(range_after_cursor.start)
18413 ..buffer.anchor_after(range_after_cursor.end),
18414 )
18415 .collect::<String>();
18416 completion.new_text.ends_with(&text_after_cursor)
18417 }
18418 }
18419 }
18420
18421 let buffer = buffer.read(cx);
18422
18423 if let CompletionSource::Lsp {
18424 insert_range: Some(insert_range),
18425 ..
18426 } = &completion.source
18427 {
18428 let completion_mode_setting =
18429 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18430 .completions
18431 .lsp_insert_mode;
18432
18433 if !should_replace(
18434 completion,
18435 &insert_range,
18436 intent,
18437 completion_mode_setting,
18438 buffer,
18439 ) {
18440 return insert_range.to_offset(buffer);
18441 }
18442 }
18443
18444 completion.replace_range.to_offset(buffer)
18445}
18446
18447fn insert_extra_newline_brackets(
18448 buffer: &MultiBufferSnapshot,
18449 range: Range<usize>,
18450 language: &language::LanguageScope,
18451) -> bool {
18452 let leading_whitespace_len = buffer
18453 .reversed_chars_at(range.start)
18454 .take_while(|c| c.is_whitespace() && *c != '\n')
18455 .map(|c| c.len_utf8())
18456 .sum::<usize>();
18457 let trailing_whitespace_len = buffer
18458 .chars_at(range.end)
18459 .take_while(|c| c.is_whitespace() && *c != '\n')
18460 .map(|c| c.len_utf8())
18461 .sum::<usize>();
18462 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18463
18464 language.brackets().any(|(pair, enabled)| {
18465 let pair_start = pair.start.trim_end();
18466 let pair_end = pair.end.trim_start();
18467
18468 enabled
18469 && pair.newline
18470 && buffer.contains_str_at(range.end, pair_end)
18471 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18472 })
18473}
18474
18475fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18476 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18477 [(buffer, range, _)] => (*buffer, range.clone()),
18478 _ => return false,
18479 };
18480 let pair = {
18481 let mut result: Option<BracketMatch> = None;
18482
18483 for pair in buffer
18484 .all_bracket_ranges(range.clone())
18485 .filter(move |pair| {
18486 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18487 })
18488 {
18489 let len = pair.close_range.end - pair.open_range.start;
18490
18491 if let Some(existing) = &result {
18492 let existing_len = existing.close_range.end - existing.open_range.start;
18493 if len > existing_len {
18494 continue;
18495 }
18496 }
18497
18498 result = Some(pair);
18499 }
18500
18501 result
18502 };
18503 let Some(pair) = pair else {
18504 return false;
18505 };
18506 pair.newline_only
18507 && buffer
18508 .chars_for_range(pair.open_range.end..range.start)
18509 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18510 .all(|c| c.is_whitespace() && c != '\n')
18511}
18512
18513fn get_uncommitted_diff_for_buffer(
18514 project: &Entity<Project>,
18515 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18516 buffer: Entity<MultiBuffer>,
18517 cx: &mut App,
18518) -> Task<()> {
18519 let mut tasks = Vec::new();
18520 project.update(cx, |project, cx| {
18521 for buffer in buffers {
18522 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18523 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18524 }
18525 }
18526 });
18527 cx.spawn(async move |cx| {
18528 let diffs = future::join_all(tasks).await;
18529 buffer
18530 .update(cx, |buffer, cx| {
18531 for diff in diffs.into_iter().flatten() {
18532 buffer.add_diff(diff, cx);
18533 }
18534 })
18535 .ok();
18536 })
18537}
18538
18539fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18540 let tab_size = tab_size.get() as usize;
18541 let mut width = offset;
18542
18543 for ch in text.chars() {
18544 width += if ch == '\t' {
18545 tab_size - (width % tab_size)
18546 } else {
18547 1
18548 };
18549 }
18550
18551 width - offset
18552}
18553
18554#[cfg(test)]
18555mod tests {
18556 use super::*;
18557
18558 #[test]
18559 fn test_string_size_with_expanded_tabs() {
18560 let nz = |val| NonZeroU32::new(val).unwrap();
18561 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18562 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18563 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18564 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18565 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18566 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18567 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18568 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18569 }
18570}
18571
18572/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18573struct WordBreakingTokenizer<'a> {
18574 input: &'a str,
18575}
18576
18577impl<'a> WordBreakingTokenizer<'a> {
18578 fn new(input: &'a str) -> Self {
18579 Self { input }
18580 }
18581}
18582
18583fn is_char_ideographic(ch: char) -> bool {
18584 use unicode_script::Script::*;
18585 use unicode_script::UnicodeScript;
18586 matches!(ch.script(), Han | Tangut | Yi)
18587}
18588
18589fn is_grapheme_ideographic(text: &str) -> bool {
18590 text.chars().any(is_char_ideographic)
18591}
18592
18593fn is_grapheme_whitespace(text: &str) -> bool {
18594 text.chars().any(|x| x.is_whitespace())
18595}
18596
18597fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18598 text.chars().next().map_or(false, |ch| {
18599 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18600 })
18601}
18602
18603#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18604enum WordBreakToken<'a> {
18605 Word { token: &'a str, grapheme_len: usize },
18606 InlineWhitespace { token: &'a str, grapheme_len: usize },
18607 Newline,
18608}
18609
18610impl<'a> Iterator for WordBreakingTokenizer<'a> {
18611 /// Yields a span, the count of graphemes in the token, and whether it was
18612 /// whitespace. Note that it also breaks at word boundaries.
18613 type Item = WordBreakToken<'a>;
18614
18615 fn next(&mut self) -> Option<Self::Item> {
18616 use unicode_segmentation::UnicodeSegmentation;
18617 if self.input.is_empty() {
18618 return None;
18619 }
18620
18621 let mut iter = self.input.graphemes(true).peekable();
18622 let mut offset = 0;
18623 let mut grapheme_len = 0;
18624 if let Some(first_grapheme) = iter.next() {
18625 let is_newline = first_grapheme == "\n";
18626 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18627 offset += first_grapheme.len();
18628 grapheme_len += 1;
18629 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18630 if let Some(grapheme) = iter.peek().copied() {
18631 if should_stay_with_preceding_ideograph(grapheme) {
18632 offset += grapheme.len();
18633 grapheme_len += 1;
18634 }
18635 }
18636 } else {
18637 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18638 let mut next_word_bound = words.peek().copied();
18639 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18640 next_word_bound = words.next();
18641 }
18642 while let Some(grapheme) = iter.peek().copied() {
18643 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18644 break;
18645 };
18646 if is_grapheme_whitespace(grapheme) != is_whitespace
18647 || (grapheme == "\n") != is_newline
18648 {
18649 break;
18650 };
18651 offset += grapheme.len();
18652 grapheme_len += 1;
18653 iter.next();
18654 }
18655 }
18656 let token = &self.input[..offset];
18657 self.input = &self.input[offset..];
18658 if token == "\n" {
18659 Some(WordBreakToken::Newline)
18660 } else if is_whitespace {
18661 Some(WordBreakToken::InlineWhitespace {
18662 token,
18663 grapheme_len,
18664 })
18665 } else {
18666 Some(WordBreakToken::Word {
18667 token,
18668 grapheme_len,
18669 })
18670 }
18671 } else {
18672 None
18673 }
18674 }
18675}
18676
18677#[test]
18678fn test_word_breaking_tokenizer() {
18679 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18680 ("", &[]),
18681 (" ", &[whitespace(" ", 2)]),
18682 ("Ʒ", &[word("Ʒ", 1)]),
18683 ("Ǽ", &[word("Ǽ", 1)]),
18684 ("⋑", &[word("⋑", 1)]),
18685 ("⋑⋑", &[word("⋑⋑", 2)]),
18686 (
18687 "原理,进而",
18688 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18689 ),
18690 (
18691 "hello world",
18692 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18693 ),
18694 (
18695 "hello, world",
18696 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18697 ),
18698 (
18699 " hello world",
18700 &[
18701 whitespace(" ", 2),
18702 word("hello", 5),
18703 whitespace(" ", 1),
18704 word("world", 5),
18705 ],
18706 ),
18707 (
18708 "这是什么 \n 钢笔",
18709 &[
18710 word("这", 1),
18711 word("是", 1),
18712 word("什", 1),
18713 word("么", 1),
18714 whitespace(" ", 1),
18715 newline(),
18716 whitespace(" ", 1),
18717 word("钢", 1),
18718 word("笔", 1),
18719 ],
18720 ),
18721 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18722 ];
18723
18724 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18725 WordBreakToken::Word {
18726 token,
18727 grapheme_len,
18728 }
18729 }
18730
18731 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18732 WordBreakToken::InlineWhitespace {
18733 token,
18734 grapheme_len,
18735 }
18736 }
18737
18738 fn newline() -> WordBreakToken<'static> {
18739 WordBreakToken::Newline
18740 }
18741
18742 for (input, result) in tests {
18743 assert_eq!(
18744 WordBreakingTokenizer::new(input)
18745 .collect::<Vec<_>>()
18746 .as_slice(),
18747 *result,
18748 );
18749 }
18750}
18751
18752fn wrap_with_prefix(
18753 line_prefix: String,
18754 unwrapped_text: String,
18755 wrap_column: usize,
18756 tab_size: NonZeroU32,
18757 preserve_existing_whitespace: bool,
18758) -> String {
18759 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18760 let mut wrapped_text = String::new();
18761 let mut current_line = line_prefix.clone();
18762
18763 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18764 let mut current_line_len = line_prefix_len;
18765 let mut in_whitespace = false;
18766 for token in tokenizer {
18767 let have_preceding_whitespace = in_whitespace;
18768 match token {
18769 WordBreakToken::Word {
18770 token,
18771 grapheme_len,
18772 } => {
18773 in_whitespace = false;
18774 if current_line_len + grapheme_len > wrap_column
18775 && current_line_len != line_prefix_len
18776 {
18777 wrapped_text.push_str(current_line.trim_end());
18778 wrapped_text.push('\n');
18779 current_line.truncate(line_prefix.len());
18780 current_line_len = line_prefix_len;
18781 }
18782 current_line.push_str(token);
18783 current_line_len += grapheme_len;
18784 }
18785 WordBreakToken::InlineWhitespace {
18786 mut token,
18787 mut grapheme_len,
18788 } => {
18789 in_whitespace = true;
18790 if have_preceding_whitespace && !preserve_existing_whitespace {
18791 continue;
18792 }
18793 if !preserve_existing_whitespace {
18794 token = " ";
18795 grapheme_len = 1;
18796 }
18797 if current_line_len + grapheme_len > wrap_column {
18798 wrapped_text.push_str(current_line.trim_end());
18799 wrapped_text.push('\n');
18800 current_line.truncate(line_prefix.len());
18801 current_line_len = line_prefix_len;
18802 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18803 current_line.push_str(token);
18804 current_line_len += grapheme_len;
18805 }
18806 }
18807 WordBreakToken::Newline => {
18808 in_whitespace = true;
18809 if preserve_existing_whitespace {
18810 wrapped_text.push_str(current_line.trim_end());
18811 wrapped_text.push('\n');
18812 current_line.truncate(line_prefix.len());
18813 current_line_len = line_prefix_len;
18814 } else if have_preceding_whitespace {
18815 continue;
18816 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18817 {
18818 wrapped_text.push_str(current_line.trim_end());
18819 wrapped_text.push('\n');
18820 current_line.truncate(line_prefix.len());
18821 current_line_len = line_prefix_len;
18822 } else if current_line_len != line_prefix_len {
18823 current_line.push(' ');
18824 current_line_len += 1;
18825 }
18826 }
18827 }
18828 }
18829
18830 if !current_line.is_empty() {
18831 wrapped_text.push_str(¤t_line);
18832 }
18833 wrapped_text
18834}
18835
18836#[test]
18837fn test_wrap_with_prefix() {
18838 assert_eq!(
18839 wrap_with_prefix(
18840 "# ".to_string(),
18841 "abcdefg".to_string(),
18842 4,
18843 NonZeroU32::new(4).unwrap(),
18844 false,
18845 ),
18846 "# abcdefg"
18847 );
18848 assert_eq!(
18849 wrap_with_prefix(
18850 "".to_string(),
18851 "\thello world".to_string(),
18852 8,
18853 NonZeroU32::new(4).unwrap(),
18854 false,
18855 ),
18856 "hello\nworld"
18857 );
18858 assert_eq!(
18859 wrap_with_prefix(
18860 "// ".to_string(),
18861 "xx \nyy zz aa bb cc".to_string(),
18862 12,
18863 NonZeroU32::new(4).unwrap(),
18864 false,
18865 ),
18866 "// xx yy zz\n// aa bb cc"
18867 );
18868 assert_eq!(
18869 wrap_with_prefix(
18870 String::new(),
18871 "这是什么 \n 钢笔".to_string(),
18872 3,
18873 NonZeroU32::new(4).unwrap(),
18874 false,
18875 ),
18876 "这是什\n么 钢\n笔"
18877 );
18878}
18879
18880pub trait CollaborationHub {
18881 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18882 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18883 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18884}
18885
18886impl CollaborationHub for Entity<Project> {
18887 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18888 self.read(cx).collaborators()
18889 }
18890
18891 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18892 self.read(cx).user_store().read(cx).participant_indices()
18893 }
18894
18895 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18896 let this = self.read(cx);
18897 let user_ids = this.collaborators().values().map(|c| c.user_id);
18898 this.user_store().read_with(cx, |user_store, cx| {
18899 user_store.participant_names(user_ids, cx)
18900 })
18901 }
18902}
18903
18904pub trait SemanticsProvider {
18905 fn hover(
18906 &self,
18907 buffer: &Entity<Buffer>,
18908 position: text::Anchor,
18909 cx: &mut App,
18910 ) -> Option<Task<Vec<project::Hover>>>;
18911
18912 fn inlay_hints(
18913 &self,
18914 buffer_handle: Entity<Buffer>,
18915 range: Range<text::Anchor>,
18916 cx: &mut App,
18917 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18918
18919 fn resolve_inlay_hint(
18920 &self,
18921 hint: InlayHint,
18922 buffer_handle: Entity<Buffer>,
18923 server_id: LanguageServerId,
18924 cx: &mut App,
18925 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18926
18927 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18928
18929 fn document_highlights(
18930 &self,
18931 buffer: &Entity<Buffer>,
18932 position: text::Anchor,
18933 cx: &mut App,
18934 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18935
18936 fn definitions(
18937 &self,
18938 buffer: &Entity<Buffer>,
18939 position: text::Anchor,
18940 kind: GotoDefinitionKind,
18941 cx: &mut App,
18942 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18943
18944 fn range_for_rename(
18945 &self,
18946 buffer: &Entity<Buffer>,
18947 position: text::Anchor,
18948 cx: &mut App,
18949 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18950
18951 fn perform_rename(
18952 &self,
18953 buffer: &Entity<Buffer>,
18954 position: text::Anchor,
18955 new_name: String,
18956 cx: &mut App,
18957 ) -> Option<Task<Result<ProjectTransaction>>>;
18958}
18959
18960pub trait CompletionProvider {
18961 fn completions(
18962 &self,
18963 excerpt_id: ExcerptId,
18964 buffer: &Entity<Buffer>,
18965 buffer_position: text::Anchor,
18966 trigger: CompletionContext,
18967 window: &mut Window,
18968 cx: &mut Context<Editor>,
18969 ) -> Task<Result<Option<Vec<Completion>>>>;
18970
18971 fn resolve_completions(
18972 &self,
18973 buffer: Entity<Buffer>,
18974 completion_indices: Vec<usize>,
18975 completions: Rc<RefCell<Box<[Completion]>>>,
18976 cx: &mut Context<Editor>,
18977 ) -> Task<Result<bool>>;
18978
18979 fn apply_additional_edits_for_completion(
18980 &self,
18981 _buffer: Entity<Buffer>,
18982 _completions: Rc<RefCell<Box<[Completion]>>>,
18983 _completion_index: usize,
18984 _push_to_history: bool,
18985 _cx: &mut Context<Editor>,
18986 ) -> Task<Result<Option<language::Transaction>>> {
18987 Task::ready(Ok(None))
18988 }
18989
18990 fn is_completion_trigger(
18991 &self,
18992 buffer: &Entity<Buffer>,
18993 position: language::Anchor,
18994 text: &str,
18995 trigger_in_words: bool,
18996 cx: &mut Context<Editor>,
18997 ) -> bool;
18998
18999 fn sort_completions(&self) -> bool {
19000 true
19001 }
19002
19003 fn filter_completions(&self) -> bool {
19004 true
19005 }
19006}
19007
19008pub trait CodeActionProvider {
19009 fn id(&self) -> Arc<str>;
19010
19011 fn code_actions(
19012 &self,
19013 buffer: &Entity<Buffer>,
19014 range: Range<text::Anchor>,
19015 window: &mut Window,
19016 cx: &mut App,
19017 ) -> Task<Result<Vec<CodeAction>>>;
19018
19019 fn apply_code_action(
19020 &self,
19021 buffer_handle: Entity<Buffer>,
19022 action: CodeAction,
19023 excerpt_id: ExcerptId,
19024 push_to_history: bool,
19025 window: &mut Window,
19026 cx: &mut App,
19027 ) -> Task<Result<ProjectTransaction>>;
19028}
19029
19030impl CodeActionProvider for Entity<Project> {
19031 fn id(&self) -> Arc<str> {
19032 "project".into()
19033 }
19034
19035 fn code_actions(
19036 &self,
19037 buffer: &Entity<Buffer>,
19038 range: Range<text::Anchor>,
19039 _window: &mut Window,
19040 cx: &mut App,
19041 ) -> Task<Result<Vec<CodeAction>>> {
19042 self.update(cx, |project, cx| {
19043 let code_lens = project.code_lens(buffer, range.clone(), cx);
19044 let code_actions = project.code_actions(buffer, range, None, cx);
19045 cx.background_spawn(async move {
19046 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19047 Ok(code_lens
19048 .context("code lens fetch")?
19049 .into_iter()
19050 .chain(code_actions.context("code action fetch")?)
19051 .collect())
19052 })
19053 })
19054 }
19055
19056 fn apply_code_action(
19057 &self,
19058 buffer_handle: Entity<Buffer>,
19059 action: CodeAction,
19060 _excerpt_id: ExcerptId,
19061 push_to_history: bool,
19062 _window: &mut Window,
19063 cx: &mut App,
19064 ) -> Task<Result<ProjectTransaction>> {
19065 self.update(cx, |project, cx| {
19066 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19067 })
19068 }
19069}
19070
19071fn snippet_completions(
19072 project: &Project,
19073 buffer: &Entity<Buffer>,
19074 buffer_position: text::Anchor,
19075 cx: &mut App,
19076) -> Task<Result<Vec<Completion>>> {
19077 let languages = buffer.read(cx).languages_at(buffer_position);
19078 let snippet_store = project.snippets().read(cx);
19079
19080 let scopes: Vec<_> = languages
19081 .iter()
19082 .filter_map(|language| {
19083 let language_name = language.lsp_id();
19084 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19085
19086 if snippets.is_empty() {
19087 None
19088 } else {
19089 Some((language.default_scope(), snippets))
19090 }
19091 })
19092 .collect();
19093
19094 if scopes.is_empty() {
19095 return Task::ready(Ok(vec![]));
19096 }
19097
19098 let snapshot = buffer.read(cx).text_snapshot();
19099 let chars: String = snapshot
19100 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19101 .collect();
19102 let executor = cx.background_executor().clone();
19103
19104 cx.background_spawn(async move {
19105 let mut all_results: Vec<Completion> = Vec::new();
19106 for (scope, snippets) in scopes.into_iter() {
19107 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19108 let mut last_word = chars
19109 .chars()
19110 .take_while(|c| classifier.is_word(*c))
19111 .collect::<String>();
19112 last_word = last_word.chars().rev().collect();
19113
19114 if last_word.is_empty() {
19115 return Ok(vec![]);
19116 }
19117
19118 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19119 let to_lsp = |point: &text::Anchor| {
19120 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19121 point_to_lsp(end)
19122 };
19123 let lsp_end = to_lsp(&buffer_position);
19124
19125 let candidates = snippets
19126 .iter()
19127 .enumerate()
19128 .flat_map(|(ix, snippet)| {
19129 snippet
19130 .prefix
19131 .iter()
19132 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19133 })
19134 .collect::<Vec<StringMatchCandidate>>();
19135
19136 let mut matches = fuzzy::match_strings(
19137 &candidates,
19138 &last_word,
19139 last_word.chars().any(|c| c.is_uppercase()),
19140 100,
19141 &Default::default(),
19142 executor.clone(),
19143 )
19144 .await;
19145
19146 // Remove all candidates where the query's start does not match the start of any word in the candidate
19147 if let Some(query_start) = last_word.chars().next() {
19148 matches.retain(|string_match| {
19149 split_words(&string_match.string).any(|word| {
19150 // Check that the first codepoint of the word as lowercase matches the first
19151 // codepoint of the query as lowercase
19152 word.chars()
19153 .flat_map(|codepoint| codepoint.to_lowercase())
19154 .zip(query_start.to_lowercase())
19155 .all(|(word_cp, query_cp)| word_cp == query_cp)
19156 })
19157 });
19158 }
19159
19160 let matched_strings = matches
19161 .into_iter()
19162 .map(|m| m.string)
19163 .collect::<HashSet<_>>();
19164
19165 let mut result: Vec<Completion> = snippets
19166 .iter()
19167 .filter_map(|snippet| {
19168 let matching_prefix = snippet
19169 .prefix
19170 .iter()
19171 .find(|prefix| matched_strings.contains(*prefix))?;
19172 let start = as_offset - last_word.len();
19173 let start = snapshot.anchor_before(start);
19174 let range = start..buffer_position;
19175 let lsp_start = to_lsp(&start);
19176 let lsp_range = lsp::Range {
19177 start: lsp_start,
19178 end: lsp_end,
19179 };
19180 Some(Completion {
19181 replace_range: range,
19182 new_text: snippet.body.clone(),
19183 source: CompletionSource::Lsp {
19184 insert_range: None,
19185 server_id: LanguageServerId(usize::MAX),
19186 resolved: true,
19187 lsp_completion: Box::new(lsp::CompletionItem {
19188 label: snippet.prefix.first().unwrap().clone(),
19189 kind: Some(CompletionItemKind::SNIPPET),
19190 label_details: snippet.description.as_ref().map(|description| {
19191 lsp::CompletionItemLabelDetails {
19192 detail: Some(description.clone()),
19193 description: None,
19194 }
19195 }),
19196 insert_text_format: Some(InsertTextFormat::SNIPPET),
19197 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19198 lsp::InsertReplaceEdit {
19199 new_text: snippet.body.clone(),
19200 insert: lsp_range,
19201 replace: lsp_range,
19202 },
19203 )),
19204 filter_text: Some(snippet.body.clone()),
19205 sort_text: Some(char::MAX.to_string()),
19206 ..lsp::CompletionItem::default()
19207 }),
19208 lsp_defaults: None,
19209 },
19210 label: CodeLabel {
19211 text: matching_prefix.clone(),
19212 runs: Vec::new(),
19213 filter_range: 0..matching_prefix.len(),
19214 },
19215 icon_path: None,
19216 documentation: snippet.description.clone().map(|description| {
19217 CompletionDocumentation::SingleLine(description.into())
19218 }),
19219 insert_text_mode: None,
19220 confirm: None,
19221 })
19222 })
19223 .collect();
19224
19225 all_results.append(&mut result);
19226 }
19227
19228 Ok(all_results)
19229 })
19230}
19231
19232impl CompletionProvider for Entity<Project> {
19233 fn completions(
19234 &self,
19235 _excerpt_id: ExcerptId,
19236 buffer: &Entity<Buffer>,
19237 buffer_position: text::Anchor,
19238 options: CompletionContext,
19239 _window: &mut Window,
19240 cx: &mut Context<Editor>,
19241 ) -> Task<Result<Option<Vec<Completion>>>> {
19242 self.update(cx, |project, cx| {
19243 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19244 let project_completions = project.completions(buffer, buffer_position, options, cx);
19245 cx.background_spawn(async move {
19246 let snippets_completions = snippets.await?;
19247 match project_completions.await? {
19248 Some(mut completions) => {
19249 completions.extend(snippets_completions);
19250 Ok(Some(completions))
19251 }
19252 None => {
19253 if snippets_completions.is_empty() {
19254 Ok(None)
19255 } else {
19256 Ok(Some(snippets_completions))
19257 }
19258 }
19259 }
19260 })
19261 })
19262 }
19263
19264 fn resolve_completions(
19265 &self,
19266 buffer: Entity<Buffer>,
19267 completion_indices: Vec<usize>,
19268 completions: Rc<RefCell<Box<[Completion]>>>,
19269 cx: &mut Context<Editor>,
19270 ) -> Task<Result<bool>> {
19271 self.update(cx, |project, cx| {
19272 project.lsp_store().update(cx, |lsp_store, cx| {
19273 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19274 })
19275 })
19276 }
19277
19278 fn apply_additional_edits_for_completion(
19279 &self,
19280 buffer: Entity<Buffer>,
19281 completions: Rc<RefCell<Box<[Completion]>>>,
19282 completion_index: usize,
19283 push_to_history: bool,
19284 cx: &mut Context<Editor>,
19285 ) -> Task<Result<Option<language::Transaction>>> {
19286 self.update(cx, |project, cx| {
19287 project.lsp_store().update(cx, |lsp_store, cx| {
19288 lsp_store.apply_additional_edits_for_completion(
19289 buffer,
19290 completions,
19291 completion_index,
19292 push_to_history,
19293 cx,
19294 )
19295 })
19296 })
19297 }
19298
19299 fn is_completion_trigger(
19300 &self,
19301 buffer: &Entity<Buffer>,
19302 position: language::Anchor,
19303 text: &str,
19304 trigger_in_words: bool,
19305 cx: &mut Context<Editor>,
19306 ) -> bool {
19307 let mut chars = text.chars();
19308 let char = if let Some(char) = chars.next() {
19309 char
19310 } else {
19311 return false;
19312 };
19313 if chars.next().is_some() {
19314 return false;
19315 }
19316
19317 let buffer = buffer.read(cx);
19318 let snapshot = buffer.snapshot();
19319 if !snapshot.settings_at(position, cx).show_completions_on_input {
19320 return false;
19321 }
19322 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19323 if trigger_in_words && classifier.is_word(char) {
19324 return true;
19325 }
19326
19327 buffer.completion_triggers().contains(text)
19328 }
19329}
19330
19331impl SemanticsProvider for Entity<Project> {
19332 fn hover(
19333 &self,
19334 buffer: &Entity<Buffer>,
19335 position: text::Anchor,
19336 cx: &mut App,
19337 ) -> Option<Task<Vec<project::Hover>>> {
19338 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19339 }
19340
19341 fn document_highlights(
19342 &self,
19343 buffer: &Entity<Buffer>,
19344 position: text::Anchor,
19345 cx: &mut App,
19346 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19347 Some(self.update(cx, |project, cx| {
19348 project.document_highlights(buffer, position, cx)
19349 }))
19350 }
19351
19352 fn definitions(
19353 &self,
19354 buffer: &Entity<Buffer>,
19355 position: text::Anchor,
19356 kind: GotoDefinitionKind,
19357 cx: &mut App,
19358 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19359 Some(self.update(cx, |project, cx| match kind {
19360 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19361 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19362 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19363 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19364 }))
19365 }
19366
19367 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19368 // TODO: make this work for remote projects
19369 self.update(cx, |this, cx| {
19370 buffer.update(cx, |buffer, cx| {
19371 this.any_language_server_supports_inlay_hints(buffer, cx)
19372 })
19373 })
19374 }
19375
19376 fn inlay_hints(
19377 &self,
19378 buffer_handle: Entity<Buffer>,
19379 range: Range<text::Anchor>,
19380 cx: &mut App,
19381 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19382 Some(self.update(cx, |project, cx| {
19383 project.inlay_hints(buffer_handle, range, cx)
19384 }))
19385 }
19386
19387 fn resolve_inlay_hint(
19388 &self,
19389 hint: InlayHint,
19390 buffer_handle: Entity<Buffer>,
19391 server_id: LanguageServerId,
19392 cx: &mut App,
19393 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19394 Some(self.update(cx, |project, cx| {
19395 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19396 }))
19397 }
19398
19399 fn range_for_rename(
19400 &self,
19401 buffer: &Entity<Buffer>,
19402 position: text::Anchor,
19403 cx: &mut App,
19404 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19405 Some(self.update(cx, |project, cx| {
19406 let buffer = buffer.clone();
19407 let task = project.prepare_rename(buffer.clone(), position, cx);
19408 cx.spawn(async move |_, cx| {
19409 Ok(match task.await? {
19410 PrepareRenameResponse::Success(range) => Some(range),
19411 PrepareRenameResponse::InvalidPosition => None,
19412 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19413 // Fallback on using TreeSitter info to determine identifier range
19414 buffer.update(cx, |buffer, _| {
19415 let snapshot = buffer.snapshot();
19416 let (range, kind) = snapshot.surrounding_word(position);
19417 if kind != Some(CharKind::Word) {
19418 return None;
19419 }
19420 Some(
19421 snapshot.anchor_before(range.start)
19422 ..snapshot.anchor_after(range.end),
19423 )
19424 })?
19425 }
19426 })
19427 })
19428 }))
19429 }
19430
19431 fn perform_rename(
19432 &self,
19433 buffer: &Entity<Buffer>,
19434 position: text::Anchor,
19435 new_name: String,
19436 cx: &mut App,
19437 ) -> Option<Task<Result<ProjectTransaction>>> {
19438 Some(self.update(cx, |project, cx| {
19439 project.perform_rename(buffer.clone(), position, new_name, cx)
19440 }))
19441 }
19442}
19443
19444fn inlay_hint_settings(
19445 location: Anchor,
19446 snapshot: &MultiBufferSnapshot,
19447 cx: &mut Context<Editor>,
19448) -> InlayHintSettings {
19449 let file = snapshot.file_at(location);
19450 let language = snapshot.language_at(location).map(|l| l.name());
19451 language_settings(language, file, cx).inlay_hints
19452}
19453
19454fn consume_contiguous_rows(
19455 contiguous_row_selections: &mut Vec<Selection<Point>>,
19456 selection: &Selection<Point>,
19457 display_map: &DisplaySnapshot,
19458 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19459) -> (MultiBufferRow, MultiBufferRow) {
19460 contiguous_row_selections.push(selection.clone());
19461 let start_row = MultiBufferRow(selection.start.row);
19462 let mut end_row = ending_row(selection, display_map);
19463
19464 while let Some(next_selection) = selections.peek() {
19465 if next_selection.start.row <= end_row.0 {
19466 end_row = ending_row(next_selection, display_map);
19467 contiguous_row_selections.push(selections.next().unwrap().clone());
19468 } else {
19469 break;
19470 }
19471 }
19472 (start_row, end_row)
19473}
19474
19475fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19476 if next_selection.end.column > 0 || next_selection.is_empty() {
19477 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19478 } else {
19479 MultiBufferRow(next_selection.end.row)
19480 }
19481}
19482
19483impl EditorSnapshot {
19484 pub fn remote_selections_in_range<'a>(
19485 &'a self,
19486 range: &'a Range<Anchor>,
19487 collaboration_hub: &dyn CollaborationHub,
19488 cx: &'a App,
19489 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19490 let participant_names = collaboration_hub.user_names(cx);
19491 let participant_indices = collaboration_hub.user_participant_indices(cx);
19492 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19493 let collaborators_by_replica_id = collaborators_by_peer_id
19494 .iter()
19495 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19496 .collect::<HashMap<_, _>>();
19497 self.buffer_snapshot
19498 .selections_in_range(range, false)
19499 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19500 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19501 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19502 let user_name = participant_names.get(&collaborator.user_id).cloned();
19503 Some(RemoteSelection {
19504 replica_id,
19505 selection,
19506 cursor_shape,
19507 line_mode,
19508 participant_index,
19509 peer_id: collaborator.peer_id,
19510 user_name,
19511 })
19512 })
19513 }
19514
19515 pub fn hunks_for_ranges(
19516 &self,
19517 ranges: impl IntoIterator<Item = Range<Point>>,
19518 ) -> Vec<MultiBufferDiffHunk> {
19519 let mut hunks = Vec::new();
19520 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19521 HashMap::default();
19522 for query_range in ranges {
19523 let query_rows =
19524 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19525 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19526 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19527 ) {
19528 // Include deleted hunks that are adjacent to the query range, because
19529 // otherwise they would be missed.
19530 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19531 if hunk.status().is_deleted() {
19532 intersects_range |= hunk.row_range.start == query_rows.end;
19533 intersects_range |= hunk.row_range.end == query_rows.start;
19534 }
19535 if intersects_range {
19536 if !processed_buffer_rows
19537 .entry(hunk.buffer_id)
19538 .or_default()
19539 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19540 {
19541 continue;
19542 }
19543 hunks.push(hunk);
19544 }
19545 }
19546 }
19547
19548 hunks
19549 }
19550
19551 fn display_diff_hunks_for_rows<'a>(
19552 &'a self,
19553 display_rows: Range<DisplayRow>,
19554 folded_buffers: &'a HashSet<BufferId>,
19555 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19556 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19557 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19558
19559 self.buffer_snapshot
19560 .diff_hunks_in_range(buffer_start..buffer_end)
19561 .filter_map(|hunk| {
19562 if folded_buffers.contains(&hunk.buffer_id) {
19563 return None;
19564 }
19565
19566 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19567 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19568
19569 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19570 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19571
19572 let display_hunk = if hunk_display_start.column() != 0 {
19573 DisplayDiffHunk::Folded {
19574 display_row: hunk_display_start.row(),
19575 }
19576 } else {
19577 let mut end_row = hunk_display_end.row();
19578 if hunk_display_end.column() > 0 {
19579 end_row.0 += 1;
19580 }
19581 let is_created_file = hunk.is_created_file();
19582 DisplayDiffHunk::Unfolded {
19583 status: hunk.status(),
19584 diff_base_byte_range: hunk.diff_base_byte_range,
19585 display_row_range: hunk_display_start.row()..end_row,
19586 multi_buffer_range: Anchor::range_in_buffer(
19587 hunk.excerpt_id,
19588 hunk.buffer_id,
19589 hunk.buffer_range,
19590 ),
19591 is_created_file,
19592 }
19593 };
19594
19595 Some(display_hunk)
19596 })
19597 }
19598
19599 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19600 self.display_snapshot.buffer_snapshot.language_at(position)
19601 }
19602
19603 pub fn is_focused(&self) -> bool {
19604 self.is_focused
19605 }
19606
19607 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19608 self.placeholder_text.as_ref()
19609 }
19610
19611 pub fn scroll_position(&self) -> gpui::Point<f32> {
19612 self.scroll_anchor.scroll_position(&self.display_snapshot)
19613 }
19614
19615 fn gutter_dimensions(
19616 &self,
19617 font_id: FontId,
19618 font_size: Pixels,
19619 max_line_number_width: Pixels,
19620 cx: &App,
19621 ) -> Option<GutterDimensions> {
19622 if !self.show_gutter {
19623 return None;
19624 }
19625
19626 let descent = cx.text_system().descent(font_id, font_size);
19627 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19628 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19629
19630 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19631 matches!(
19632 ProjectSettings::get_global(cx).git.git_gutter,
19633 Some(GitGutterSetting::TrackedFiles)
19634 )
19635 });
19636 let gutter_settings = EditorSettings::get_global(cx).gutter;
19637 let show_line_numbers = self
19638 .show_line_numbers
19639 .unwrap_or(gutter_settings.line_numbers);
19640 let line_gutter_width = if show_line_numbers {
19641 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19642 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19643 max_line_number_width.max(min_width_for_number_on_gutter)
19644 } else {
19645 0.0.into()
19646 };
19647
19648 let show_code_actions = self
19649 .show_code_actions
19650 .unwrap_or(gutter_settings.code_actions);
19651
19652 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19653 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19654
19655 let git_blame_entries_width =
19656 self.git_blame_gutter_max_author_length
19657 .map(|max_author_length| {
19658 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19659 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19660
19661 /// The number of characters to dedicate to gaps and margins.
19662 const SPACING_WIDTH: usize = 4;
19663
19664 let max_char_count = max_author_length.min(renderer.max_author_length())
19665 + ::git::SHORT_SHA_LENGTH
19666 + MAX_RELATIVE_TIMESTAMP.len()
19667 + SPACING_WIDTH;
19668
19669 em_advance * max_char_count
19670 });
19671
19672 let is_singleton = self.buffer_snapshot.is_singleton();
19673
19674 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19675 left_padding += if !is_singleton {
19676 em_width * 4.0
19677 } else if show_code_actions || show_runnables || show_breakpoints {
19678 em_width * 3.0
19679 } else if show_git_gutter && show_line_numbers {
19680 em_width * 2.0
19681 } else if show_git_gutter || show_line_numbers {
19682 em_width
19683 } else {
19684 px(0.)
19685 };
19686
19687 let shows_folds = is_singleton && gutter_settings.folds;
19688
19689 let right_padding = if shows_folds && show_line_numbers {
19690 em_width * 4.0
19691 } else if shows_folds || (!is_singleton && show_line_numbers) {
19692 em_width * 3.0
19693 } else if show_line_numbers {
19694 em_width
19695 } else {
19696 px(0.)
19697 };
19698
19699 Some(GutterDimensions {
19700 left_padding,
19701 right_padding,
19702 width: line_gutter_width + left_padding + right_padding,
19703 margin: -descent,
19704 git_blame_entries_width,
19705 })
19706 }
19707
19708 pub fn render_crease_toggle(
19709 &self,
19710 buffer_row: MultiBufferRow,
19711 row_contains_cursor: bool,
19712 editor: Entity<Editor>,
19713 window: &mut Window,
19714 cx: &mut App,
19715 ) -> Option<AnyElement> {
19716 let folded = self.is_line_folded(buffer_row);
19717 let mut is_foldable = false;
19718
19719 if let Some(crease) = self
19720 .crease_snapshot
19721 .query_row(buffer_row, &self.buffer_snapshot)
19722 {
19723 is_foldable = true;
19724 match crease {
19725 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19726 if let Some(render_toggle) = render_toggle {
19727 let toggle_callback =
19728 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19729 if folded {
19730 editor.update(cx, |editor, cx| {
19731 editor.fold_at(buffer_row, window, cx)
19732 });
19733 } else {
19734 editor.update(cx, |editor, cx| {
19735 editor.unfold_at(buffer_row, window, cx)
19736 });
19737 }
19738 });
19739 return Some((render_toggle)(
19740 buffer_row,
19741 folded,
19742 toggle_callback,
19743 window,
19744 cx,
19745 ));
19746 }
19747 }
19748 }
19749 }
19750
19751 is_foldable |= self.starts_indent(buffer_row);
19752
19753 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19754 Some(
19755 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19756 .toggle_state(folded)
19757 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19758 if folded {
19759 this.unfold_at(buffer_row, window, cx);
19760 } else {
19761 this.fold_at(buffer_row, window, cx);
19762 }
19763 }))
19764 .into_any_element(),
19765 )
19766 } else {
19767 None
19768 }
19769 }
19770
19771 pub fn render_crease_trailer(
19772 &self,
19773 buffer_row: MultiBufferRow,
19774 window: &mut Window,
19775 cx: &mut App,
19776 ) -> Option<AnyElement> {
19777 let folded = self.is_line_folded(buffer_row);
19778 if let Crease::Inline { render_trailer, .. } = self
19779 .crease_snapshot
19780 .query_row(buffer_row, &self.buffer_snapshot)?
19781 {
19782 let render_trailer = render_trailer.as_ref()?;
19783 Some(render_trailer(buffer_row, folded, window, cx))
19784 } else {
19785 None
19786 }
19787 }
19788}
19789
19790impl Deref for EditorSnapshot {
19791 type Target = DisplaySnapshot;
19792
19793 fn deref(&self) -> &Self::Target {
19794 &self.display_snapshot
19795 }
19796}
19797
19798#[derive(Clone, Debug, PartialEq, Eq)]
19799pub enum EditorEvent {
19800 InputIgnored {
19801 text: Arc<str>,
19802 },
19803 InputHandled {
19804 utf16_range_to_replace: Option<Range<isize>>,
19805 text: Arc<str>,
19806 },
19807 ExcerptsAdded {
19808 buffer: Entity<Buffer>,
19809 predecessor: ExcerptId,
19810 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19811 },
19812 ExcerptsRemoved {
19813 ids: Vec<ExcerptId>,
19814 removed_buffer_ids: Vec<BufferId>,
19815 },
19816 BufferFoldToggled {
19817 ids: Vec<ExcerptId>,
19818 folded: bool,
19819 },
19820 ExcerptsEdited {
19821 ids: Vec<ExcerptId>,
19822 },
19823 ExcerptsExpanded {
19824 ids: Vec<ExcerptId>,
19825 },
19826 BufferEdited,
19827 Edited {
19828 transaction_id: clock::Lamport,
19829 },
19830 Reparsed(BufferId),
19831 Focused,
19832 FocusedIn,
19833 Blurred,
19834 DirtyChanged,
19835 Saved,
19836 TitleChanged,
19837 DiffBaseChanged,
19838 SelectionsChanged {
19839 local: bool,
19840 },
19841 ScrollPositionChanged {
19842 local: bool,
19843 autoscroll: bool,
19844 },
19845 Closed,
19846 TransactionUndone {
19847 transaction_id: clock::Lamport,
19848 },
19849 TransactionBegun {
19850 transaction_id: clock::Lamport,
19851 },
19852 Reloaded,
19853 CursorShapeChanged,
19854 PushedToNavHistory {
19855 anchor: Anchor,
19856 is_deactivate: bool,
19857 },
19858}
19859
19860impl EventEmitter<EditorEvent> for Editor {}
19861
19862impl Focusable for Editor {
19863 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19864 self.focus_handle.clone()
19865 }
19866}
19867
19868impl Render for Editor {
19869 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19870 let settings = ThemeSettings::get_global(cx);
19871
19872 let mut text_style = match self.mode {
19873 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19874 color: cx.theme().colors().editor_foreground,
19875 font_family: settings.ui_font.family.clone(),
19876 font_features: settings.ui_font.features.clone(),
19877 font_fallbacks: settings.ui_font.fallbacks.clone(),
19878 font_size: rems(0.875).into(),
19879 font_weight: settings.ui_font.weight,
19880 line_height: relative(settings.buffer_line_height.value()),
19881 ..Default::default()
19882 },
19883 EditorMode::Full { .. } => TextStyle {
19884 color: cx.theme().colors().editor_foreground,
19885 font_family: settings.buffer_font.family.clone(),
19886 font_features: settings.buffer_font.features.clone(),
19887 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19888 font_size: settings.buffer_font_size(cx).into(),
19889 font_weight: settings.buffer_font.weight,
19890 line_height: relative(settings.buffer_line_height.value()),
19891 ..Default::default()
19892 },
19893 };
19894 if let Some(text_style_refinement) = &self.text_style_refinement {
19895 text_style.refine(text_style_refinement)
19896 }
19897
19898 let background = match self.mode {
19899 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19900 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19901 EditorMode::Full { .. } => cx.theme().colors().editor_background,
19902 };
19903
19904 EditorElement::new(
19905 &cx.entity(),
19906 EditorStyle {
19907 background,
19908 local_player: cx.theme().players().local(),
19909 text: text_style,
19910 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19911 syntax: cx.theme().syntax().clone(),
19912 status: cx.theme().status().clone(),
19913 inlay_hints_style: make_inlay_hints_style(cx),
19914 inline_completion_styles: make_suggestion_styles(cx),
19915 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19916 },
19917 )
19918 }
19919}
19920
19921impl EntityInputHandler for Editor {
19922 fn text_for_range(
19923 &mut self,
19924 range_utf16: Range<usize>,
19925 adjusted_range: &mut Option<Range<usize>>,
19926 _: &mut Window,
19927 cx: &mut Context<Self>,
19928 ) -> Option<String> {
19929 let snapshot = self.buffer.read(cx).read(cx);
19930 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19931 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19932 if (start.0..end.0) != range_utf16 {
19933 adjusted_range.replace(start.0..end.0);
19934 }
19935 Some(snapshot.text_for_range(start..end).collect())
19936 }
19937
19938 fn selected_text_range(
19939 &mut self,
19940 ignore_disabled_input: bool,
19941 _: &mut Window,
19942 cx: &mut Context<Self>,
19943 ) -> Option<UTF16Selection> {
19944 // Prevent the IME menu from appearing when holding down an alphabetic key
19945 // while input is disabled.
19946 if !ignore_disabled_input && !self.input_enabled {
19947 return None;
19948 }
19949
19950 let selection = self.selections.newest::<OffsetUtf16>(cx);
19951 let range = selection.range();
19952
19953 Some(UTF16Selection {
19954 range: range.start.0..range.end.0,
19955 reversed: selection.reversed,
19956 })
19957 }
19958
19959 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19960 let snapshot = self.buffer.read(cx).read(cx);
19961 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19962 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19963 }
19964
19965 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19966 self.clear_highlights::<InputComposition>(cx);
19967 self.ime_transaction.take();
19968 }
19969
19970 fn replace_text_in_range(
19971 &mut self,
19972 range_utf16: Option<Range<usize>>,
19973 text: &str,
19974 window: &mut Window,
19975 cx: &mut Context<Self>,
19976 ) {
19977 if !self.input_enabled {
19978 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19979 return;
19980 }
19981
19982 self.transact(window, cx, |this, window, cx| {
19983 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19984 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19985 Some(this.selection_replacement_ranges(range_utf16, cx))
19986 } else {
19987 this.marked_text_ranges(cx)
19988 };
19989
19990 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19991 let newest_selection_id = this.selections.newest_anchor().id;
19992 this.selections
19993 .all::<OffsetUtf16>(cx)
19994 .iter()
19995 .zip(ranges_to_replace.iter())
19996 .find_map(|(selection, range)| {
19997 if selection.id == newest_selection_id {
19998 Some(
19999 (range.start.0 as isize - selection.head().0 as isize)
20000 ..(range.end.0 as isize - selection.head().0 as isize),
20001 )
20002 } else {
20003 None
20004 }
20005 })
20006 });
20007
20008 cx.emit(EditorEvent::InputHandled {
20009 utf16_range_to_replace: range_to_replace,
20010 text: text.into(),
20011 });
20012
20013 if let Some(new_selected_ranges) = new_selected_ranges {
20014 this.change_selections(None, window, cx, |selections| {
20015 selections.select_ranges(new_selected_ranges)
20016 });
20017 this.backspace(&Default::default(), window, cx);
20018 }
20019
20020 this.handle_input(text, window, cx);
20021 });
20022
20023 if let Some(transaction) = self.ime_transaction {
20024 self.buffer.update(cx, |buffer, cx| {
20025 buffer.group_until_transaction(transaction, cx);
20026 });
20027 }
20028
20029 self.unmark_text(window, cx);
20030 }
20031
20032 fn replace_and_mark_text_in_range(
20033 &mut self,
20034 range_utf16: Option<Range<usize>>,
20035 text: &str,
20036 new_selected_range_utf16: Option<Range<usize>>,
20037 window: &mut Window,
20038 cx: &mut Context<Self>,
20039 ) {
20040 if !self.input_enabled {
20041 return;
20042 }
20043
20044 let transaction = self.transact(window, cx, |this, window, cx| {
20045 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20046 let snapshot = this.buffer.read(cx).read(cx);
20047 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20048 for marked_range in &mut marked_ranges {
20049 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20050 marked_range.start.0 += relative_range_utf16.start;
20051 marked_range.start =
20052 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20053 marked_range.end =
20054 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20055 }
20056 }
20057 Some(marked_ranges)
20058 } else if let Some(range_utf16) = range_utf16 {
20059 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20060 Some(this.selection_replacement_ranges(range_utf16, cx))
20061 } else {
20062 None
20063 };
20064
20065 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20066 let newest_selection_id = this.selections.newest_anchor().id;
20067 this.selections
20068 .all::<OffsetUtf16>(cx)
20069 .iter()
20070 .zip(ranges_to_replace.iter())
20071 .find_map(|(selection, range)| {
20072 if selection.id == newest_selection_id {
20073 Some(
20074 (range.start.0 as isize - selection.head().0 as isize)
20075 ..(range.end.0 as isize - selection.head().0 as isize),
20076 )
20077 } else {
20078 None
20079 }
20080 })
20081 });
20082
20083 cx.emit(EditorEvent::InputHandled {
20084 utf16_range_to_replace: range_to_replace,
20085 text: text.into(),
20086 });
20087
20088 if let Some(ranges) = ranges_to_replace {
20089 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20090 }
20091
20092 let marked_ranges = {
20093 let snapshot = this.buffer.read(cx).read(cx);
20094 this.selections
20095 .disjoint_anchors()
20096 .iter()
20097 .map(|selection| {
20098 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20099 })
20100 .collect::<Vec<_>>()
20101 };
20102
20103 if text.is_empty() {
20104 this.unmark_text(window, cx);
20105 } else {
20106 this.highlight_text::<InputComposition>(
20107 marked_ranges.clone(),
20108 HighlightStyle {
20109 underline: Some(UnderlineStyle {
20110 thickness: px(1.),
20111 color: None,
20112 wavy: false,
20113 }),
20114 ..Default::default()
20115 },
20116 cx,
20117 );
20118 }
20119
20120 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20121 let use_autoclose = this.use_autoclose;
20122 let use_auto_surround = this.use_auto_surround;
20123 this.set_use_autoclose(false);
20124 this.set_use_auto_surround(false);
20125 this.handle_input(text, window, cx);
20126 this.set_use_autoclose(use_autoclose);
20127 this.set_use_auto_surround(use_auto_surround);
20128
20129 if let Some(new_selected_range) = new_selected_range_utf16 {
20130 let snapshot = this.buffer.read(cx).read(cx);
20131 let new_selected_ranges = marked_ranges
20132 .into_iter()
20133 .map(|marked_range| {
20134 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20135 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20136 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20137 snapshot.clip_offset_utf16(new_start, Bias::Left)
20138 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20139 })
20140 .collect::<Vec<_>>();
20141
20142 drop(snapshot);
20143 this.change_selections(None, window, cx, |selections| {
20144 selections.select_ranges(new_selected_ranges)
20145 });
20146 }
20147 });
20148
20149 self.ime_transaction = self.ime_transaction.or(transaction);
20150 if let Some(transaction) = self.ime_transaction {
20151 self.buffer.update(cx, |buffer, cx| {
20152 buffer.group_until_transaction(transaction, cx);
20153 });
20154 }
20155
20156 if self.text_highlights::<InputComposition>(cx).is_none() {
20157 self.ime_transaction.take();
20158 }
20159 }
20160
20161 fn bounds_for_range(
20162 &mut self,
20163 range_utf16: Range<usize>,
20164 element_bounds: gpui::Bounds<Pixels>,
20165 window: &mut Window,
20166 cx: &mut Context<Self>,
20167 ) -> Option<gpui::Bounds<Pixels>> {
20168 let text_layout_details = self.text_layout_details(window);
20169 let gpui::Size {
20170 width: em_width,
20171 height: line_height,
20172 } = self.character_size(window);
20173
20174 let snapshot = self.snapshot(window, cx);
20175 let scroll_position = snapshot.scroll_position();
20176 let scroll_left = scroll_position.x * em_width;
20177
20178 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20179 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20180 + self.gutter_dimensions.width
20181 + self.gutter_dimensions.margin;
20182 let y = line_height * (start.row().as_f32() - scroll_position.y);
20183
20184 Some(Bounds {
20185 origin: element_bounds.origin + point(x, y),
20186 size: size(em_width, line_height),
20187 })
20188 }
20189
20190 fn character_index_for_point(
20191 &mut self,
20192 point: gpui::Point<Pixels>,
20193 _window: &mut Window,
20194 _cx: &mut Context<Self>,
20195 ) -> Option<usize> {
20196 let position_map = self.last_position_map.as_ref()?;
20197 if !position_map.text_hitbox.contains(&point) {
20198 return None;
20199 }
20200 let display_point = position_map.point_for_position(point).previous_valid;
20201 let anchor = position_map
20202 .snapshot
20203 .display_point_to_anchor(display_point, Bias::Left);
20204 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20205 Some(utf16_offset.0)
20206 }
20207}
20208
20209trait SelectionExt {
20210 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20211 fn spanned_rows(
20212 &self,
20213 include_end_if_at_line_start: bool,
20214 map: &DisplaySnapshot,
20215 ) -> Range<MultiBufferRow>;
20216}
20217
20218impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20219 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20220 let start = self
20221 .start
20222 .to_point(&map.buffer_snapshot)
20223 .to_display_point(map);
20224 let end = self
20225 .end
20226 .to_point(&map.buffer_snapshot)
20227 .to_display_point(map);
20228 if self.reversed {
20229 end..start
20230 } else {
20231 start..end
20232 }
20233 }
20234
20235 fn spanned_rows(
20236 &self,
20237 include_end_if_at_line_start: bool,
20238 map: &DisplaySnapshot,
20239 ) -> Range<MultiBufferRow> {
20240 let start = self.start.to_point(&map.buffer_snapshot);
20241 let mut end = self.end.to_point(&map.buffer_snapshot);
20242 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20243 end.row -= 1;
20244 }
20245
20246 let buffer_start = map.prev_line_boundary(start).0;
20247 let buffer_end = map.next_line_boundary(end).0;
20248 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20249 }
20250}
20251
20252impl<T: InvalidationRegion> InvalidationStack<T> {
20253 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20254 where
20255 S: Clone + ToOffset,
20256 {
20257 while let Some(region) = self.last() {
20258 let all_selections_inside_invalidation_ranges =
20259 if selections.len() == region.ranges().len() {
20260 selections
20261 .iter()
20262 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20263 .all(|(selection, invalidation_range)| {
20264 let head = selection.head().to_offset(buffer);
20265 invalidation_range.start <= head && invalidation_range.end >= head
20266 })
20267 } else {
20268 false
20269 };
20270
20271 if all_selections_inside_invalidation_ranges {
20272 break;
20273 } else {
20274 self.pop();
20275 }
20276 }
20277 }
20278}
20279
20280impl<T> Default for InvalidationStack<T> {
20281 fn default() -> Self {
20282 Self(Default::default())
20283 }
20284}
20285
20286impl<T> Deref for InvalidationStack<T> {
20287 type Target = Vec<T>;
20288
20289 fn deref(&self) -> &Self::Target {
20290 &self.0
20291 }
20292}
20293
20294impl<T> DerefMut for InvalidationStack<T> {
20295 fn deref_mut(&mut self) -> &mut Self::Target {
20296 &mut self.0
20297 }
20298}
20299
20300impl InvalidationRegion for SnippetState {
20301 fn ranges(&self) -> &[Range<Anchor>] {
20302 &self.ranges[self.active_index]
20303 }
20304}
20305
20306fn inline_completion_edit_text(
20307 current_snapshot: &BufferSnapshot,
20308 edits: &[(Range<Anchor>, String)],
20309 edit_preview: &EditPreview,
20310 include_deletions: bool,
20311 cx: &App,
20312) -> HighlightedText {
20313 let edits = edits
20314 .iter()
20315 .map(|(anchor, text)| {
20316 (
20317 anchor.start.text_anchor..anchor.end.text_anchor,
20318 text.clone(),
20319 )
20320 })
20321 .collect::<Vec<_>>();
20322
20323 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20324}
20325
20326pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20327 match severity {
20328 DiagnosticSeverity::ERROR => colors.error,
20329 DiagnosticSeverity::WARNING => colors.warning,
20330 DiagnosticSeverity::INFORMATION => colors.info,
20331 DiagnosticSeverity::HINT => colors.info,
20332 _ => colors.ignored,
20333 }
20334}
20335
20336pub fn styled_runs_for_code_label<'a>(
20337 label: &'a CodeLabel,
20338 syntax_theme: &'a theme::SyntaxTheme,
20339) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20340 let fade_out = HighlightStyle {
20341 fade_out: Some(0.35),
20342 ..Default::default()
20343 };
20344
20345 let mut prev_end = label.filter_range.end;
20346 label
20347 .runs
20348 .iter()
20349 .enumerate()
20350 .flat_map(move |(ix, (range, highlight_id))| {
20351 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20352 style
20353 } else {
20354 return Default::default();
20355 };
20356 let mut muted_style = style;
20357 muted_style.highlight(fade_out);
20358
20359 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20360 if range.start >= label.filter_range.end {
20361 if range.start > prev_end {
20362 runs.push((prev_end..range.start, fade_out));
20363 }
20364 runs.push((range.clone(), muted_style));
20365 } else if range.end <= label.filter_range.end {
20366 runs.push((range.clone(), style));
20367 } else {
20368 runs.push((range.start..label.filter_range.end, style));
20369 runs.push((label.filter_range.end..range.end, muted_style));
20370 }
20371 prev_end = cmp::max(prev_end, range.end);
20372
20373 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20374 runs.push((prev_end..label.text.len(), fade_out));
20375 }
20376
20377 runs
20378 })
20379}
20380
20381pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20382 let mut prev_index = 0;
20383 let mut prev_codepoint: Option<char> = None;
20384 text.char_indices()
20385 .chain([(text.len(), '\0')])
20386 .filter_map(move |(index, codepoint)| {
20387 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20388 let is_boundary = index == text.len()
20389 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20390 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20391 if is_boundary {
20392 let chunk = &text[prev_index..index];
20393 prev_index = index;
20394 Some(chunk)
20395 } else {
20396 None
20397 }
20398 })
20399}
20400
20401pub trait RangeToAnchorExt: Sized {
20402 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20403
20404 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20405 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20406 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20407 }
20408}
20409
20410impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20411 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20412 let start_offset = self.start.to_offset(snapshot);
20413 let end_offset = self.end.to_offset(snapshot);
20414 if start_offset == end_offset {
20415 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20416 } else {
20417 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20418 }
20419 }
20420}
20421
20422pub trait RowExt {
20423 fn as_f32(&self) -> f32;
20424
20425 fn next_row(&self) -> Self;
20426
20427 fn previous_row(&self) -> Self;
20428
20429 fn minus(&self, other: Self) -> u32;
20430}
20431
20432impl RowExt for DisplayRow {
20433 fn as_f32(&self) -> f32 {
20434 self.0 as f32
20435 }
20436
20437 fn next_row(&self) -> Self {
20438 Self(self.0 + 1)
20439 }
20440
20441 fn previous_row(&self) -> Self {
20442 Self(self.0.saturating_sub(1))
20443 }
20444
20445 fn minus(&self, other: Self) -> u32 {
20446 self.0 - other.0
20447 }
20448}
20449
20450impl RowExt for MultiBufferRow {
20451 fn as_f32(&self) -> f32 {
20452 self.0 as f32
20453 }
20454
20455 fn next_row(&self) -> Self {
20456 Self(self.0 + 1)
20457 }
20458
20459 fn previous_row(&self) -> Self {
20460 Self(self.0.saturating_sub(1))
20461 }
20462
20463 fn minus(&self, other: Self) -> u32 {
20464 self.0 - other.0
20465 }
20466}
20467
20468trait RowRangeExt {
20469 type Row;
20470
20471 fn len(&self) -> usize;
20472
20473 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20474}
20475
20476impl RowRangeExt for Range<MultiBufferRow> {
20477 type Row = MultiBufferRow;
20478
20479 fn len(&self) -> usize {
20480 (self.end.0 - self.start.0) as usize
20481 }
20482
20483 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20484 (self.start.0..self.end.0).map(MultiBufferRow)
20485 }
20486}
20487
20488impl RowRangeExt for Range<DisplayRow> {
20489 type Row = DisplayRow;
20490
20491 fn len(&self) -> usize {
20492 (self.end.0 - self.start.0) as usize
20493 }
20494
20495 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20496 (self.start.0..self.end.0).map(DisplayRow)
20497 }
20498}
20499
20500/// If select range has more than one line, we
20501/// just point the cursor to range.start.
20502fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20503 if range.start.row == range.end.row {
20504 range
20505 } else {
20506 range.start..range.start
20507 }
20508}
20509pub struct KillRing(ClipboardItem);
20510impl Global for KillRing {}
20511
20512const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20513
20514enum BreakpointPromptEditAction {
20515 Log,
20516 Condition,
20517 HitCondition,
20518}
20519
20520struct BreakpointPromptEditor {
20521 pub(crate) prompt: Entity<Editor>,
20522 editor: WeakEntity<Editor>,
20523 breakpoint_anchor: Anchor,
20524 breakpoint: Breakpoint,
20525 edit_action: BreakpointPromptEditAction,
20526 block_ids: HashSet<CustomBlockId>,
20527 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20528 _subscriptions: Vec<Subscription>,
20529}
20530
20531impl BreakpointPromptEditor {
20532 const MAX_LINES: u8 = 4;
20533
20534 fn new(
20535 editor: WeakEntity<Editor>,
20536 breakpoint_anchor: Anchor,
20537 breakpoint: Breakpoint,
20538 edit_action: BreakpointPromptEditAction,
20539 window: &mut Window,
20540 cx: &mut Context<Self>,
20541 ) -> Self {
20542 let base_text = match edit_action {
20543 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20544 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20545 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20546 }
20547 .map(|msg| msg.to_string())
20548 .unwrap_or_default();
20549
20550 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20551 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20552
20553 let prompt = cx.new(|cx| {
20554 let mut prompt = Editor::new(
20555 EditorMode::AutoHeight {
20556 max_lines: Self::MAX_LINES as usize,
20557 },
20558 buffer,
20559 None,
20560 window,
20561 cx,
20562 );
20563 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20564 prompt.set_show_cursor_when_unfocused(false, cx);
20565 prompt.set_placeholder_text(
20566 match edit_action {
20567 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20568 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20569 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20570 },
20571 cx,
20572 );
20573
20574 prompt
20575 });
20576
20577 Self {
20578 prompt,
20579 editor,
20580 breakpoint_anchor,
20581 breakpoint,
20582 edit_action,
20583 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20584 block_ids: Default::default(),
20585 _subscriptions: vec![],
20586 }
20587 }
20588
20589 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20590 self.block_ids.extend(block_ids)
20591 }
20592
20593 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20594 if let Some(editor) = self.editor.upgrade() {
20595 let message = self
20596 .prompt
20597 .read(cx)
20598 .buffer
20599 .read(cx)
20600 .as_singleton()
20601 .expect("A multi buffer in breakpoint prompt isn't possible")
20602 .read(cx)
20603 .as_rope()
20604 .to_string();
20605
20606 editor.update(cx, |editor, cx| {
20607 editor.edit_breakpoint_at_anchor(
20608 self.breakpoint_anchor,
20609 self.breakpoint.clone(),
20610 match self.edit_action {
20611 BreakpointPromptEditAction::Log => {
20612 BreakpointEditAction::EditLogMessage(message.into())
20613 }
20614 BreakpointPromptEditAction::Condition => {
20615 BreakpointEditAction::EditCondition(message.into())
20616 }
20617 BreakpointPromptEditAction::HitCondition => {
20618 BreakpointEditAction::EditHitCondition(message.into())
20619 }
20620 },
20621 cx,
20622 );
20623
20624 editor.remove_blocks(self.block_ids.clone(), None, cx);
20625 cx.focus_self(window);
20626 });
20627 }
20628 }
20629
20630 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20631 self.editor
20632 .update(cx, |editor, cx| {
20633 editor.remove_blocks(self.block_ids.clone(), None, cx);
20634 window.focus(&editor.focus_handle);
20635 })
20636 .log_err();
20637 }
20638
20639 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20640 let settings = ThemeSettings::get_global(cx);
20641 let text_style = TextStyle {
20642 color: if self.prompt.read(cx).read_only(cx) {
20643 cx.theme().colors().text_disabled
20644 } else {
20645 cx.theme().colors().text
20646 },
20647 font_family: settings.buffer_font.family.clone(),
20648 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20649 font_size: settings.buffer_font_size(cx).into(),
20650 font_weight: settings.buffer_font.weight,
20651 line_height: relative(settings.buffer_line_height.value()),
20652 ..Default::default()
20653 };
20654 EditorElement::new(
20655 &self.prompt,
20656 EditorStyle {
20657 background: cx.theme().colors().editor_background,
20658 local_player: cx.theme().players().local(),
20659 text: text_style,
20660 ..Default::default()
20661 },
20662 )
20663 }
20664}
20665
20666impl Render for BreakpointPromptEditor {
20667 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20668 let gutter_dimensions = *self.gutter_dimensions.lock();
20669 h_flex()
20670 .key_context("Editor")
20671 .bg(cx.theme().colors().editor_background)
20672 .border_y_1()
20673 .border_color(cx.theme().status().info_border)
20674 .size_full()
20675 .py(window.line_height() / 2.5)
20676 .on_action(cx.listener(Self::confirm))
20677 .on_action(cx.listener(Self::cancel))
20678 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20679 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20680 }
20681}
20682
20683impl Focusable for BreakpointPromptEditor {
20684 fn focus_handle(&self, cx: &App) -> FocusHandle {
20685 self.prompt.focus_handle(cx)
20686 }
20687}
20688
20689fn all_edits_insertions_or_deletions(
20690 edits: &Vec<(Range<Anchor>, String)>,
20691 snapshot: &MultiBufferSnapshot,
20692) -> bool {
20693 let mut all_insertions = true;
20694 let mut all_deletions = true;
20695
20696 for (range, new_text) in edits.iter() {
20697 let range_is_empty = range.to_offset(&snapshot).is_empty();
20698 let text_is_empty = new_text.is_empty();
20699
20700 if range_is_empty != text_is_empty {
20701 if range_is_empty {
20702 all_deletions = false;
20703 } else {
20704 all_insertions = false;
20705 }
20706 } else {
20707 return false;
20708 }
20709
20710 if !all_insertions && !all_deletions {
20711 return false;
20712 }
20713 }
20714 all_insertions || all_deletions
20715}
20716
20717struct MissingEditPredictionKeybindingTooltip;
20718
20719impl Render for MissingEditPredictionKeybindingTooltip {
20720 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20721 ui::tooltip_container(window, cx, |container, _, cx| {
20722 container
20723 .flex_shrink_0()
20724 .max_w_80()
20725 .min_h(rems_from_px(124.))
20726 .justify_between()
20727 .child(
20728 v_flex()
20729 .flex_1()
20730 .text_ui_sm(cx)
20731 .child(Label::new("Conflict with Accept Keybinding"))
20732 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20733 )
20734 .child(
20735 h_flex()
20736 .pb_1()
20737 .gap_1()
20738 .items_end()
20739 .w_full()
20740 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20741 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20742 }))
20743 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20744 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20745 })),
20746 )
20747 })
20748 }
20749}
20750
20751#[derive(Debug, Clone, Copy, PartialEq)]
20752pub struct LineHighlight {
20753 pub background: Background,
20754 pub border: Option<gpui::Hsla>,
20755 pub include_gutter: bool,
20756 pub type_id: Option<TypeId>,
20757}
20758
20759fn render_diff_hunk_controls(
20760 row: u32,
20761 status: &DiffHunkStatus,
20762 hunk_range: Range<Anchor>,
20763 is_created_file: bool,
20764 line_height: Pixels,
20765 editor: &Entity<Editor>,
20766 _window: &mut Window,
20767 cx: &mut App,
20768) -> AnyElement {
20769 h_flex()
20770 .h(line_height)
20771 .mr_1()
20772 .gap_1()
20773 .px_0p5()
20774 .pb_1()
20775 .border_x_1()
20776 .border_b_1()
20777 .border_color(cx.theme().colors().border_variant)
20778 .rounded_b_lg()
20779 .bg(cx.theme().colors().editor_background)
20780 .gap_1()
20781 .occlude()
20782 .shadow_md()
20783 .child(if status.has_secondary_hunk() {
20784 Button::new(("stage", row as u64), "Stage")
20785 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20786 .tooltip({
20787 let focus_handle = editor.focus_handle(cx);
20788 move |window, cx| {
20789 Tooltip::for_action_in(
20790 "Stage Hunk",
20791 &::git::ToggleStaged,
20792 &focus_handle,
20793 window,
20794 cx,
20795 )
20796 }
20797 })
20798 .on_click({
20799 let editor = editor.clone();
20800 move |_event, _window, cx| {
20801 editor.update(cx, |editor, cx| {
20802 editor.stage_or_unstage_diff_hunks(
20803 true,
20804 vec![hunk_range.start..hunk_range.start],
20805 cx,
20806 );
20807 });
20808 }
20809 })
20810 } else {
20811 Button::new(("unstage", row as u64), "Unstage")
20812 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20813 .tooltip({
20814 let focus_handle = editor.focus_handle(cx);
20815 move |window, cx| {
20816 Tooltip::for_action_in(
20817 "Unstage Hunk",
20818 &::git::ToggleStaged,
20819 &focus_handle,
20820 window,
20821 cx,
20822 )
20823 }
20824 })
20825 .on_click({
20826 let editor = editor.clone();
20827 move |_event, _window, cx| {
20828 editor.update(cx, |editor, cx| {
20829 editor.stage_or_unstage_diff_hunks(
20830 false,
20831 vec![hunk_range.start..hunk_range.start],
20832 cx,
20833 );
20834 });
20835 }
20836 })
20837 })
20838 .child(
20839 Button::new(("restore", row as u64), "Restore")
20840 .tooltip({
20841 let focus_handle = editor.focus_handle(cx);
20842 move |window, cx| {
20843 Tooltip::for_action_in(
20844 "Restore Hunk",
20845 &::git::Restore,
20846 &focus_handle,
20847 window,
20848 cx,
20849 )
20850 }
20851 })
20852 .on_click({
20853 let editor = editor.clone();
20854 move |_event, window, cx| {
20855 editor.update(cx, |editor, cx| {
20856 let snapshot = editor.snapshot(window, cx);
20857 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20858 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20859 });
20860 }
20861 })
20862 .disabled(is_created_file),
20863 )
20864 .when(
20865 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20866 |el| {
20867 el.child(
20868 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20869 .shape(IconButtonShape::Square)
20870 .icon_size(IconSize::Small)
20871 // .disabled(!has_multiple_hunks)
20872 .tooltip({
20873 let focus_handle = editor.focus_handle(cx);
20874 move |window, cx| {
20875 Tooltip::for_action_in(
20876 "Next Hunk",
20877 &GoToHunk,
20878 &focus_handle,
20879 window,
20880 cx,
20881 )
20882 }
20883 })
20884 .on_click({
20885 let editor = editor.clone();
20886 move |_event, window, cx| {
20887 editor.update(cx, |editor, cx| {
20888 let snapshot = editor.snapshot(window, cx);
20889 let position =
20890 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20891 editor.go_to_hunk_before_or_after_position(
20892 &snapshot,
20893 position,
20894 Direction::Next,
20895 window,
20896 cx,
20897 );
20898 editor.expand_selected_diff_hunks(cx);
20899 });
20900 }
20901 }),
20902 )
20903 .child(
20904 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20905 .shape(IconButtonShape::Square)
20906 .icon_size(IconSize::Small)
20907 // .disabled(!has_multiple_hunks)
20908 .tooltip({
20909 let focus_handle = editor.focus_handle(cx);
20910 move |window, cx| {
20911 Tooltip::for_action_in(
20912 "Previous Hunk",
20913 &GoToPreviousHunk,
20914 &focus_handle,
20915 window,
20916 cx,
20917 )
20918 }
20919 })
20920 .on_click({
20921 let editor = editor.clone();
20922 move |_event, window, cx| {
20923 editor.update(cx, |editor, cx| {
20924 let snapshot = editor.snapshot(window, cx);
20925 let point =
20926 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20927 editor.go_to_hunk_before_or_after_position(
20928 &snapshot,
20929 point,
20930 Direction::Prev,
20931 window,
20932 cx,
20933 );
20934 editor.expand_selected_diff_hunks(cx);
20935 });
20936 }
20937 }),
20938 )
20939 },
20940 )
20941 .into_any_element()
20942}