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::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132};
133
134pub use git::blame::BlameRenderer;
135pub use proposed_changes_editor::{
136 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
137};
138use smallvec::smallvec;
139use std::{cell::OnceCell, iter::Peekable};
140use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
141
142pub use lsp::CompletionContext;
143use lsp::{
144 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
145 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
146};
147
148use language::BufferSnapshot;
149pub use lsp_ext::lsp_tasks;
150use movement::TextLayoutDetails;
151pub use multi_buffer::{
152 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
153 RowInfo, ToOffset, ToPoint,
154};
155use multi_buffer::{
156 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
157 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
158};
159use parking_lot::Mutex;
160use project::{
161 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
162 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
163 TaskSourceKind,
164 debugger::breakpoint_store::Breakpoint,
165 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
166 project_settings::{GitGutterSetting, ProjectSettings},
167};
168use rand::prelude::*;
169use rpc::{ErrorExt, proto::*};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
171use selections_collection::{
172 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
173};
174use serde::{Deserialize, Serialize};
175use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
176use smallvec::SmallVec;
177use snippet::Snippet;
178use std::sync::Arc;
179use std::{
180 any::TypeId,
181 borrow::Cow,
182 cell::RefCell,
183 cmp::{self, Ordering, Reverse},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 time::{Duration, Instant},
190};
191pub use sum_tree::Bias;
192use sum_tree::TreeMap;
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
194use theme::{
195 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Key, Tooltip, h_flex, prelude::*,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemHandle, PreviewTabsSettings},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::hover_links::{find_url, find_url_from_range};
213use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
249 alt: true,
250 shift: true,
251 control: false,
252 platform: false,
253 function: false,
254};
255
256struct InlineValueCache {
257 enabled: bool,
258 inlays: Vec<InlayId>,
259 refresh_task: Task<Option<()>>,
260}
261
262impl InlineValueCache {
263 fn new(enabled: bool) -> Self {
264 Self {
265 enabled,
266 inlays: Vec::new(),
267 refresh_task: Task::ready(None),
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
273pub enum InlayId {
274 InlineCompletion(usize),
275 Hint(usize),
276 DebuggerValue(usize),
277}
278
279impl InlayId {
280 fn id(&self) -> usize {
281 match self {
282 Self::InlineCompletion(id) => *id,
283 Self::Hint(id) => *id,
284 Self::DebuggerValue(id) => *id,
285 }
286 }
287}
288
289pub enum ActiveDebugLine {}
290enum DocumentHighlightRead {}
291enum DocumentHighlightWrite {}
292enum InputComposition {}
293enum SelectedTextHighlight {}
294
295pub enum ConflictsOuter {}
296pub enum ConflictsOurs {}
297pub enum ConflictsTheirs {}
298pub enum ConflictsOursMarker {}
299pub enum ConflictsTheirsMarker {}
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum Navigated {
303 Yes,
304 No,
305}
306
307impl Navigated {
308 pub fn from_bool(yes: bool) -> Navigated {
309 if yes { Navigated::Yes } else { Navigated::No }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
314enum DisplayDiffHunk {
315 Folded {
316 display_row: DisplayRow,
317 },
318 Unfolded {
319 is_created_file: bool,
320 diff_base_byte_range: Range<usize>,
321 display_row_range: Range<DisplayRow>,
322 multi_buffer_range: Range<Anchor>,
323 status: DiffHunkStatus,
324 },
325}
326
327pub enum HideMouseCursorOrigin {
328 TypingAction,
329 MovementAction,
330}
331
332pub fn init_settings(cx: &mut App) {
333 EditorSettings::register(cx);
334}
335
336pub fn init(cx: &mut App) {
337 init_settings(cx);
338
339 cx.set_global(GlobalBlameRenderer(Arc::new(())));
340
341 workspace::register_project_item::<Editor>(cx);
342 workspace::FollowableViewRegistry::register::<Editor>(cx);
343 workspace::register_serializable_item::<Editor>(cx);
344
345 cx.observe_new(
346 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
347 workspace.register_action(Editor::new_file);
348 workspace.register_action(Editor::new_file_vertical);
349 workspace.register_action(Editor::new_file_horizontal);
350 workspace.register_action(Editor::cancel_language_server_work);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach();
367 }
368 });
369 cx.on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384}
385
386pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
387 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
388}
389
390pub trait DiagnosticRenderer {
391 fn render_group(
392 &self,
393 diagnostic_group: Vec<DiagnosticEntry<Point>>,
394 buffer_id: BufferId,
395 snapshot: EditorSnapshot,
396 editor: WeakEntity<Editor>,
397 cx: &mut App,
398 ) -> Vec<BlockProperties<Anchor>>;
399
400 fn render_hover(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 range: Range<Point>,
404 buffer_id: BufferId,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 goal_column: u32,
447 },
448 Extend {
449 position: DisplayPoint,
450 click_count: usize,
451 },
452 Update {
453 position: DisplayPoint,
454 goal_column: u32,
455 scroll_delta: gpui::Point<f32>,
456 },
457 End,
458}
459
460#[derive(Clone, Debug)]
461pub enum SelectMode {
462 Character,
463 Word(Range<Anchor>),
464 Line(Range<Anchor>),
465 All,
466}
467
468#[derive(Copy, Clone, PartialEq, Eq, Debug)]
469pub enum EditorMode {
470 SingleLine {
471 auto_width: bool,
472 },
473 AutoHeight {
474 max_lines: usize,
475 },
476 Full {
477 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
478 scale_ui_elements_with_buffer_font_size: bool,
479 /// When set to `true`, the editor will render a background for the active line.
480 show_active_line_background: bool,
481 /// When set to `true`, the editor's height will be determined by its content.
482 sized_by_content: bool,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sized_by_content: false,
492 }
493 }
494
495 pub fn is_full(&self) -> bool {
496 matches!(self, Self::Full { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub horizontal_padding: Pixels,
521 pub local_player: PlayerColor,
522 pub text: TextStyle,
523 pub scrollbar_width: Pixels,
524 pub syntax: Arc<SyntaxTheme>,
525 pub status: StatusColors,
526 pub inlay_hints_style: HighlightStyle,
527 pub inline_completion_styles: InlineCompletionStyles,
528 pub unnecessary_code_fade: f32,
529}
530
531impl Default for EditorStyle {
532 fn default() -> Self {
533 Self {
534 background: Hsla::default(),
535 horizontal_padding: Pixels::default(),
536 local_player: PlayerColor::default(),
537 text: TextStyle::default(),
538 scrollbar_width: Pixels::default(),
539 syntax: Default::default(),
540 // HACK: Status colors don't have a real default.
541 // We should look into removing the status colors from the editor
542 // style and retrieve them directly from the theme.
543 status: StatusColors::dark(),
544 inlay_hints_style: HighlightStyle::default(),
545 inline_completion_styles: InlineCompletionStyles {
546 insertion: HighlightStyle::default(),
547 whitespace: HighlightStyle::default(),
548 },
549 unnecessary_code_fade: Default::default(),
550 }
551 }
552}
553
554pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
555 let show_background = language_settings::language_settings(None, None, cx)
556 .inlay_hints
557 .show_background;
558
559 HighlightStyle {
560 color: Some(cx.theme().status().hint),
561 background_color: show_background.then(|| cx.theme().status().hint_background),
562 ..HighlightStyle::default()
563 }
564}
565
566pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
567 InlineCompletionStyles {
568 insertion: HighlightStyle {
569 color: Some(cx.theme().status().predictive),
570 ..HighlightStyle::default()
571 },
572 whitespace: HighlightStyle {
573 background_color: Some(cx.theme().status().created_background),
574 ..HighlightStyle::default()
575 },
576 }
577}
578
579type CompletionId = usize;
580
581pub(crate) enum EditDisplayMode {
582 TabAccept,
583 DiffPopover,
584 Inline,
585}
586
587enum InlineCompletion {
588 Edit {
589 edits: Vec<(Range<Anchor>, String)>,
590 edit_preview: Option<EditPreview>,
591 display_mode: EditDisplayMode,
592 snapshot: BufferSnapshot,
593 },
594 Move {
595 target: Anchor,
596 snapshot: BufferSnapshot,
597 },
598}
599
600struct InlineCompletionState {
601 inlay_ids: Vec<InlayId>,
602 completion: InlineCompletion,
603 completion_id: Option<SharedString>,
604 invalidation_range: Range<Anchor>,
605}
606
607enum EditPredictionSettings {
608 Disabled,
609 Enabled {
610 show_in_menu: bool,
611 preview_requires_modifier: bool,
612 },
613}
614
615enum InlineCompletionHighlight {}
616
617#[derive(Debug, Clone)]
618struct InlineDiagnostic {
619 message: SharedString,
620 group_id: usize,
621 is_primary: bool,
622 start: Point,
623 severity: DiagnosticSeverity,
624}
625
626pub enum MenuInlineCompletionsPolicy {
627 Never,
628 ByProvider,
629}
630
631pub enum EditPredictionPreview {
632 /// Modifier is not pressed
633 Inactive { released_too_fast: bool },
634 /// Modifier pressed
635 Active {
636 since: Instant,
637 previous_scroll_position: Option<ScrollAnchor>,
638 },
639}
640
641impl EditPredictionPreview {
642 pub fn released_too_fast(&self) -> bool {
643 match self {
644 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
645 EditPredictionPreview::Active { .. } => false,
646 }
647 }
648
649 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
650 if let EditPredictionPreview::Active {
651 previous_scroll_position,
652 ..
653 } = self
654 {
655 *previous_scroll_position = scroll_position;
656 }
657 }
658}
659
660pub struct ContextMenuOptions {
661 pub min_entries_visible: usize,
662 pub max_entries_visible: usize,
663 pub placement: Option<ContextMenuPlacement>,
664}
665
666#[derive(Debug, Clone, PartialEq, Eq)]
667pub enum ContextMenuPlacement {
668 Above,
669 Below,
670}
671
672#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
673struct EditorActionId(usize);
674
675impl EditorActionId {
676 pub fn post_inc(&mut self) -> Self {
677 let answer = self.0;
678
679 *self = Self(answer + 1);
680
681 Self(answer)
682 }
683}
684
685// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
686// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
687
688type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
689type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
690
691#[derive(Default)]
692struct ScrollbarMarkerState {
693 scrollbar_size: Size<Pixels>,
694 dirty: bool,
695 markers: Arc<[PaintQuad]>,
696 pending_refresh: Option<Task<Result<()>>>,
697}
698
699impl ScrollbarMarkerState {
700 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
701 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
702 }
703}
704
705#[derive(Clone, Debug)]
706struct RunnableTasks {
707 templates: Vec<(TaskSourceKind, TaskTemplate)>,
708 offset: multi_buffer::Anchor,
709 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
710 column: u32,
711 // Values of all named captures, including those starting with '_'
712 extra_variables: HashMap<String, String>,
713 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
714 context_range: Range<BufferOffset>,
715}
716
717impl RunnableTasks {
718 fn resolve<'a>(
719 &'a self,
720 cx: &'a task::TaskContext,
721 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
722 self.templates.iter().filter_map(|(kind, template)| {
723 template
724 .resolve_task(&kind.to_id_base(), cx)
725 .map(|task| (kind.clone(), task))
726 })
727 }
728}
729
730#[derive(Clone)]
731struct ResolvedTasks {
732 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
733 position: Anchor,
734}
735
736#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
737struct BufferOffset(usize);
738
739// Addons allow storing per-editor state in other crates (e.g. Vim)
740pub trait Addon: 'static {
741 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
742
743 fn render_buffer_header_controls(
744 &self,
745 _: &ExcerptInfo,
746 _: &Window,
747 _: &App,
748 ) -> Option<AnyElement> {
749 None
750 }
751
752 fn to_any(&self) -> &dyn std::any::Any;
753
754 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
755 None
756 }
757}
758
759/// A set of caret positions, registered when the editor was edited.
760pub struct ChangeList {
761 changes: Vec<Vec<Anchor>>,
762 /// Currently "selected" change.
763 position: Option<usize>,
764}
765
766impl ChangeList {
767 pub fn new() -> Self {
768 Self {
769 changes: Vec::new(),
770 position: None,
771 }
772 }
773
774 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
775 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
776 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
777 if self.changes.is_empty() {
778 return None;
779 }
780
781 let prev = self.position.unwrap_or(self.changes.len());
782 let next = if direction == Direction::Prev {
783 prev.saturating_sub(count)
784 } else {
785 (prev + count).min(self.changes.len() - 1)
786 };
787 self.position = Some(next);
788 self.changes.get(next).map(|anchors| anchors.as_slice())
789 }
790
791 /// Adds a new change to the list, resetting the change list position.
792 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
793 self.position.take();
794 if pop_state {
795 self.changes.pop();
796 }
797 self.changes.push(new_positions.clone());
798 }
799
800 pub fn last(&self) -> Option<&[Anchor]> {
801 self.changes.last().map(|anchors| anchors.as_slice())
802 }
803}
804
805#[derive(Clone)]
806struct InlineBlamePopoverState {
807 scroll_handle: ScrollHandle,
808 commit_message: Option<ParsedCommitMessage>,
809 markdown: Entity<Markdown>,
810}
811
812struct InlineBlamePopover {
813 position: gpui::Point<Pixels>,
814 show_task: Option<Task<()>>,
815 hide_task: Option<Task<()>>,
816 popover_bounds: Option<Bounds<Pixels>>,
817 popover_state: InlineBlamePopoverState,
818}
819
820/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
821/// a breakpoint on them.
822#[derive(Clone, Copy, Debug)]
823struct PhantomBreakpointIndicator {
824 display_row: DisplayRow,
825 /// There's a small debounce between hovering over the line and showing the indicator.
826 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
827 is_active: bool,
828 collides_with_existing_breakpoint: bool,
829}
830/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
831///
832/// See the [module level documentation](self) for more information.
833pub struct Editor {
834 focus_handle: FocusHandle,
835 last_focused_descendant: Option<WeakFocusHandle>,
836 /// The text buffer being edited
837 buffer: Entity<MultiBuffer>,
838 /// Map of how text in the buffer should be displayed.
839 /// Handles soft wraps, folds, fake inlay text insertions, etc.
840 pub display_map: Entity<DisplayMap>,
841 pub selections: SelectionsCollection,
842 pub scroll_manager: ScrollManager,
843 /// When inline assist editors are linked, they all render cursors because
844 /// typing enters text into each of them, even the ones that aren't focused.
845 pub(crate) show_cursor_when_unfocused: bool,
846 columnar_selection_tail: Option<Anchor>,
847 add_selections_state: Option<AddSelectionsState>,
848 select_next_state: Option<SelectNextState>,
849 select_prev_state: Option<SelectNextState>,
850 selection_history: SelectionHistory,
851 autoclose_regions: Vec<AutocloseRegion>,
852 snippet_stack: InvalidationStack<SnippetState>,
853 select_syntax_node_history: SelectSyntaxNodeHistory,
854 ime_transaction: Option<TransactionId>,
855 active_diagnostics: ActiveDiagnostic,
856 show_inline_diagnostics: bool,
857 inline_diagnostics_update: Task<()>,
858 inline_diagnostics_enabled: bool,
859 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
860 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
861 hard_wrap: Option<usize>,
862
863 // TODO: make this a access method
864 pub project: Option<Entity<Project>>,
865 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
866 completion_provider: Option<Box<dyn CompletionProvider>>,
867 collaboration_hub: Option<Box<dyn CollaborationHub>>,
868 blink_manager: Entity<BlinkManager>,
869 show_cursor_names: bool,
870 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
871 pub show_local_selections: bool,
872 mode: EditorMode,
873 show_breadcrumbs: bool,
874 show_gutter: bool,
875 show_scrollbars: bool,
876 disable_expand_excerpt_buttons: bool,
877 show_line_numbers: Option<bool>,
878 use_relative_line_numbers: Option<bool>,
879 show_git_diff_gutter: Option<bool>,
880 show_code_actions: Option<bool>,
881 show_runnables: Option<bool>,
882 show_breakpoints: Option<bool>,
883 show_wrap_guides: Option<bool>,
884 show_indent_guides: Option<bool>,
885 placeholder_text: Option<Arc<str>>,
886 highlight_order: usize,
887 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
888 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
889 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
890 scrollbar_marker_state: ScrollbarMarkerState,
891 active_indent_guides_state: ActiveIndentGuidesState,
892 nav_history: Option<ItemNavHistory>,
893 context_menu: RefCell<Option<CodeContextMenu>>,
894 context_menu_options: Option<ContextMenuOptions>,
895 mouse_context_menu: Option<MouseContextMenu>,
896 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
897 inline_blame_popover: Option<InlineBlamePopover>,
898 signature_help_state: SignatureHelpState,
899 auto_signature_help: Option<bool>,
900 find_all_references_task_sources: Vec<Anchor>,
901 next_completion_id: CompletionId,
902 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
903 code_actions_task: Option<Task<Result<()>>>,
904 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
905 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
906 document_highlights_task: Option<Task<()>>,
907 linked_editing_range_task: Option<Task<Option<()>>>,
908 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
909 pending_rename: Option<RenameState>,
910 searchable: bool,
911 cursor_shape: CursorShape,
912 current_line_highlight: Option<CurrentLineHighlight>,
913 collapse_matches: bool,
914 autoindent_mode: Option<AutoindentMode>,
915 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
916 input_enabled: bool,
917 use_modal_editing: bool,
918 read_only: bool,
919 leader_id: Option<CollaboratorId>,
920 remote_id: Option<ViewId>,
921 pub hover_state: HoverState,
922 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
923 gutter_hovered: bool,
924 hovered_link_state: Option<HoveredLinkState>,
925 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
926 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
927 active_inline_completion: Option<InlineCompletionState>,
928 /// Used to prevent flickering as the user types while the menu is open
929 stale_inline_completion_in_menu: Option<InlineCompletionState>,
930 edit_prediction_settings: EditPredictionSettings,
931 inline_completions_hidden_for_vim_mode: bool,
932 show_inline_completions_override: Option<bool>,
933 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
934 edit_prediction_preview: EditPredictionPreview,
935 edit_prediction_indent_conflict: bool,
936 edit_prediction_requires_modifier_in_indent_conflict: bool,
937 inlay_hint_cache: InlayHintCache,
938 next_inlay_id: usize,
939 _subscriptions: Vec<Subscription>,
940 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
941 gutter_dimensions: GutterDimensions,
942 style: Option<EditorStyle>,
943 text_style_refinement: Option<TextStyleRefinement>,
944 next_editor_action_id: EditorActionId,
945 editor_actions:
946 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
947 use_autoclose: bool,
948 use_auto_surround: bool,
949 auto_replace_emoji_shortcode: bool,
950 jsx_tag_auto_close_enabled_in_any_buffer: bool,
951 show_git_blame_gutter: bool,
952 show_git_blame_inline: bool,
953 show_git_blame_inline_delay_task: Option<Task<()>>,
954 git_blame_inline_enabled: bool,
955 render_diff_hunk_controls: RenderDiffHunkControlsFn,
956 serialize_dirty_buffers: bool,
957 show_selection_menu: Option<bool>,
958 blame: Option<Entity<GitBlame>>,
959 blame_subscription: Option<Subscription>,
960 custom_context_menu: Option<
961 Box<
962 dyn 'static
963 + Fn(
964 &mut Self,
965 DisplayPoint,
966 &mut Window,
967 &mut Context<Self>,
968 ) -> Option<Entity<ui::ContextMenu>>,
969 >,
970 >,
971 last_bounds: Option<Bounds<Pixels>>,
972 last_position_map: Option<Rc<PositionMap>>,
973 expect_bounds_change: Option<Bounds<Pixels>>,
974 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
975 tasks_update_task: Option<Task<()>>,
976 breakpoint_store: Option<Entity<BreakpointStore>>,
977 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
978 in_project_search: bool,
979 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
980 breadcrumb_header: Option<String>,
981 focused_block: Option<FocusedBlock>,
982 next_scroll_position: NextScrollCursorCenterTopBottom,
983 addons: HashMap<TypeId, Box<dyn Addon>>,
984 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
985 load_diff_task: Option<Shared<Task<()>>>,
986 /// Whether we are temporarily displaying a diff other than git's
987 temporary_diff_override: bool,
988 selection_mark_mode: bool,
989 toggle_fold_multiple_buffers: Task<()>,
990 _scroll_cursor_center_top_bottom_task: Task<()>,
991 serialize_selections: Task<()>,
992 serialize_folds: Task<()>,
993 mouse_cursor_hidden: bool,
994 hide_mouse_mode: HideMouseMode,
995 pub change_list: ChangeList,
996 inline_value_cache: InlineValueCache,
997}
998
999#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1000enum NextScrollCursorCenterTopBottom {
1001 #[default]
1002 Center,
1003 Top,
1004 Bottom,
1005}
1006
1007impl NextScrollCursorCenterTopBottom {
1008 fn next(&self) -> Self {
1009 match self {
1010 Self::Center => Self::Top,
1011 Self::Top => Self::Bottom,
1012 Self::Bottom => Self::Center,
1013 }
1014 }
1015}
1016
1017#[derive(Clone)]
1018pub struct EditorSnapshot {
1019 pub mode: EditorMode,
1020 show_gutter: bool,
1021 show_line_numbers: Option<bool>,
1022 show_git_diff_gutter: Option<bool>,
1023 show_code_actions: Option<bool>,
1024 show_runnables: Option<bool>,
1025 show_breakpoints: Option<bool>,
1026 git_blame_gutter_max_author_length: Option<usize>,
1027 pub display_snapshot: DisplaySnapshot,
1028 pub placeholder_text: Option<Arc<str>>,
1029 is_focused: bool,
1030 scroll_anchor: ScrollAnchor,
1031 ongoing_scroll: OngoingScroll,
1032 current_line_highlight: CurrentLineHighlight,
1033 gutter_hovered: bool,
1034}
1035
1036#[derive(Default, Debug, Clone, Copy)]
1037pub struct GutterDimensions {
1038 pub left_padding: Pixels,
1039 pub right_padding: Pixels,
1040 pub width: Pixels,
1041 pub margin: Pixels,
1042 pub git_blame_entries_width: Option<Pixels>,
1043}
1044
1045impl GutterDimensions {
1046 /// The full width of the space taken up by the gutter.
1047 pub fn full_width(&self) -> Pixels {
1048 self.margin + self.width
1049 }
1050
1051 /// The width of the space reserved for the fold indicators,
1052 /// use alongside 'justify_end' and `gutter_width` to
1053 /// right align content with the line numbers
1054 pub fn fold_area_width(&self) -> Pixels {
1055 self.margin + self.right_padding
1056 }
1057}
1058
1059#[derive(Debug)]
1060pub struct RemoteSelection {
1061 pub replica_id: ReplicaId,
1062 pub selection: Selection<Anchor>,
1063 pub cursor_shape: CursorShape,
1064 pub collaborator_id: CollaboratorId,
1065 pub line_mode: bool,
1066 pub user_name: Option<SharedString>,
1067 pub color: PlayerColor,
1068}
1069
1070#[derive(Clone, Debug)]
1071struct SelectionHistoryEntry {
1072 selections: Arc<[Selection<Anchor>]>,
1073 select_next_state: Option<SelectNextState>,
1074 select_prev_state: Option<SelectNextState>,
1075 add_selections_state: Option<AddSelectionsState>,
1076}
1077
1078enum SelectionHistoryMode {
1079 Normal,
1080 Undoing,
1081 Redoing,
1082}
1083
1084#[derive(Clone, PartialEq, Eq, Hash)]
1085struct HoveredCursor {
1086 replica_id: u16,
1087 selection_id: usize,
1088}
1089
1090impl Default for SelectionHistoryMode {
1091 fn default() -> Self {
1092 Self::Normal
1093 }
1094}
1095
1096#[derive(Default)]
1097struct SelectionHistory {
1098 #[allow(clippy::type_complexity)]
1099 selections_by_transaction:
1100 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1101 mode: SelectionHistoryMode,
1102 undo_stack: VecDeque<SelectionHistoryEntry>,
1103 redo_stack: VecDeque<SelectionHistoryEntry>,
1104}
1105
1106impl SelectionHistory {
1107 fn insert_transaction(
1108 &mut self,
1109 transaction_id: TransactionId,
1110 selections: Arc<[Selection<Anchor>]>,
1111 ) {
1112 self.selections_by_transaction
1113 .insert(transaction_id, (selections, None));
1114 }
1115
1116 #[allow(clippy::type_complexity)]
1117 fn transaction(
1118 &self,
1119 transaction_id: TransactionId,
1120 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1121 self.selections_by_transaction.get(&transaction_id)
1122 }
1123
1124 #[allow(clippy::type_complexity)]
1125 fn transaction_mut(
1126 &mut self,
1127 transaction_id: TransactionId,
1128 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1129 self.selections_by_transaction.get_mut(&transaction_id)
1130 }
1131
1132 fn push(&mut self, entry: SelectionHistoryEntry) {
1133 if !entry.selections.is_empty() {
1134 match self.mode {
1135 SelectionHistoryMode::Normal => {
1136 self.push_undo(entry);
1137 self.redo_stack.clear();
1138 }
1139 SelectionHistoryMode::Undoing => self.push_redo(entry),
1140 SelectionHistoryMode::Redoing => self.push_undo(entry),
1141 }
1142 }
1143 }
1144
1145 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1146 if self
1147 .undo_stack
1148 .back()
1149 .map_or(true, |e| e.selections != entry.selections)
1150 {
1151 self.undo_stack.push_back(entry);
1152 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1153 self.undo_stack.pop_front();
1154 }
1155 }
1156 }
1157
1158 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1159 if self
1160 .redo_stack
1161 .back()
1162 .map_or(true, |e| e.selections != entry.selections)
1163 {
1164 self.redo_stack.push_back(entry);
1165 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1166 self.redo_stack.pop_front();
1167 }
1168 }
1169 }
1170}
1171
1172#[derive(Clone, Copy)]
1173pub struct RowHighlightOptions {
1174 pub autoscroll: bool,
1175 pub include_gutter: bool,
1176}
1177
1178impl Default for RowHighlightOptions {
1179 fn default() -> Self {
1180 Self {
1181 autoscroll: Default::default(),
1182 include_gutter: true,
1183 }
1184 }
1185}
1186
1187struct RowHighlight {
1188 index: usize,
1189 range: Range<Anchor>,
1190 color: Hsla,
1191 options: RowHighlightOptions,
1192 type_id: TypeId,
1193}
1194
1195#[derive(Clone, Debug)]
1196struct AddSelectionsState {
1197 above: bool,
1198 stack: Vec<usize>,
1199}
1200
1201#[derive(Clone)]
1202struct SelectNextState {
1203 query: AhoCorasick,
1204 wordwise: bool,
1205 done: bool,
1206}
1207
1208impl std::fmt::Debug for SelectNextState {
1209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1210 f.debug_struct(std::any::type_name::<Self>())
1211 .field("wordwise", &self.wordwise)
1212 .field("done", &self.done)
1213 .finish()
1214 }
1215}
1216
1217#[derive(Debug)]
1218struct AutocloseRegion {
1219 selection_id: usize,
1220 range: Range<Anchor>,
1221 pair: BracketPair,
1222}
1223
1224#[derive(Debug)]
1225struct SnippetState {
1226 ranges: Vec<Vec<Range<Anchor>>>,
1227 active_index: usize,
1228 choices: Vec<Option<Vec<String>>>,
1229}
1230
1231#[doc(hidden)]
1232pub struct RenameState {
1233 pub range: Range<Anchor>,
1234 pub old_name: Arc<str>,
1235 pub editor: Entity<Editor>,
1236 block_id: CustomBlockId,
1237}
1238
1239struct InvalidationStack<T>(Vec<T>);
1240
1241struct RegisteredInlineCompletionProvider {
1242 provider: Arc<dyn InlineCompletionProviderHandle>,
1243 _subscription: Subscription,
1244}
1245
1246#[derive(Debug, PartialEq, Eq)]
1247pub struct ActiveDiagnosticGroup {
1248 pub active_range: Range<Anchor>,
1249 pub active_message: String,
1250 pub group_id: usize,
1251 pub blocks: HashSet<CustomBlockId>,
1252}
1253
1254#[derive(Debug, PartialEq, Eq)]
1255#[allow(clippy::large_enum_variant)]
1256pub(crate) enum ActiveDiagnostic {
1257 None,
1258 All,
1259 Group(ActiveDiagnosticGroup),
1260}
1261
1262#[derive(Serialize, Deserialize, Clone, Debug)]
1263pub struct ClipboardSelection {
1264 /// The number of bytes in this selection.
1265 pub len: usize,
1266 /// Whether this was a full-line selection.
1267 pub is_entire_line: bool,
1268 /// The indentation of the first line when this content was originally copied.
1269 pub first_line_indent: u32,
1270}
1271
1272// selections, scroll behavior, was newest selection reversed
1273type SelectSyntaxNodeHistoryState = (
1274 Box<[Selection<usize>]>,
1275 SelectSyntaxNodeScrollBehavior,
1276 bool,
1277);
1278
1279#[derive(Default)]
1280struct SelectSyntaxNodeHistory {
1281 stack: Vec<SelectSyntaxNodeHistoryState>,
1282 // disable temporarily to allow changing selections without losing the stack
1283 pub disable_clearing: bool,
1284}
1285
1286impl SelectSyntaxNodeHistory {
1287 pub fn try_clear(&mut self) {
1288 if !self.disable_clearing {
1289 self.stack.clear();
1290 }
1291 }
1292
1293 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1294 self.stack.push(selection);
1295 }
1296
1297 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1298 self.stack.pop()
1299 }
1300}
1301
1302enum SelectSyntaxNodeScrollBehavior {
1303 CursorTop,
1304 FitSelection,
1305 CursorBottom,
1306}
1307
1308#[derive(Debug)]
1309pub(crate) struct NavigationData {
1310 cursor_anchor: Anchor,
1311 cursor_position: Point,
1312 scroll_anchor: ScrollAnchor,
1313 scroll_top_row: u32,
1314}
1315
1316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1317pub enum GotoDefinitionKind {
1318 Symbol,
1319 Declaration,
1320 Type,
1321 Implementation,
1322}
1323
1324#[derive(Debug, Clone)]
1325enum InlayHintRefreshReason {
1326 ModifiersChanged(bool),
1327 Toggle(bool),
1328 SettingsChange(InlayHintSettings),
1329 NewLinesShown,
1330 BufferEdited(HashSet<Arc<Language>>),
1331 RefreshRequested,
1332 ExcerptsRemoved(Vec<ExcerptId>),
1333}
1334
1335impl InlayHintRefreshReason {
1336 fn description(&self) -> &'static str {
1337 match self {
1338 Self::ModifiersChanged(_) => "modifiers changed",
1339 Self::Toggle(_) => "toggle",
1340 Self::SettingsChange(_) => "settings change",
1341 Self::NewLinesShown => "new lines shown",
1342 Self::BufferEdited(_) => "buffer edited",
1343 Self::RefreshRequested => "refresh requested",
1344 Self::ExcerptsRemoved(_) => "excerpts removed",
1345 }
1346 }
1347}
1348
1349pub enum FormatTarget {
1350 Buffers,
1351 Ranges(Vec<Range<MultiBufferPoint>>),
1352}
1353
1354pub(crate) struct FocusedBlock {
1355 id: BlockId,
1356 focus_handle: WeakFocusHandle,
1357}
1358
1359#[derive(Clone)]
1360enum JumpData {
1361 MultiBufferRow {
1362 row: MultiBufferRow,
1363 line_offset_from_top: u32,
1364 },
1365 MultiBufferPoint {
1366 excerpt_id: ExcerptId,
1367 position: Point,
1368 anchor: text::Anchor,
1369 line_offset_from_top: u32,
1370 },
1371}
1372
1373pub enum MultibufferSelectionMode {
1374 First,
1375 All,
1376}
1377
1378#[derive(Clone, Copy, Debug, Default)]
1379pub struct RewrapOptions {
1380 pub override_language_settings: bool,
1381 pub preserve_existing_whitespace: bool,
1382}
1383
1384impl Editor {
1385 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1386 let buffer = cx.new(|cx| Buffer::local("", cx));
1387 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1388 Self::new(
1389 EditorMode::SingleLine { auto_width: false },
1390 buffer,
1391 None,
1392 window,
1393 cx,
1394 )
1395 }
1396
1397 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1398 let buffer = cx.new(|cx| Buffer::local("", cx));
1399 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1400 Self::new(EditorMode::full(), buffer, None, window, cx)
1401 }
1402
1403 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1404 let buffer = cx.new(|cx| Buffer::local("", cx));
1405 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1406 Self::new(
1407 EditorMode::SingleLine { auto_width: true },
1408 buffer,
1409 None,
1410 window,
1411 cx,
1412 )
1413 }
1414
1415 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1416 let buffer = cx.new(|cx| Buffer::local("", cx));
1417 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1418 Self::new(
1419 EditorMode::AutoHeight { max_lines },
1420 buffer,
1421 None,
1422 window,
1423 cx,
1424 )
1425 }
1426
1427 pub fn for_buffer(
1428 buffer: Entity<Buffer>,
1429 project: Option<Entity<Project>>,
1430 window: &mut Window,
1431 cx: &mut Context<Self>,
1432 ) -> Self {
1433 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1434 Self::new(EditorMode::full(), buffer, project, window, cx)
1435 }
1436
1437 pub fn for_multibuffer(
1438 buffer: Entity<MultiBuffer>,
1439 project: Option<Entity<Project>>,
1440 window: &mut Window,
1441 cx: &mut Context<Self>,
1442 ) -> Self {
1443 Self::new(EditorMode::full(), buffer, project, window, cx)
1444 }
1445
1446 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1447 let mut clone = Self::new(
1448 self.mode,
1449 self.buffer.clone(),
1450 self.project.clone(),
1451 window,
1452 cx,
1453 );
1454 self.display_map.update(cx, |display_map, cx| {
1455 let snapshot = display_map.snapshot(cx);
1456 clone.display_map.update(cx, |display_map, cx| {
1457 display_map.set_state(&snapshot, cx);
1458 });
1459 });
1460 clone.folds_did_change(cx);
1461 clone.selections.clone_state(&self.selections);
1462 clone.scroll_manager.clone_state(&self.scroll_manager);
1463 clone.searchable = self.searchable;
1464 clone.read_only = self.read_only;
1465 clone
1466 }
1467
1468 pub fn new(
1469 mode: EditorMode,
1470 buffer: Entity<MultiBuffer>,
1471 project: Option<Entity<Project>>,
1472 window: &mut Window,
1473 cx: &mut Context<Self>,
1474 ) -> Self {
1475 let style = window.text_style();
1476 let font_size = style.font_size.to_pixels(window.rem_size());
1477 let editor = cx.entity().downgrade();
1478 let fold_placeholder = FoldPlaceholder {
1479 constrain_width: true,
1480 render: Arc::new(move |fold_id, fold_range, cx| {
1481 let editor = editor.clone();
1482 div()
1483 .id(fold_id)
1484 .bg(cx.theme().colors().ghost_element_background)
1485 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1486 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1487 .rounded_xs()
1488 .size_full()
1489 .cursor_pointer()
1490 .child("⋯")
1491 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1492 .on_click(move |_, _window, cx| {
1493 editor
1494 .update(cx, |editor, cx| {
1495 editor.unfold_ranges(
1496 &[fold_range.start..fold_range.end],
1497 true,
1498 false,
1499 cx,
1500 );
1501 cx.stop_propagation();
1502 })
1503 .ok();
1504 })
1505 .into_any()
1506 }),
1507 merge_adjacent: true,
1508 ..Default::default()
1509 };
1510 let display_map = cx.new(|cx| {
1511 DisplayMap::new(
1512 buffer.clone(),
1513 style.font(),
1514 font_size,
1515 None,
1516 FILE_HEADER_HEIGHT,
1517 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1518 fold_placeholder,
1519 cx,
1520 )
1521 });
1522
1523 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1524
1525 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1526
1527 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1528 .then(|| language_settings::SoftWrap::None);
1529
1530 let mut project_subscriptions = Vec::new();
1531 if mode.is_full() {
1532 if let Some(project) = project.as_ref() {
1533 project_subscriptions.push(cx.subscribe_in(
1534 project,
1535 window,
1536 |editor, _, event, window, cx| match event {
1537 project::Event::RefreshCodeLens => {
1538 // we always query lens with actions, without storing them, always refreshing them
1539 }
1540 project::Event::RefreshInlayHints => {
1541 editor
1542 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1543 }
1544 project::Event::SnippetEdit(id, snippet_edits) => {
1545 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1546 let focus_handle = editor.focus_handle(cx);
1547 if focus_handle.is_focused(window) {
1548 let snapshot = buffer.read(cx).snapshot();
1549 for (range, snippet) in snippet_edits {
1550 let editor_range =
1551 language::range_from_lsp(*range).to_offset(&snapshot);
1552 editor
1553 .insert_snippet(
1554 &[editor_range],
1555 snippet.clone(),
1556 window,
1557 cx,
1558 )
1559 .ok();
1560 }
1561 }
1562 }
1563 }
1564 _ => {}
1565 },
1566 ));
1567 if let Some(task_inventory) = project
1568 .read(cx)
1569 .task_store()
1570 .read(cx)
1571 .task_inventory()
1572 .cloned()
1573 {
1574 project_subscriptions.push(cx.observe_in(
1575 &task_inventory,
1576 window,
1577 |editor, _, window, cx| {
1578 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1579 },
1580 ));
1581 };
1582
1583 project_subscriptions.push(cx.subscribe_in(
1584 &project.read(cx).breakpoint_store(),
1585 window,
1586 |editor, _, event, window, cx| match event {
1587 BreakpointStoreEvent::ClearDebugLines => {
1588 editor.clear_row_highlights::<ActiveDebugLine>();
1589 editor.refresh_inline_values(cx);
1590 }
1591 BreakpointStoreEvent::SetDebugLine => {
1592 if editor.go_to_active_debug_line(window, cx) {
1593 cx.stop_propagation();
1594 }
1595
1596 editor.refresh_inline_values(cx);
1597 }
1598 _ => {}
1599 },
1600 ));
1601 }
1602 }
1603
1604 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1605
1606 let inlay_hint_settings =
1607 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1608 let focus_handle = cx.focus_handle();
1609 cx.on_focus(&focus_handle, window, Self::handle_focus)
1610 .detach();
1611 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1612 .detach();
1613 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1614 .detach();
1615 cx.on_blur(&focus_handle, window, Self::handle_blur)
1616 .detach();
1617
1618 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1619 Some(false)
1620 } else {
1621 None
1622 };
1623
1624 let breakpoint_store = match (mode, project.as_ref()) {
1625 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1626 _ => None,
1627 };
1628
1629 let mut code_action_providers = Vec::new();
1630 let mut load_uncommitted_diff = None;
1631 if let Some(project) = project.clone() {
1632 load_uncommitted_diff = Some(
1633 update_uncommitted_diff_for_buffer(
1634 cx.entity(),
1635 &project,
1636 buffer.read(cx).all_buffers(),
1637 buffer.clone(),
1638 cx,
1639 )
1640 .shared(),
1641 );
1642 code_action_providers.push(Rc::new(project) as Rc<_>);
1643 }
1644
1645 let mut this = Self {
1646 focus_handle,
1647 show_cursor_when_unfocused: false,
1648 last_focused_descendant: None,
1649 buffer: buffer.clone(),
1650 display_map: display_map.clone(),
1651 selections,
1652 scroll_manager: ScrollManager::new(cx),
1653 columnar_selection_tail: None,
1654 add_selections_state: None,
1655 select_next_state: None,
1656 select_prev_state: None,
1657 selection_history: Default::default(),
1658 autoclose_regions: Default::default(),
1659 snippet_stack: Default::default(),
1660 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1661 ime_transaction: Default::default(),
1662 active_diagnostics: ActiveDiagnostic::None,
1663 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1664 inline_diagnostics_update: Task::ready(()),
1665 inline_diagnostics: Vec::new(),
1666 soft_wrap_mode_override,
1667 hard_wrap: None,
1668 completion_provider: project.clone().map(|project| Box::new(project) as _),
1669 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1670 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1671 project,
1672 blink_manager: blink_manager.clone(),
1673 show_local_selections: true,
1674 show_scrollbars: true,
1675 mode,
1676 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1677 show_gutter: mode.is_full(),
1678 show_line_numbers: None,
1679 use_relative_line_numbers: None,
1680 disable_expand_excerpt_buttons: false,
1681 show_git_diff_gutter: None,
1682 show_code_actions: None,
1683 show_runnables: None,
1684 show_breakpoints: None,
1685 show_wrap_guides: None,
1686 show_indent_guides,
1687 placeholder_text: None,
1688 highlight_order: 0,
1689 highlighted_rows: HashMap::default(),
1690 background_highlights: Default::default(),
1691 gutter_highlights: TreeMap::default(),
1692 scrollbar_marker_state: ScrollbarMarkerState::default(),
1693 active_indent_guides_state: ActiveIndentGuidesState::default(),
1694 nav_history: None,
1695 context_menu: RefCell::new(None),
1696 context_menu_options: None,
1697 mouse_context_menu: None,
1698 completion_tasks: Default::default(),
1699 inline_blame_popover: Default::default(),
1700 signature_help_state: SignatureHelpState::default(),
1701 auto_signature_help: None,
1702 find_all_references_task_sources: Vec::new(),
1703 next_completion_id: 0,
1704 next_inlay_id: 0,
1705 code_action_providers,
1706 available_code_actions: Default::default(),
1707 code_actions_task: Default::default(),
1708 quick_selection_highlight_task: Default::default(),
1709 debounced_selection_highlight_task: Default::default(),
1710 document_highlights_task: Default::default(),
1711 linked_editing_range_task: Default::default(),
1712 pending_rename: Default::default(),
1713 searchable: true,
1714 cursor_shape: EditorSettings::get_global(cx)
1715 .cursor_shape
1716 .unwrap_or_default(),
1717 current_line_highlight: None,
1718 autoindent_mode: Some(AutoindentMode::EachLine),
1719 collapse_matches: false,
1720 workspace: None,
1721 input_enabled: true,
1722 use_modal_editing: mode.is_full(),
1723 read_only: false,
1724 use_autoclose: true,
1725 use_auto_surround: true,
1726 auto_replace_emoji_shortcode: false,
1727 jsx_tag_auto_close_enabled_in_any_buffer: false,
1728 leader_id: None,
1729 remote_id: None,
1730 hover_state: Default::default(),
1731 pending_mouse_down: None,
1732 hovered_link_state: Default::default(),
1733 edit_prediction_provider: None,
1734 active_inline_completion: None,
1735 stale_inline_completion_in_menu: None,
1736 edit_prediction_preview: EditPredictionPreview::Inactive {
1737 released_too_fast: false,
1738 },
1739 inline_diagnostics_enabled: mode.is_full(),
1740 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1741 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1742
1743 gutter_hovered: false,
1744 pixel_position_of_newest_cursor: None,
1745 last_bounds: None,
1746 last_position_map: None,
1747 expect_bounds_change: None,
1748 gutter_dimensions: GutterDimensions::default(),
1749 style: None,
1750 show_cursor_names: false,
1751 hovered_cursors: Default::default(),
1752 next_editor_action_id: EditorActionId::default(),
1753 editor_actions: Rc::default(),
1754 inline_completions_hidden_for_vim_mode: false,
1755 show_inline_completions_override: None,
1756 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1757 edit_prediction_settings: EditPredictionSettings::Disabled,
1758 edit_prediction_indent_conflict: false,
1759 edit_prediction_requires_modifier_in_indent_conflict: true,
1760 custom_context_menu: None,
1761 show_git_blame_gutter: false,
1762 show_git_blame_inline: false,
1763 show_selection_menu: None,
1764 show_git_blame_inline_delay_task: None,
1765 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1766 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1767 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1768 .session
1769 .restore_unsaved_buffers,
1770 blame: None,
1771 blame_subscription: None,
1772 tasks: Default::default(),
1773
1774 breakpoint_store,
1775 gutter_breakpoint_indicator: (None, None),
1776 _subscriptions: vec![
1777 cx.observe(&buffer, Self::on_buffer_changed),
1778 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1779 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1780 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1781 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1782 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1783 cx.observe_window_activation(window, |editor, window, cx| {
1784 let active = window.is_window_active();
1785 editor.blink_manager.update(cx, |blink_manager, cx| {
1786 if active {
1787 blink_manager.enable(cx);
1788 } else {
1789 blink_manager.disable(cx);
1790 }
1791 });
1792 }),
1793 ],
1794 tasks_update_task: None,
1795 linked_edit_ranges: Default::default(),
1796 in_project_search: false,
1797 previous_search_ranges: None,
1798 breadcrumb_header: None,
1799 focused_block: None,
1800 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1801 addons: HashMap::default(),
1802 registered_buffers: HashMap::default(),
1803 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1804 selection_mark_mode: false,
1805 toggle_fold_multiple_buffers: Task::ready(()),
1806 serialize_selections: Task::ready(()),
1807 serialize_folds: Task::ready(()),
1808 text_style_refinement: None,
1809 load_diff_task: load_uncommitted_diff,
1810 temporary_diff_override: false,
1811 mouse_cursor_hidden: false,
1812 hide_mouse_mode: EditorSettings::get_global(cx)
1813 .hide_mouse
1814 .unwrap_or_default(),
1815 change_list: ChangeList::new(),
1816 };
1817 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1818 this._subscriptions
1819 .push(cx.observe(breakpoints, |_, _, cx| {
1820 cx.notify();
1821 }));
1822 }
1823 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1824 this._subscriptions.extend(project_subscriptions);
1825
1826 this._subscriptions.push(cx.subscribe_in(
1827 &cx.entity(),
1828 window,
1829 |editor, _, e: &EditorEvent, window, cx| match e {
1830 EditorEvent::ScrollPositionChanged { local, .. } => {
1831 if *local {
1832 let new_anchor = editor.scroll_manager.anchor();
1833 let snapshot = editor.snapshot(window, cx);
1834 editor.update_restoration_data(cx, move |data| {
1835 data.scroll_position = (
1836 new_anchor.top_row(&snapshot.buffer_snapshot),
1837 new_anchor.offset,
1838 );
1839 });
1840 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1841 editor.inline_blame_popover.take();
1842 }
1843 }
1844 EditorEvent::Edited { .. } => {
1845 if !vim_enabled(cx) {
1846 let (map, selections) = editor.selections.all_adjusted_display(cx);
1847 let pop_state = editor
1848 .change_list
1849 .last()
1850 .map(|previous| {
1851 previous.len() == selections.len()
1852 && previous.iter().enumerate().all(|(ix, p)| {
1853 p.to_display_point(&map).row()
1854 == selections[ix].head().row()
1855 })
1856 })
1857 .unwrap_or(false);
1858 let new_positions = selections
1859 .into_iter()
1860 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1861 .collect();
1862 editor
1863 .change_list
1864 .push_to_change_list(pop_state, new_positions);
1865 }
1866 }
1867 _ => (),
1868 },
1869 ));
1870
1871 if let Some(dap_store) = this
1872 .project
1873 .as_ref()
1874 .map(|project| project.read(cx).dap_store())
1875 {
1876 let weak_editor = cx.weak_entity();
1877
1878 this._subscriptions
1879 .push(
1880 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1881 let session_entity = cx.entity();
1882 weak_editor
1883 .update(cx, |editor, cx| {
1884 editor._subscriptions.push(
1885 cx.subscribe(&session_entity, Self::on_debug_session_event),
1886 );
1887 })
1888 .ok();
1889 }),
1890 );
1891
1892 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1893 this._subscriptions
1894 .push(cx.subscribe(&session, Self::on_debug_session_event));
1895 }
1896 }
1897
1898 this.end_selection(window, cx);
1899 this.scroll_manager.show_scrollbars(window, cx);
1900 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1901
1902 if mode.is_full() {
1903 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1904 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1905
1906 if this.git_blame_inline_enabled {
1907 this.git_blame_inline_enabled = true;
1908 this.start_git_blame_inline(false, window, cx);
1909 }
1910
1911 this.go_to_active_debug_line(window, cx);
1912
1913 if let Some(buffer) = buffer.read(cx).as_singleton() {
1914 if let Some(project) = this.project.as_ref() {
1915 let handle = project.update(cx, |project, cx| {
1916 project.register_buffer_with_language_servers(&buffer, cx)
1917 });
1918 this.registered_buffers
1919 .insert(buffer.read(cx).remote_id(), handle);
1920 }
1921 }
1922 }
1923
1924 this.report_editor_event("Editor Opened", None, cx);
1925 this
1926 }
1927
1928 pub fn deploy_mouse_context_menu(
1929 &mut self,
1930 position: gpui::Point<Pixels>,
1931 context_menu: Entity<ContextMenu>,
1932 window: &mut Window,
1933 cx: &mut Context<Self>,
1934 ) {
1935 self.mouse_context_menu = Some(MouseContextMenu::new(
1936 self,
1937 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1938 context_menu,
1939 window,
1940 cx,
1941 ));
1942 }
1943
1944 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1945 self.mouse_context_menu
1946 .as_ref()
1947 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1948 }
1949
1950 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1951 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1952 }
1953
1954 fn key_context_internal(
1955 &self,
1956 has_active_edit_prediction: bool,
1957 window: &Window,
1958 cx: &App,
1959 ) -> KeyContext {
1960 let mut key_context = KeyContext::new_with_defaults();
1961 key_context.add("Editor");
1962 let mode = match self.mode {
1963 EditorMode::SingleLine { .. } => "single_line",
1964 EditorMode::AutoHeight { .. } => "auto_height",
1965 EditorMode::Full { .. } => "full",
1966 };
1967
1968 if EditorSettings::jupyter_enabled(cx) {
1969 key_context.add("jupyter");
1970 }
1971
1972 key_context.set("mode", mode);
1973 if self.pending_rename.is_some() {
1974 key_context.add("renaming");
1975 }
1976
1977 match self.context_menu.borrow().as_ref() {
1978 Some(CodeContextMenu::Completions(_)) => {
1979 key_context.add("menu");
1980 key_context.add("showing_completions");
1981 }
1982 Some(CodeContextMenu::CodeActions(_)) => {
1983 key_context.add("menu");
1984 key_context.add("showing_code_actions")
1985 }
1986 None => {}
1987 }
1988
1989 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1990 if !self.focus_handle(cx).contains_focused(window, cx)
1991 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1992 {
1993 for addon in self.addons.values() {
1994 addon.extend_key_context(&mut key_context, cx)
1995 }
1996 }
1997
1998 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1999 if let Some(extension) = singleton_buffer
2000 .read(cx)
2001 .file()
2002 .and_then(|file| file.path().extension()?.to_str())
2003 {
2004 key_context.set("extension", extension.to_string());
2005 }
2006 } else {
2007 key_context.add("multibuffer");
2008 }
2009
2010 if has_active_edit_prediction {
2011 if self.edit_prediction_in_conflict() {
2012 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2013 } else {
2014 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2015 key_context.add("copilot_suggestion");
2016 }
2017 }
2018
2019 if self.selection_mark_mode {
2020 key_context.add("selection_mode");
2021 }
2022
2023 key_context
2024 }
2025
2026 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2027 self.mouse_cursor_hidden = match origin {
2028 HideMouseCursorOrigin::TypingAction => {
2029 matches!(
2030 self.hide_mouse_mode,
2031 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2032 )
2033 }
2034 HideMouseCursorOrigin::MovementAction => {
2035 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2036 }
2037 };
2038 }
2039
2040 pub fn edit_prediction_in_conflict(&self) -> bool {
2041 if !self.show_edit_predictions_in_menu() {
2042 return false;
2043 }
2044
2045 let showing_completions = self
2046 .context_menu
2047 .borrow()
2048 .as_ref()
2049 .map_or(false, |context| {
2050 matches!(context, CodeContextMenu::Completions(_))
2051 });
2052
2053 showing_completions
2054 || self.edit_prediction_requires_modifier()
2055 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2056 // bindings to insert tab characters.
2057 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2058 }
2059
2060 pub fn accept_edit_prediction_keybind(
2061 &self,
2062 window: &Window,
2063 cx: &App,
2064 ) -> AcceptEditPredictionBinding {
2065 let key_context = self.key_context_internal(true, window, cx);
2066 let in_conflict = self.edit_prediction_in_conflict();
2067
2068 AcceptEditPredictionBinding(
2069 window
2070 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2071 .into_iter()
2072 .filter(|binding| {
2073 !in_conflict
2074 || binding
2075 .keystrokes()
2076 .first()
2077 .map_or(false, |keystroke| keystroke.modifiers.modified())
2078 })
2079 .rev()
2080 .min_by_key(|binding| {
2081 binding
2082 .keystrokes()
2083 .first()
2084 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2085 }),
2086 )
2087 }
2088
2089 pub fn new_file(
2090 workspace: &mut Workspace,
2091 _: &workspace::NewFile,
2092 window: &mut Window,
2093 cx: &mut Context<Workspace>,
2094 ) {
2095 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2096 "Failed to create buffer",
2097 window,
2098 cx,
2099 |e, _, _| match e.error_code() {
2100 ErrorCode::RemoteUpgradeRequired => Some(format!(
2101 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2102 e.error_tag("required").unwrap_or("the latest version")
2103 )),
2104 _ => None,
2105 },
2106 );
2107 }
2108
2109 pub fn new_in_workspace(
2110 workspace: &mut Workspace,
2111 window: &mut Window,
2112 cx: &mut Context<Workspace>,
2113 ) -> Task<Result<Entity<Editor>>> {
2114 let project = workspace.project().clone();
2115 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2116
2117 cx.spawn_in(window, async move |workspace, cx| {
2118 let buffer = create.await?;
2119 workspace.update_in(cx, |workspace, window, cx| {
2120 let editor =
2121 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2122 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2123 editor
2124 })
2125 })
2126 }
2127
2128 fn new_file_vertical(
2129 workspace: &mut Workspace,
2130 _: &workspace::NewFileSplitVertical,
2131 window: &mut Window,
2132 cx: &mut Context<Workspace>,
2133 ) {
2134 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2135 }
2136
2137 fn new_file_horizontal(
2138 workspace: &mut Workspace,
2139 _: &workspace::NewFileSplitHorizontal,
2140 window: &mut Window,
2141 cx: &mut Context<Workspace>,
2142 ) {
2143 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2144 }
2145
2146 fn new_file_in_direction(
2147 workspace: &mut Workspace,
2148 direction: SplitDirection,
2149 window: &mut Window,
2150 cx: &mut Context<Workspace>,
2151 ) {
2152 let project = workspace.project().clone();
2153 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2154
2155 cx.spawn_in(window, async move |workspace, cx| {
2156 let buffer = create.await?;
2157 workspace.update_in(cx, move |workspace, window, cx| {
2158 workspace.split_item(
2159 direction,
2160 Box::new(
2161 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2162 ),
2163 window,
2164 cx,
2165 )
2166 })?;
2167 anyhow::Ok(())
2168 })
2169 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2170 match e.error_code() {
2171 ErrorCode::RemoteUpgradeRequired => Some(format!(
2172 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2173 e.error_tag("required").unwrap_or("the latest version")
2174 )),
2175 _ => None,
2176 }
2177 });
2178 }
2179
2180 pub fn leader_id(&self) -> Option<CollaboratorId> {
2181 self.leader_id
2182 }
2183
2184 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2185 &self.buffer
2186 }
2187
2188 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2189 self.workspace.as_ref()?.0.upgrade()
2190 }
2191
2192 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2193 self.buffer().read(cx).title(cx)
2194 }
2195
2196 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2197 let git_blame_gutter_max_author_length = self
2198 .render_git_blame_gutter(cx)
2199 .then(|| {
2200 if let Some(blame) = self.blame.as_ref() {
2201 let max_author_length =
2202 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2203 Some(max_author_length)
2204 } else {
2205 None
2206 }
2207 })
2208 .flatten();
2209
2210 EditorSnapshot {
2211 mode: self.mode,
2212 show_gutter: self.show_gutter,
2213 show_line_numbers: self.show_line_numbers,
2214 show_git_diff_gutter: self.show_git_diff_gutter,
2215 show_code_actions: self.show_code_actions,
2216 show_runnables: self.show_runnables,
2217 show_breakpoints: self.show_breakpoints,
2218 git_blame_gutter_max_author_length,
2219 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2220 scroll_anchor: self.scroll_manager.anchor(),
2221 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2222 placeholder_text: self.placeholder_text.clone(),
2223 is_focused: self.focus_handle.is_focused(window),
2224 current_line_highlight: self
2225 .current_line_highlight
2226 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2227 gutter_hovered: self.gutter_hovered,
2228 }
2229 }
2230
2231 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2232 self.buffer.read(cx).language_at(point, cx)
2233 }
2234
2235 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2236 self.buffer.read(cx).read(cx).file_at(point).cloned()
2237 }
2238
2239 pub fn active_excerpt(
2240 &self,
2241 cx: &App,
2242 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2243 self.buffer
2244 .read(cx)
2245 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2246 }
2247
2248 pub fn mode(&self) -> EditorMode {
2249 self.mode
2250 }
2251
2252 pub fn set_mode(&mut self, mode: EditorMode) {
2253 self.mode = mode;
2254 }
2255
2256 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2257 self.collaboration_hub.as_deref()
2258 }
2259
2260 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2261 self.collaboration_hub = Some(hub);
2262 }
2263
2264 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2265 self.in_project_search = in_project_search;
2266 }
2267
2268 pub fn set_custom_context_menu(
2269 &mut self,
2270 f: impl 'static
2271 + Fn(
2272 &mut Self,
2273 DisplayPoint,
2274 &mut Window,
2275 &mut Context<Self>,
2276 ) -> Option<Entity<ui::ContextMenu>>,
2277 ) {
2278 self.custom_context_menu = Some(Box::new(f))
2279 }
2280
2281 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2282 self.completion_provider = provider;
2283 }
2284
2285 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2286 self.semantics_provider.clone()
2287 }
2288
2289 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2290 self.semantics_provider = provider;
2291 }
2292
2293 pub fn set_edit_prediction_provider<T>(
2294 &mut self,
2295 provider: Option<Entity<T>>,
2296 window: &mut Window,
2297 cx: &mut Context<Self>,
2298 ) where
2299 T: EditPredictionProvider,
2300 {
2301 self.edit_prediction_provider =
2302 provider.map(|provider| RegisteredInlineCompletionProvider {
2303 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2304 if this.focus_handle.is_focused(window) {
2305 this.update_visible_inline_completion(window, cx);
2306 }
2307 }),
2308 provider: Arc::new(provider),
2309 });
2310 self.update_edit_prediction_settings(cx);
2311 self.refresh_inline_completion(false, false, window, cx);
2312 }
2313
2314 pub fn placeholder_text(&self) -> Option<&str> {
2315 self.placeholder_text.as_deref()
2316 }
2317
2318 pub fn set_placeholder_text(
2319 &mut self,
2320 placeholder_text: impl Into<Arc<str>>,
2321 cx: &mut Context<Self>,
2322 ) {
2323 let placeholder_text = Some(placeholder_text.into());
2324 if self.placeholder_text != placeholder_text {
2325 self.placeholder_text = placeholder_text;
2326 cx.notify();
2327 }
2328 }
2329
2330 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2331 self.cursor_shape = cursor_shape;
2332
2333 // Disrupt blink for immediate user feedback that the cursor shape has changed
2334 self.blink_manager.update(cx, BlinkManager::show_cursor);
2335
2336 cx.notify();
2337 }
2338
2339 pub fn set_current_line_highlight(
2340 &mut self,
2341 current_line_highlight: Option<CurrentLineHighlight>,
2342 ) {
2343 self.current_line_highlight = current_line_highlight;
2344 }
2345
2346 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2347 self.collapse_matches = collapse_matches;
2348 }
2349
2350 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2351 let buffers = self.buffer.read(cx).all_buffers();
2352 let Some(project) = self.project.as_ref() else {
2353 return;
2354 };
2355 project.update(cx, |project, cx| {
2356 for buffer in buffers {
2357 self.registered_buffers
2358 .entry(buffer.read(cx).remote_id())
2359 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2360 }
2361 })
2362 }
2363
2364 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2365 if self.collapse_matches {
2366 return range.start..range.start;
2367 }
2368 range.clone()
2369 }
2370
2371 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2372 if self.display_map.read(cx).clip_at_line_ends != clip {
2373 self.display_map
2374 .update(cx, |map, _| map.clip_at_line_ends = clip);
2375 }
2376 }
2377
2378 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2379 self.input_enabled = input_enabled;
2380 }
2381
2382 pub fn set_inline_completions_hidden_for_vim_mode(
2383 &mut self,
2384 hidden: bool,
2385 window: &mut Window,
2386 cx: &mut Context<Self>,
2387 ) {
2388 if hidden != self.inline_completions_hidden_for_vim_mode {
2389 self.inline_completions_hidden_for_vim_mode = hidden;
2390 if hidden {
2391 self.update_visible_inline_completion(window, cx);
2392 } else {
2393 self.refresh_inline_completion(true, false, window, cx);
2394 }
2395 }
2396 }
2397
2398 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2399 self.menu_inline_completions_policy = value;
2400 }
2401
2402 pub fn set_autoindent(&mut self, autoindent: bool) {
2403 if autoindent {
2404 self.autoindent_mode = Some(AutoindentMode::EachLine);
2405 } else {
2406 self.autoindent_mode = None;
2407 }
2408 }
2409
2410 pub fn read_only(&self, cx: &App) -> bool {
2411 self.read_only || self.buffer.read(cx).read_only()
2412 }
2413
2414 pub fn set_read_only(&mut self, read_only: bool) {
2415 self.read_only = read_only;
2416 }
2417
2418 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2419 self.use_autoclose = autoclose;
2420 }
2421
2422 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2423 self.use_auto_surround = auto_surround;
2424 }
2425
2426 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2427 self.auto_replace_emoji_shortcode = auto_replace;
2428 }
2429
2430 pub fn toggle_edit_predictions(
2431 &mut self,
2432 _: &ToggleEditPrediction,
2433 window: &mut Window,
2434 cx: &mut Context<Self>,
2435 ) {
2436 if self.show_inline_completions_override.is_some() {
2437 self.set_show_edit_predictions(None, window, cx);
2438 } else {
2439 let show_edit_predictions = !self.edit_predictions_enabled();
2440 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2441 }
2442 }
2443
2444 pub fn set_show_edit_predictions(
2445 &mut self,
2446 show_edit_predictions: Option<bool>,
2447 window: &mut Window,
2448 cx: &mut Context<Self>,
2449 ) {
2450 self.show_inline_completions_override = show_edit_predictions;
2451 self.update_edit_prediction_settings(cx);
2452
2453 if let Some(false) = show_edit_predictions {
2454 self.discard_inline_completion(false, cx);
2455 } else {
2456 self.refresh_inline_completion(false, true, window, cx);
2457 }
2458 }
2459
2460 fn inline_completions_disabled_in_scope(
2461 &self,
2462 buffer: &Entity<Buffer>,
2463 buffer_position: language::Anchor,
2464 cx: &App,
2465 ) -> bool {
2466 let snapshot = buffer.read(cx).snapshot();
2467 let settings = snapshot.settings_at(buffer_position, cx);
2468
2469 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2470 return false;
2471 };
2472
2473 scope.override_name().map_or(false, |scope_name| {
2474 settings
2475 .edit_predictions_disabled_in
2476 .iter()
2477 .any(|s| s == scope_name)
2478 })
2479 }
2480
2481 pub fn set_use_modal_editing(&mut self, to: bool) {
2482 self.use_modal_editing = to;
2483 }
2484
2485 pub fn use_modal_editing(&self) -> bool {
2486 self.use_modal_editing
2487 }
2488
2489 fn selections_did_change(
2490 &mut self,
2491 local: bool,
2492 old_cursor_position: &Anchor,
2493 show_completions: bool,
2494 window: &mut Window,
2495 cx: &mut Context<Self>,
2496 ) {
2497 window.invalidate_character_coordinates();
2498
2499 // Copy selections to primary selection buffer
2500 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2501 if local {
2502 let selections = self.selections.all::<usize>(cx);
2503 let buffer_handle = self.buffer.read(cx).read(cx);
2504
2505 let mut text = String::new();
2506 for (index, selection) in selections.iter().enumerate() {
2507 let text_for_selection = buffer_handle
2508 .text_for_range(selection.start..selection.end)
2509 .collect::<String>();
2510
2511 text.push_str(&text_for_selection);
2512 if index != selections.len() - 1 {
2513 text.push('\n');
2514 }
2515 }
2516
2517 if !text.is_empty() {
2518 cx.write_to_primary(ClipboardItem::new_string(text));
2519 }
2520 }
2521
2522 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2523 self.buffer.update(cx, |buffer, cx| {
2524 buffer.set_active_selections(
2525 &self.selections.disjoint_anchors(),
2526 self.selections.line_mode,
2527 self.cursor_shape,
2528 cx,
2529 )
2530 });
2531 }
2532 let display_map = self
2533 .display_map
2534 .update(cx, |display_map, cx| display_map.snapshot(cx));
2535 let buffer = &display_map.buffer_snapshot;
2536 self.add_selections_state = None;
2537 self.select_next_state = None;
2538 self.select_prev_state = None;
2539 self.select_syntax_node_history.try_clear();
2540 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2541 self.snippet_stack
2542 .invalidate(&self.selections.disjoint_anchors(), buffer);
2543 self.take_rename(false, window, cx);
2544
2545 let new_cursor_position = self.selections.newest_anchor().head();
2546
2547 self.push_to_nav_history(
2548 *old_cursor_position,
2549 Some(new_cursor_position.to_point(buffer)),
2550 false,
2551 cx,
2552 );
2553
2554 if local {
2555 let new_cursor_position = self.selections.newest_anchor().head();
2556 let mut context_menu = self.context_menu.borrow_mut();
2557 let completion_menu = match context_menu.as_ref() {
2558 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2559 _ => {
2560 *context_menu = None;
2561 None
2562 }
2563 };
2564 if let Some(buffer_id) = new_cursor_position.buffer_id {
2565 if !self.registered_buffers.contains_key(&buffer_id) {
2566 if let Some(project) = self.project.as_ref() {
2567 project.update(cx, |project, cx| {
2568 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2569 return;
2570 };
2571 self.registered_buffers.insert(
2572 buffer_id,
2573 project.register_buffer_with_language_servers(&buffer, cx),
2574 );
2575 })
2576 }
2577 }
2578 }
2579
2580 if let Some(completion_menu) = completion_menu {
2581 let cursor_position = new_cursor_position.to_offset(buffer);
2582 let (word_range, kind) =
2583 buffer.surrounding_word(completion_menu.initial_position, true);
2584 if kind == Some(CharKind::Word)
2585 && word_range.to_inclusive().contains(&cursor_position)
2586 {
2587 let mut completion_menu = completion_menu.clone();
2588 drop(context_menu);
2589
2590 let query = Self::completion_query(buffer, cursor_position);
2591 cx.spawn(async move |this, cx| {
2592 completion_menu
2593 .filter(query.as_deref(), cx.background_executor().clone())
2594 .await;
2595
2596 this.update(cx, |this, cx| {
2597 let mut context_menu = this.context_menu.borrow_mut();
2598 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2599 else {
2600 return;
2601 };
2602
2603 if menu.id > completion_menu.id {
2604 return;
2605 }
2606
2607 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2608 drop(context_menu);
2609 cx.notify();
2610 })
2611 })
2612 .detach();
2613
2614 if show_completions {
2615 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2616 }
2617 } else {
2618 drop(context_menu);
2619 self.hide_context_menu(window, cx);
2620 }
2621 } else {
2622 drop(context_menu);
2623 }
2624
2625 hide_hover(self, cx);
2626
2627 if old_cursor_position.to_display_point(&display_map).row()
2628 != new_cursor_position.to_display_point(&display_map).row()
2629 {
2630 self.available_code_actions.take();
2631 }
2632 self.refresh_code_actions(window, cx);
2633 self.refresh_document_highlights(cx);
2634 self.refresh_selected_text_highlights(false, window, cx);
2635 refresh_matching_bracket_highlights(self, window, cx);
2636 self.update_visible_inline_completion(window, cx);
2637 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2638 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2639 self.inline_blame_popover.take();
2640 if self.git_blame_inline_enabled {
2641 self.start_inline_blame_timer(window, cx);
2642 }
2643 }
2644
2645 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2646 cx.emit(EditorEvent::SelectionsChanged { local });
2647
2648 let selections = &self.selections.disjoint;
2649 if selections.len() == 1 {
2650 cx.emit(SearchEvent::ActiveMatchChanged)
2651 }
2652 if local {
2653 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2654 let inmemory_selections = selections
2655 .iter()
2656 .map(|s| {
2657 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2658 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2659 })
2660 .collect();
2661 self.update_restoration_data(cx, |data| {
2662 data.selections = inmemory_selections;
2663 });
2664
2665 if WorkspaceSettings::get(None, cx).restore_on_startup
2666 != RestoreOnStartupBehavior::None
2667 {
2668 if let Some(workspace_id) =
2669 self.workspace.as_ref().and_then(|workspace| workspace.1)
2670 {
2671 let snapshot = self.buffer().read(cx).snapshot(cx);
2672 let selections = selections.clone();
2673 let background_executor = cx.background_executor().clone();
2674 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2675 self.serialize_selections = cx.background_spawn(async move {
2676 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2677 let db_selections = selections
2678 .iter()
2679 .map(|selection| {
2680 (
2681 selection.start.to_offset(&snapshot),
2682 selection.end.to_offset(&snapshot),
2683 )
2684 })
2685 .collect();
2686
2687 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2688 .await
2689 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2690 .log_err();
2691 });
2692 }
2693 }
2694 }
2695 }
2696
2697 cx.notify();
2698 }
2699
2700 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2701 use text::ToOffset as _;
2702 use text::ToPoint as _;
2703
2704 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2705 return;
2706 }
2707
2708 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2709 return;
2710 };
2711
2712 let snapshot = singleton.read(cx).snapshot();
2713 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2714 let display_snapshot = display_map.snapshot(cx);
2715
2716 display_snapshot
2717 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2718 .map(|fold| {
2719 fold.range.start.text_anchor.to_point(&snapshot)
2720 ..fold.range.end.text_anchor.to_point(&snapshot)
2721 })
2722 .collect()
2723 });
2724 self.update_restoration_data(cx, |data| {
2725 data.folds = inmemory_folds;
2726 });
2727
2728 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2729 return;
2730 };
2731 let background_executor = cx.background_executor().clone();
2732 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2733 let db_folds = self.display_map.update(cx, |display_map, cx| {
2734 display_map
2735 .snapshot(cx)
2736 .folds_in_range(0..snapshot.len())
2737 .map(|fold| {
2738 (
2739 fold.range.start.text_anchor.to_offset(&snapshot),
2740 fold.range.end.text_anchor.to_offset(&snapshot),
2741 )
2742 })
2743 .collect()
2744 });
2745 self.serialize_folds = cx.background_spawn(async move {
2746 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2747 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2748 .await
2749 .with_context(|| {
2750 format!(
2751 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2752 )
2753 })
2754 .log_err();
2755 });
2756 }
2757
2758 pub fn sync_selections(
2759 &mut self,
2760 other: Entity<Editor>,
2761 cx: &mut Context<Self>,
2762 ) -> gpui::Subscription {
2763 let other_selections = other.read(cx).selections.disjoint.to_vec();
2764 self.selections.change_with(cx, |selections| {
2765 selections.select_anchors(other_selections);
2766 });
2767
2768 let other_subscription =
2769 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2770 EditorEvent::SelectionsChanged { local: true } => {
2771 let other_selections = other.read(cx).selections.disjoint.to_vec();
2772 if other_selections.is_empty() {
2773 return;
2774 }
2775 this.selections.change_with(cx, |selections| {
2776 selections.select_anchors(other_selections);
2777 });
2778 }
2779 _ => {}
2780 });
2781
2782 let this_subscription =
2783 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2784 EditorEvent::SelectionsChanged { local: true } => {
2785 let these_selections = this.selections.disjoint.to_vec();
2786 if these_selections.is_empty() {
2787 return;
2788 }
2789 other.update(cx, |other_editor, cx| {
2790 other_editor.selections.change_with(cx, |selections| {
2791 selections.select_anchors(these_selections);
2792 })
2793 });
2794 }
2795 _ => {}
2796 });
2797
2798 Subscription::join(other_subscription, this_subscription)
2799 }
2800
2801 pub fn change_selections<R>(
2802 &mut self,
2803 autoscroll: Option<Autoscroll>,
2804 window: &mut Window,
2805 cx: &mut Context<Self>,
2806 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2807 ) -> R {
2808 self.change_selections_inner(autoscroll, true, window, cx, change)
2809 }
2810
2811 fn change_selections_inner<R>(
2812 &mut self,
2813 autoscroll: Option<Autoscroll>,
2814 request_completions: bool,
2815 window: &mut Window,
2816 cx: &mut Context<Self>,
2817 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2818 ) -> R {
2819 let old_cursor_position = self.selections.newest_anchor().head();
2820 self.push_to_selection_history();
2821
2822 let (changed, result) = self.selections.change_with(cx, change);
2823
2824 if changed {
2825 if let Some(autoscroll) = autoscroll {
2826 self.request_autoscroll(autoscroll, cx);
2827 }
2828 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2829
2830 if self.should_open_signature_help_automatically(
2831 &old_cursor_position,
2832 self.signature_help_state.backspace_pressed(),
2833 cx,
2834 ) {
2835 self.show_signature_help(&ShowSignatureHelp, window, cx);
2836 }
2837 self.signature_help_state.set_backspace_pressed(false);
2838 }
2839
2840 result
2841 }
2842
2843 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2844 where
2845 I: IntoIterator<Item = (Range<S>, T)>,
2846 S: ToOffset,
2847 T: Into<Arc<str>>,
2848 {
2849 if self.read_only(cx) {
2850 return;
2851 }
2852
2853 self.buffer
2854 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2855 }
2856
2857 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2858 where
2859 I: IntoIterator<Item = (Range<S>, T)>,
2860 S: ToOffset,
2861 T: Into<Arc<str>>,
2862 {
2863 if self.read_only(cx) {
2864 return;
2865 }
2866
2867 self.buffer.update(cx, |buffer, cx| {
2868 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2869 });
2870 }
2871
2872 pub fn edit_with_block_indent<I, S, T>(
2873 &mut self,
2874 edits: I,
2875 original_indent_columns: Vec<Option<u32>>,
2876 cx: &mut Context<Self>,
2877 ) where
2878 I: IntoIterator<Item = (Range<S>, T)>,
2879 S: ToOffset,
2880 T: Into<Arc<str>>,
2881 {
2882 if self.read_only(cx) {
2883 return;
2884 }
2885
2886 self.buffer.update(cx, |buffer, cx| {
2887 buffer.edit(
2888 edits,
2889 Some(AutoindentMode::Block {
2890 original_indent_columns,
2891 }),
2892 cx,
2893 )
2894 });
2895 }
2896
2897 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2898 self.hide_context_menu(window, cx);
2899
2900 match phase {
2901 SelectPhase::Begin {
2902 position,
2903 add,
2904 click_count,
2905 } => self.begin_selection(position, add, click_count, window, cx),
2906 SelectPhase::BeginColumnar {
2907 position,
2908 goal_column,
2909 reset,
2910 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2911 SelectPhase::Extend {
2912 position,
2913 click_count,
2914 } => self.extend_selection(position, click_count, window, cx),
2915 SelectPhase::Update {
2916 position,
2917 goal_column,
2918 scroll_delta,
2919 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2920 SelectPhase::End => self.end_selection(window, cx),
2921 }
2922 }
2923
2924 fn extend_selection(
2925 &mut self,
2926 position: DisplayPoint,
2927 click_count: usize,
2928 window: &mut Window,
2929 cx: &mut Context<Self>,
2930 ) {
2931 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2932 let tail = self.selections.newest::<usize>(cx).tail();
2933 self.begin_selection(position, false, click_count, window, cx);
2934
2935 let position = position.to_offset(&display_map, Bias::Left);
2936 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2937
2938 let mut pending_selection = self
2939 .selections
2940 .pending_anchor()
2941 .expect("extend_selection not called with pending selection");
2942 if position >= tail {
2943 pending_selection.start = tail_anchor;
2944 } else {
2945 pending_selection.end = tail_anchor;
2946 pending_selection.reversed = true;
2947 }
2948
2949 let mut pending_mode = self.selections.pending_mode().unwrap();
2950 match &mut pending_mode {
2951 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2952 _ => {}
2953 }
2954
2955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2956 s.set_pending(pending_selection, pending_mode)
2957 });
2958 }
2959
2960 fn begin_selection(
2961 &mut self,
2962 position: DisplayPoint,
2963 add: bool,
2964 click_count: usize,
2965 window: &mut Window,
2966 cx: &mut Context<Self>,
2967 ) {
2968 if !self.focus_handle.is_focused(window) {
2969 self.last_focused_descendant = None;
2970 window.focus(&self.focus_handle);
2971 }
2972
2973 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2974 let buffer = &display_map.buffer_snapshot;
2975 let newest_selection = self.selections.newest_anchor().clone();
2976 let position = display_map.clip_point(position, Bias::Left);
2977
2978 let start;
2979 let end;
2980 let mode;
2981 let mut auto_scroll;
2982 match click_count {
2983 1 => {
2984 start = buffer.anchor_before(position.to_point(&display_map));
2985 end = start;
2986 mode = SelectMode::Character;
2987 auto_scroll = true;
2988 }
2989 2 => {
2990 let range = movement::surrounding_word(&display_map, position);
2991 start = buffer.anchor_before(range.start.to_point(&display_map));
2992 end = buffer.anchor_before(range.end.to_point(&display_map));
2993 mode = SelectMode::Word(start..end);
2994 auto_scroll = true;
2995 }
2996 3 => {
2997 let position = display_map
2998 .clip_point(position, Bias::Left)
2999 .to_point(&display_map);
3000 let line_start = display_map.prev_line_boundary(position).0;
3001 let next_line_start = buffer.clip_point(
3002 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3003 Bias::Left,
3004 );
3005 start = buffer.anchor_before(line_start);
3006 end = buffer.anchor_before(next_line_start);
3007 mode = SelectMode::Line(start..end);
3008 auto_scroll = true;
3009 }
3010 _ => {
3011 start = buffer.anchor_before(0);
3012 end = buffer.anchor_before(buffer.len());
3013 mode = SelectMode::All;
3014 auto_scroll = false;
3015 }
3016 }
3017 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3018
3019 let point_to_delete: Option<usize> = {
3020 let selected_points: Vec<Selection<Point>> =
3021 self.selections.disjoint_in_range(start..end, cx);
3022
3023 if !add || click_count > 1 {
3024 None
3025 } else if !selected_points.is_empty() {
3026 Some(selected_points[0].id)
3027 } else {
3028 let clicked_point_already_selected =
3029 self.selections.disjoint.iter().find(|selection| {
3030 selection.start.to_point(buffer) == start.to_point(buffer)
3031 || selection.end.to_point(buffer) == end.to_point(buffer)
3032 });
3033
3034 clicked_point_already_selected.map(|selection| selection.id)
3035 }
3036 };
3037
3038 let selections_count = self.selections.count();
3039
3040 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3041 if let Some(point_to_delete) = point_to_delete {
3042 s.delete(point_to_delete);
3043
3044 if selections_count == 1 {
3045 s.set_pending_anchor_range(start..end, mode);
3046 }
3047 } else {
3048 if !add {
3049 s.clear_disjoint();
3050 } else if click_count > 1 {
3051 s.delete(newest_selection.id)
3052 }
3053
3054 s.set_pending_anchor_range(start..end, mode);
3055 }
3056 });
3057 }
3058
3059 fn begin_columnar_selection(
3060 &mut self,
3061 position: DisplayPoint,
3062 goal_column: u32,
3063 reset: bool,
3064 window: &mut Window,
3065 cx: &mut Context<Self>,
3066 ) {
3067 if !self.focus_handle.is_focused(window) {
3068 self.last_focused_descendant = None;
3069 window.focus(&self.focus_handle);
3070 }
3071
3072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3073
3074 if reset {
3075 let pointer_position = display_map
3076 .buffer_snapshot
3077 .anchor_before(position.to_point(&display_map));
3078
3079 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3080 s.clear_disjoint();
3081 s.set_pending_anchor_range(
3082 pointer_position..pointer_position,
3083 SelectMode::Character,
3084 );
3085 });
3086 }
3087
3088 let tail = self.selections.newest::<Point>(cx).tail();
3089 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3090
3091 if !reset {
3092 self.select_columns(
3093 tail.to_display_point(&display_map),
3094 position,
3095 goal_column,
3096 &display_map,
3097 window,
3098 cx,
3099 );
3100 }
3101 }
3102
3103 fn update_selection(
3104 &mut self,
3105 position: DisplayPoint,
3106 goal_column: u32,
3107 scroll_delta: gpui::Point<f32>,
3108 window: &mut Window,
3109 cx: &mut Context<Self>,
3110 ) {
3111 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3112
3113 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3114 let tail = tail.to_display_point(&display_map);
3115 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3116 } else if let Some(mut pending) = self.selections.pending_anchor() {
3117 let buffer = self.buffer.read(cx).snapshot(cx);
3118 let head;
3119 let tail;
3120 let mode = self.selections.pending_mode().unwrap();
3121 match &mode {
3122 SelectMode::Character => {
3123 head = position.to_point(&display_map);
3124 tail = pending.tail().to_point(&buffer);
3125 }
3126 SelectMode::Word(original_range) => {
3127 let original_display_range = original_range.start.to_display_point(&display_map)
3128 ..original_range.end.to_display_point(&display_map);
3129 let original_buffer_range = original_display_range.start.to_point(&display_map)
3130 ..original_display_range.end.to_point(&display_map);
3131 if movement::is_inside_word(&display_map, position)
3132 || original_display_range.contains(&position)
3133 {
3134 let word_range = movement::surrounding_word(&display_map, position);
3135 if word_range.start < original_display_range.start {
3136 head = word_range.start.to_point(&display_map);
3137 } else {
3138 head = word_range.end.to_point(&display_map);
3139 }
3140 } else {
3141 head = position.to_point(&display_map);
3142 }
3143
3144 if head <= original_buffer_range.start {
3145 tail = original_buffer_range.end;
3146 } else {
3147 tail = original_buffer_range.start;
3148 }
3149 }
3150 SelectMode::Line(original_range) => {
3151 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3152
3153 let position = display_map
3154 .clip_point(position, Bias::Left)
3155 .to_point(&display_map);
3156 let line_start = display_map.prev_line_boundary(position).0;
3157 let next_line_start = buffer.clip_point(
3158 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3159 Bias::Left,
3160 );
3161
3162 if line_start < original_range.start {
3163 head = line_start
3164 } else {
3165 head = next_line_start
3166 }
3167
3168 if head <= original_range.start {
3169 tail = original_range.end;
3170 } else {
3171 tail = original_range.start;
3172 }
3173 }
3174 SelectMode::All => {
3175 return;
3176 }
3177 };
3178
3179 if head < tail {
3180 pending.start = buffer.anchor_before(head);
3181 pending.end = buffer.anchor_before(tail);
3182 pending.reversed = true;
3183 } else {
3184 pending.start = buffer.anchor_before(tail);
3185 pending.end = buffer.anchor_before(head);
3186 pending.reversed = false;
3187 }
3188
3189 self.change_selections(None, window, cx, |s| {
3190 s.set_pending(pending, mode);
3191 });
3192 } else {
3193 log::error!("update_selection dispatched with no pending selection");
3194 return;
3195 }
3196
3197 self.apply_scroll_delta(scroll_delta, window, cx);
3198 cx.notify();
3199 }
3200
3201 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3202 self.columnar_selection_tail.take();
3203 if self.selections.pending_anchor().is_some() {
3204 let selections = self.selections.all::<usize>(cx);
3205 self.change_selections(None, window, cx, |s| {
3206 s.select(selections);
3207 s.clear_pending();
3208 });
3209 }
3210 }
3211
3212 fn select_columns(
3213 &mut self,
3214 tail: DisplayPoint,
3215 head: DisplayPoint,
3216 goal_column: u32,
3217 display_map: &DisplaySnapshot,
3218 window: &mut Window,
3219 cx: &mut Context<Self>,
3220 ) {
3221 let start_row = cmp::min(tail.row(), head.row());
3222 let end_row = cmp::max(tail.row(), head.row());
3223 let start_column = cmp::min(tail.column(), goal_column);
3224 let end_column = cmp::max(tail.column(), goal_column);
3225 let reversed = start_column < tail.column();
3226
3227 let selection_ranges = (start_row.0..=end_row.0)
3228 .map(DisplayRow)
3229 .filter_map(|row| {
3230 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3231 let start = display_map
3232 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3233 .to_point(display_map);
3234 let end = display_map
3235 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3236 .to_point(display_map);
3237 if reversed {
3238 Some(end..start)
3239 } else {
3240 Some(start..end)
3241 }
3242 } else {
3243 None
3244 }
3245 })
3246 .collect::<Vec<_>>();
3247
3248 self.change_selections(None, window, cx, |s| {
3249 s.select_ranges(selection_ranges);
3250 });
3251 cx.notify();
3252 }
3253
3254 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3255 self.selections
3256 .all_adjusted(cx)
3257 .iter()
3258 .any(|selection| !selection.is_empty())
3259 }
3260
3261 pub fn has_pending_nonempty_selection(&self) -> bool {
3262 let pending_nonempty_selection = match self.selections.pending_anchor() {
3263 Some(Selection { start, end, .. }) => start != end,
3264 None => false,
3265 };
3266
3267 pending_nonempty_selection
3268 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3269 }
3270
3271 pub fn has_pending_selection(&self) -> bool {
3272 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3273 }
3274
3275 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3276 self.selection_mark_mode = false;
3277
3278 if self.clear_expanded_diff_hunks(cx) {
3279 cx.notify();
3280 return;
3281 }
3282 if self.dismiss_menus_and_popups(true, window, cx) {
3283 return;
3284 }
3285
3286 if self.mode.is_full()
3287 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3288 {
3289 return;
3290 }
3291
3292 cx.propagate();
3293 }
3294
3295 pub fn dismiss_menus_and_popups(
3296 &mut self,
3297 is_user_requested: bool,
3298 window: &mut Window,
3299 cx: &mut Context<Self>,
3300 ) -> bool {
3301 if self.take_rename(false, window, cx).is_some() {
3302 return true;
3303 }
3304
3305 if hide_hover(self, cx) {
3306 return true;
3307 }
3308
3309 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3310 return true;
3311 }
3312
3313 if self.hide_context_menu(window, cx).is_some() {
3314 return true;
3315 }
3316
3317 if self.mouse_context_menu.take().is_some() {
3318 return true;
3319 }
3320
3321 if is_user_requested && self.discard_inline_completion(true, cx) {
3322 return true;
3323 }
3324
3325 if self.snippet_stack.pop().is_some() {
3326 return true;
3327 }
3328
3329 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3330 self.dismiss_diagnostics(cx);
3331 return true;
3332 }
3333
3334 false
3335 }
3336
3337 fn linked_editing_ranges_for(
3338 &self,
3339 selection: Range<text::Anchor>,
3340 cx: &App,
3341 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3342 if self.linked_edit_ranges.is_empty() {
3343 return None;
3344 }
3345 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3346 selection.end.buffer_id.and_then(|end_buffer_id| {
3347 if selection.start.buffer_id != Some(end_buffer_id) {
3348 return None;
3349 }
3350 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3351 let snapshot = buffer.read(cx).snapshot();
3352 self.linked_edit_ranges
3353 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3354 .map(|ranges| (ranges, snapshot, buffer))
3355 })?;
3356 use text::ToOffset as TO;
3357 // find offset from the start of current range to current cursor position
3358 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3359
3360 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3361 let start_difference = start_offset - start_byte_offset;
3362 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3363 let end_difference = end_offset - start_byte_offset;
3364 // Current range has associated linked ranges.
3365 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3366 for range in linked_ranges.iter() {
3367 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3368 let end_offset = start_offset + end_difference;
3369 let start_offset = start_offset + start_difference;
3370 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3371 continue;
3372 }
3373 if self.selections.disjoint_anchor_ranges().any(|s| {
3374 if s.start.buffer_id != selection.start.buffer_id
3375 || s.end.buffer_id != selection.end.buffer_id
3376 {
3377 return false;
3378 }
3379 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3380 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3381 }) {
3382 continue;
3383 }
3384 let start = buffer_snapshot.anchor_after(start_offset);
3385 let end = buffer_snapshot.anchor_after(end_offset);
3386 linked_edits
3387 .entry(buffer.clone())
3388 .or_default()
3389 .push(start..end);
3390 }
3391 Some(linked_edits)
3392 }
3393
3394 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3395 let text: Arc<str> = text.into();
3396
3397 if self.read_only(cx) {
3398 return;
3399 }
3400
3401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3402
3403 let selections = self.selections.all_adjusted(cx);
3404 let mut bracket_inserted = false;
3405 let mut edits = Vec::new();
3406 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3407 let mut new_selections = Vec::with_capacity(selections.len());
3408 let mut new_autoclose_regions = Vec::new();
3409 let snapshot = self.buffer.read(cx).read(cx);
3410 let mut clear_linked_edit_ranges = false;
3411
3412 for (selection, autoclose_region) in
3413 self.selections_with_autoclose_regions(selections, &snapshot)
3414 {
3415 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3416 // Determine if the inserted text matches the opening or closing
3417 // bracket of any of this language's bracket pairs.
3418 let mut bracket_pair = None;
3419 let mut is_bracket_pair_start = false;
3420 let mut is_bracket_pair_end = false;
3421 if !text.is_empty() {
3422 let mut bracket_pair_matching_end = None;
3423 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3424 // and they are removing the character that triggered IME popup.
3425 for (pair, enabled) in scope.brackets() {
3426 if !pair.close && !pair.surround {
3427 continue;
3428 }
3429
3430 if enabled && pair.start.ends_with(text.as_ref()) {
3431 let prefix_len = pair.start.len() - text.len();
3432 let preceding_text_matches_prefix = prefix_len == 0
3433 || (selection.start.column >= (prefix_len as u32)
3434 && snapshot.contains_str_at(
3435 Point::new(
3436 selection.start.row,
3437 selection.start.column - (prefix_len as u32),
3438 ),
3439 &pair.start[..prefix_len],
3440 ));
3441 if preceding_text_matches_prefix {
3442 bracket_pair = Some(pair.clone());
3443 is_bracket_pair_start = true;
3444 break;
3445 }
3446 }
3447 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3448 {
3449 // take first bracket pair matching end, but don't break in case a later bracket
3450 // pair matches start
3451 bracket_pair_matching_end = Some(pair.clone());
3452 }
3453 }
3454 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3455 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3456 is_bracket_pair_end = true;
3457 }
3458 }
3459
3460 if let Some(bracket_pair) = bracket_pair {
3461 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3462 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3463 let auto_surround =
3464 self.use_auto_surround && snapshot_settings.use_auto_surround;
3465 if selection.is_empty() {
3466 if is_bracket_pair_start {
3467 // If the inserted text is a suffix of an opening bracket and the
3468 // selection is preceded by the rest of the opening bracket, then
3469 // insert the closing bracket.
3470 let following_text_allows_autoclose = snapshot
3471 .chars_at(selection.start)
3472 .next()
3473 .map_or(true, |c| scope.should_autoclose_before(c));
3474
3475 let preceding_text_allows_autoclose = selection.start.column == 0
3476 || snapshot.reversed_chars_at(selection.start).next().map_or(
3477 true,
3478 |c| {
3479 bracket_pair.start != bracket_pair.end
3480 || !snapshot
3481 .char_classifier_at(selection.start)
3482 .is_word(c)
3483 },
3484 );
3485
3486 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3487 && bracket_pair.start.len() == 1
3488 {
3489 let target = bracket_pair.start.chars().next().unwrap();
3490 let current_line_count = snapshot
3491 .reversed_chars_at(selection.start)
3492 .take_while(|&c| c != '\n')
3493 .filter(|&c| c == target)
3494 .count();
3495 current_line_count % 2 == 1
3496 } else {
3497 false
3498 };
3499
3500 if autoclose
3501 && bracket_pair.close
3502 && following_text_allows_autoclose
3503 && preceding_text_allows_autoclose
3504 && !is_closing_quote
3505 {
3506 let anchor = snapshot.anchor_before(selection.end);
3507 new_selections.push((selection.map(|_| anchor), text.len()));
3508 new_autoclose_regions.push((
3509 anchor,
3510 text.len(),
3511 selection.id,
3512 bracket_pair.clone(),
3513 ));
3514 edits.push((
3515 selection.range(),
3516 format!("{}{}", text, bracket_pair.end).into(),
3517 ));
3518 bracket_inserted = true;
3519 continue;
3520 }
3521 }
3522
3523 if let Some(region) = autoclose_region {
3524 // If the selection is followed by an auto-inserted closing bracket,
3525 // then don't insert that closing bracket again; just move the selection
3526 // past the closing bracket.
3527 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3528 && text.as_ref() == region.pair.end.as_str();
3529 if should_skip {
3530 let anchor = snapshot.anchor_after(selection.end);
3531 new_selections
3532 .push((selection.map(|_| anchor), region.pair.end.len()));
3533 continue;
3534 }
3535 }
3536
3537 let always_treat_brackets_as_autoclosed = snapshot
3538 .language_settings_at(selection.start, cx)
3539 .always_treat_brackets_as_autoclosed;
3540 if always_treat_brackets_as_autoclosed
3541 && is_bracket_pair_end
3542 && snapshot.contains_str_at(selection.end, text.as_ref())
3543 {
3544 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3545 // and the inserted text is a closing bracket and the selection is followed
3546 // by the closing bracket then move the selection past the closing bracket.
3547 let anchor = snapshot.anchor_after(selection.end);
3548 new_selections.push((selection.map(|_| anchor), text.len()));
3549 continue;
3550 }
3551 }
3552 // If an opening bracket is 1 character long and is typed while
3553 // text is selected, then surround that text with the bracket pair.
3554 else if auto_surround
3555 && bracket_pair.surround
3556 && is_bracket_pair_start
3557 && bracket_pair.start.chars().count() == 1
3558 {
3559 edits.push((selection.start..selection.start, text.clone()));
3560 edits.push((
3561 selection.end..selection.end,
3562 bracket_pair.end.as_str().into(),
3563 ));
3564 bracket_inserted = true;
3565 new_selections.push((
3566 Selection {
3567 id: selection.id,
3568 start: snapshot.anchor_after(selection.start),
3569 end: snapshot.anchor_before(selection.end),
3570 reversed: selection.reversed,
3571 goal: selection.goal,
3572 },
3573 0,
3574 ));
3575 continue;
3576 }
3577 }
3578 }
3579
3580 if self.auto_replace_emoji_shortcode
3581 && selection.is_empty()
3582 && text.as_ref().ends_with(':')
3583 {
3584 if let Some(possible_emoji_short_code) =
3585 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3586 {
3587 if !possible_emoji_short_code.is_empty() {
3588 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3589 let emoji_shortcode_start = Point::new(
3590 selection.start.row,
3591 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3592 );
3593
3594 // Remove shortcode from buffer
3595 edits.push((
3596 emoji_shortcode_start..selection.start,
3597 "".to_string().into(),
3598 ));
3599 new_selections.push((
3600 Selection {
3601 id: selection.id,
3602 start: snapshot.anchor_after(emoji_shortcode_start),
3603 end: snapshot.anchor_before(selection.start),
3604 reversed: selection.reversed,
3605 goal: selection.goal,
3606 },
3607 0,
3608 ));
3609
3610 // Insert emoji
3611 let selection_start_anchor = snapshot.anchor_after(selection.start);
3612 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3613 edits.push((selection.start..selection.end, emoji.to_string().into()));
3614
3615 continue;
3616 }
3617 }
3618 }
3619 }
3620
3621 // If not handling any auto-close operation, then just replace the selected
3622 // text with the given input and move the selection to the end of the
3623 // newly inserted text.
3624 let anchor = snapshot.anchor_after(selection.end);
3625 if !self.linked_edit_ranges.is_empty() {
3626 let start_anchor = snapshot.anchor_before(selection.start);
3627
3628 let is_word_char = text.chars().next().map_or(true, |char| {
3629 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3630 classifier.is_word(char)
3631 });
3632
3633 if is_word_char {
3634 if let Some(ranges) = self
3635 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3636 {
3637 for (buffer, edits) in ranges {
3638 linked_edits
3639 .entry(buffer.clone())
3640 .or_default()
3641 .extend(edits.into_iter().map(|range| (range, text.clone())));
3642 }
3643 }
3644 } else {
3645 clear_linked_edit_ranges = true;
3646 }
3647 }
3648
3649 new_selections.push((selection.map(|_| anchor), 0));
3650 edits.push((selection.start..selection.end, text.clone()));
3651 }
3652
3653 drop(snapshot);
3654
3655 self.transact(window, cx, |this, window, cx| {
3656 if clear_linked_edit_ranges {
3657 this.linked_edit_ranges.clear();
3658 }
3659 let initial_buffer_versions =
3660 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3661
3662 this.buffer.update(cx, |buffer, cx| {
3663 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3664 });
3665 for (buffer, edits) in linked_edits {
3666 buffer.update(cx, |buffer, cx| {
3667 let snapshot = buffer.snapshot();
3668 let edits = edits
3669 .into_iter()
3670 .map(|(range, text)| {
3671 use text::ToPoint as TP;
3672 let end_point = TP::to_point(&range.end, &snapshot);
3673 let start_point = TP::to_point(&range.start, &snapshot);
3674 (start_point..end_point, text)
3675 })
3676 .sorted_by_key(|(range, _)| range.start);
3677 buffer.edit(edits, None, cx);
3678 })
3679 }
3680 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3681 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3682 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3683 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3684 .zip(new_selection_deltas)
3685 .map(|(selection, delta)| Selection {
3686 id: selection.id,
3687 start: selection.start + delta,
3688 end: selection.end + delta,
3689 reversed: selection.reversed,
3690 goal: SelectionGoal::None,
3691 })
3692 .collect::<Vec<_>>();
3693
3694 let mut i = 0;
3695 for (position, delta, selection_id, pair) in new_autoclose_regions {
3696 let position = position.to_offset(&map.buffer_snapshot) + delta;
3697 let start = map.buffer_snapshot.anchor_before(position);
3698 let end = map.buffer_snapshot.anchor_after(position);
3699 while let Some(existing_state) = this.autoclose_regions.get(i) {
3700 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3701 Ordering::Less => i += 1,
3702 Ordering::Greater => break,
3703 Ordering::Equal => {
3704 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3705 Ordering::Less => i += 1,
3706 Ordering::Equal => break,
3707 Ordering::Greater => break,
3708 }
3709 }
3710 }
3711 }
3712 this.autoclose_regions.insert(
3713 i,
3714 AutocloseRegion {
3715 selection_id,
3716 range: start..end,
3717 pair,
3718 },
3719 );
3720 }
3721
3722 let had_active_inline_completion = this.has_active_inline_completion();
3723 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3724 s.select(new_selections)
3725 });
3726
3727 if !bracket_inserted {
3728 if let Some(on_type_format_task) =
3729 this.trigger_on_type_formatting(text.to_string(), window, cx)
3730 {
3731 on_type_format_task.detach_and_log_err(cx);
3732 }
3733 }
3734
3735 let editor_settings = EditorSettings::get_global(cx);
3736 if bracket_inserted
3737 && (editor_settings.auto_signature_help
3738 || editor_settings.show_signature_help_after_edits)
3739 {
3740 this.show_signature_help(&ShowSignatureHelp, window, cx);
3741 }
3742
3743 let trigger_in_words =
3744 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3745 if this.hard_wrap.is_some() {
3746 let latest: Range<Point> = this.selections.newest(cx).range();
3747 if latest.is_empty()
3748 && this
3749 .buffer()
3750 .read(cx)
3751 .snapshot(cx)
3752 .line_len(MultiBufferRow(latest.start.row))
3753 == latest.start.column
3754 {
3755 this.rewrap_impl(
3756 RewrapOptions {
3757 override_language_settings: true,
3758 preserve_existing_whitespace: true,
3759 },
3760 cx,
3761 )
3762 }
3763 }
3764 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3765 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3766 this.refresh_inline_completion(true, false, window, cx);
3767 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3768 });
3769 }
3770
3771 fn find_possible_emoji_shortcode_at_position(
3772 snapshot: &MultiBufferSnapshot,
3773 position: Point,
3774 ) -> Option<String> {
3775 let mut chars = Vec::new();
3776 let mut found_colon = false;
3777 for char in snapshot.reversed_chars_at(position).take(100) {
3778 // Found a possible emoji shortcode in the middle of the buffer
3779 if found_colon {
3780 if char.is_whitespace() {
3781 chars.reverse();
3782 return Some(chars.iter().collect());
3783 }
3784 // If the previous character is not a whitespace, we are in the middle of a word
3785 // and we only want to complete the shortcode if the word is made up of other emojis
3786 let mut containing_word = String::new();
3787 for ch in snapshot
3788 .reversed_chars_at(position)
3789 .skip(chars.len() + 1)
3790 .take(100)
3791 {
3792 if ch.is_whitespace() {
3793 break;
3794 }
3795 containing_word.push(ch);
3796 }
3797 let containing_word = containing_word.chars().rev().collect::<String>();
3798 if util::word_consists_of_emojis(containing_word.as_str()) {
3799 chars.reverse();
3800 return Some(chars.iter().collect());
3801 }
3802 }
3803
3804 if char.is_whitespace() || !char.is_ascii() {
3805 return None;
3806 }
3807 if char == ':' {
3808 found_colon = true;
3809 } else {
3810 chars.push(char);
3811 }
3812 }
3813 // Found a possible emoji shortcode at the beginning of the buffer
3814 chars.reverse();
3815 Some(chars.iter().collect())
3816 }
3817
3818 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3819 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3820 self.transact(window, cx, |this, window, cx| {
3821 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3822 let selections = this.selections.all::<usize>(cx);
3823 let multi_buffer = this.buffer.read(cx);
3824 let buffer = multi_buffer.snapshot(cx);
3825 selections
3826 .iter()
3827 .map(|selection| {
3828 let start_point = selection.start.to_point(&buffer);
3829 let mut indent =
3830 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3831 indent.len = cmp::min(indent.len, start_point.column);
3832 let start = selection.start;
3833 let end = selection.end;
3834 let selection_is_empty = start == end;
3835 let language_scope = buffer.language_scope_at(start);
3836 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3837 &language_scope
3838 {
3839 let insert_extra_newline =
3840 insert_extra_newline_brackets(&buffer, start..end, language)
3841 || insert_extra_newline_tree_sitter(&buffer, start..end);
3842
3843 // Comment extension on newline is allowed only for cursor selections
3844 let comment_delimiter = maybe!({
3845 if !selection_is_empty {
3846 return None;
3847 }
3848
3849 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3850 return None;
3851 }
3852
3853 let delimiters = language.line_comment_prefixes();
3854 let max_len_of_delimiter =
3855 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3856 let (snapshot, range) =
3857 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3858
3859 let mut index_of_first_non_whitespace = 0;
3860 let comment_candidate = snapshot
3861 .chars_for_range(range)
3862 .skip_while(|c| {
3863 let should_skip = c.is_whitespace();
3864 if should_skip {
3865 index_of_first_non_whitespace += 1;
3866 }
3867 should_skip
3868 })
3869 .take(max_len_of_delimiter)
3870 .collect::<String>();
3871 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3872 comment_candidate.starts_with(comment_prefix.as_ref())
3873 })?;
3874 let cursor_is_placed_after_comment_marker =
3875 index_of_first_non_whitespace + comment_prefix.len()
3876 <= start_point.column as usize;
3877 if cursor_is_placed_after_comment_marker {
3878 Some(comment_prefix.clone())
3879 } else {
3880 None
3881 }
3882 });
3883 (comment_delimiter, insert_extra_newline)
3884 } else {
3885 (None, false)
3886 };
3887
3888 let capacity_for_delimiter = comment_delimiter
3889 .as_deref()
3890 .map(str::len)
3891 .unwrap_or_default();
3892 let mut new_text =
3893 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3894 new_text.push('\n');
3895 new_text.extend(indent.chars());
3896 if let Some(delimiter) = &comment_delimiter {
3897 new_text.push_str(delimiter);
3898 }
3899 if insert_extra_newline {
3900 new_text = new_text.repeat(2);
3901 }
3902
3903 let anchor = buffer.anchor_after(end);
3904 let new_selection = selection.map(|_| anchor);
3905 (
3906 (start..end, new_text),
3907 (insert_extra_newline, new_selection),
3908 )
3909 })
3910 .unzip()
3911 };
3912
3913 this.edit_with_autoindent(edits, cx);
3914 let buffer = this.buffer.read(cx).snapshot(cx);
3915 let new_selections = selection_fixup_info
3916 .into_iter()
3917 .map(|(extra_newline_inserted, new_selection)| {
3918 let mut cursor = new_selection.end.to_point(&buffer);
3919 if extra_newline_inserted {
3920 cursor.row -= 1;
3921 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3922 }
3923 new_selection.map(|_| cursor)
3924 })
3925 .collect();
3926
3927 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3928 s.select(new_selections)
3929 });
3930 this.refresh_inline_completion(true, false, window, cx);
3931 });
3932 }
3933
3934 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3935 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3936
3937 let buffer = self.buffer.read(cx);
3938 let snapshot = buffer.snapshot(cx);
3939
3940 let mut edits = Vec::new();
3941 let mut rows = Vec::new();
3942
3943 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3944 let cursor = selection.head();
3945 let row = cursor.row;
3946
3947 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3948
3949 let newline = "\n".to_string();
3950 edits.push((start_of_line..start_of_line, newline));
3951
3952 rows.push(row + rows_inserted as u32);
3953 }
3954
3955 self.transact(window, cx, |editor, window, cx| {
3956 editor.edit(edits, cx);
3957
3958 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3959 let mut index = 0;
3960 s.move_cursors_with(|map, _, _| {
3961 let row = rows[index];
3962 index += 1;
3963
3964 let point = Point::new(row, 0);
3965 let boundary = map.next_line_boundary(point).1;
3966 let clipped = map.clip_point(boundary, Bias::Left);
3967
3968 (clipped, SelectionGoal::None)
3969 });
3970 });
3971
3972 let mut indent_edits = Vec::new();
3973 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3974 for row in rows {
3975 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3976 for (row, indent) in indents {
3977 if indent.len == 0 {
3978 continue;
3979 }
3980
3981 let text = match indent.kind {
3982 IndentKind::Space => " ".repeat(indent.len as usize),
3983 IndentKind::Tab => "\t".repeat(indent.len as usize),
3984 };
3985 let point = Point::new(row.0, 0);
3986 indent_edits.push((point..point, text));
3987 }
3988 }
3989 editor.edit(indent_edits, cx);
3990 });
3991 }
3992
3993 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3994 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3995
3996 let buffer = self.buffer.read(cx);
3997 let snapshot = buffer.snapshot(cx);
3998
3999 let mut edits = Vec::new();
4000 let mut rows = Vec::new();
4001 let mut rows_inserted = 0;
4002
4003 for selection in self.selections.all_adjusted(cx) {
4004 let cursor = selection.head();
4005 let row = cursor.row;
4006
4007 let point = Point::new(row + 1, 0);
4008 let start_of_line = snapshot.clip_point(point, Bias::Left);
4009
4010 let newline = "\n".to_string();
4011 edits.push((start_of_line..start_of_line, newline));
4012
4013 rows_inserted += 1;
4014 rows.push(row + rows_inserted);
4015 }
4016
4017 self.transact(window, cx, |editor, window, cx| {
4018 editor.edit(edits, cx);
4019
4020 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4021 let mut index = 0;
4022 s.move_cursors_with(|map, _, _| {
4023 let row = rows[index];
4024 index += 1;
4025
4026 let point = Point::new(row, 0);
4027 let boundary = map.next_line_boundary(point).1;
4028 let clipped = map.clip_point(boundary, Bias::Left);
4029
4030 (clipped, SelectionGoal::None)
4031 });
4032 });
4033
4034 let mut indent_edits = Vec::new();
4035 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4036 for row in rows {
4037 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4038 for (row, indent) in indents {
4039 if indent.len == 0 {
4040 continue;
4041 }
4042
4043 let text = match indent.kind {
4044 IndentKind::Space => " ".repeat(indent.len as usize),
4045 IndentKind::Tab => "\t".repeat(indent.len as usize),
4046 };
4047 let point = Point::new(row.0, 0);
4048 indent_edits.push((point..point, text));
4049 }
4050 }
4051 editor.edit(indent_edits, cx);
4052 });
4053 }
4054
4055 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4056 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4057 original_indent_columns: Vec::new(),
4058 });
4059 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4060 }
4061
4062 fn insert_with_autoindent_mode(
4063 &mut self,
4064 text: &str,
4065 autoindent_mode: Option<AutoindentMode>,
4066 window: &mut Window,
4067 cx: &mut Context<Self>,
4068 ) {
4069 if self.read_only(cx) {
4070 return;
4071 }
4072
4073 let text: Arc<str> = text.into();
4074 self.transact(window, cx, |this, window, cx| {
4075 let old_selections = this.selections.all_adjusted(cx);
4076 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4077 let anchors = {
4078 let snapshot = buffer.read(cx);
4079 old_selections
4080 .iter()
4081 .map(|s| {
4082 let anchor = snapshot.anchor_after(s.head());
4083 s.map(|_| anchor)
4084 })
4085 .collect::<Vec<_>>()
4086 };
4087 buffer.edit(
4088 old_selections
4089 .iter()
4090 .map(|s| (s.start..s.end, text.clone())),
4091 autoindent_mode,
4092 cx,
4093 );
4094 anchors
4095 });
4096
4097 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4098 s.select_anchors(selection_anchors);
4099 });
4100
4101 cx.notify();
4102 });
4103 }
4104
4105 fn trigger_completion_on_input(
4106 &mut self,
4107 text: &str,
4108 trigger_in_words: bool,
4109 window: &mut Window,
4110 cx: &mut Context<Self>,
4111 ) {
4112 let ignore_completion_provider = self
4113 .context_menu
4114 .borrow()
4115 .as_ref()
4116 .map(|menu| match menu {
4117 CodeContextMenu::Completions(completions_menu) => {
4118 completions_menu.ignore_completion_provider
4119 }
4120 CodeContextMenu::CodeActions(_) => false,
4121 })
4122 .unwrap_or(false);
4123
4124 if ignore_completion_provider {
4125 self.show_word_completions(&ShowWordCompletions, window, cx);
4126 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4127 self.show_completions(
4128 &ShowCompletions {
4129 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4130 },
4131 window,
4132 cx,
4133 );
4134 } else {
4135 self.hide_context_menu(window, cx);
4136 }
4137 }
4138
4139 fn is_completion_trigger(
4140 &self,
4141 text: &str,
4142 trigger_in_words: bool,
4143 cx: &mut Context<Self>,
4144 ) -> bool {
4145 let position = self.selections.newest_anchor().head();
4146 let multibuffer = self.buffer.read(cx);
4147 let Some(buffer) = position
4148 .buffer_id
4149 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4150 else {
4151 return false;
4152 };
4153
4154 if let Some(completion_provider) = &self.completion_provider {
4155 completion_provider.is_completion_trigger(
4156 &buffer,
4157 position.text_anchor,
4158 text,
4159 trigger_in_words,
4160 cx,
4161 )
4162 } else {
4163 false
4164 }
4165 }
4166
4167 /// If any empty selections is touching the start of its innermost containing autoclose
4168 /// region, expand it to select the brackets.
4169 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4170 let selections = self.selections.all::<usize>(cx);
4171 let buffer = self.buffer.read(cx).read(cx);
4172 let new_selections = self
4173 .selections_with_autoclose_regions(selections, &buffer)
4174 .map(|(mut selection, region)| {
4175 if !selection.is_empty() {
4176 return selection;
4177 }
4178
4179 if let Some(region) = region {
4180 let mut range = region.range.to_offset(&buffer);
4181 if selection.start == range.start && range.start >= region.pair.start.len() {
4182 range.start -= region.pair.start.len();
4183 if buffer.contains_str_at(range.start, ®ion.pair.start)
4184 && buffer.contains_str_at(range.end, ®ion.pair.end)
4185 {
4186 range.end += region.pair.end.len();
4187 selection.start = range.start;
4188 selection.end = range.end;
4189
4190 return selection;
4191 }
4192 }
4193 }
4194
4195 let always_treat_brackets_as_autoclosed = buffer
4196 .language_settings_at(selection.start, cx)
4197 .always_treat_brackets_as_autoclosed;
4198
4199 if !always_treat_brackets_as_autoclosed {
4200 return selection;
4201 }
4202
4203 if let Some(scope) = buffer.language_scope_at(selection.start) {
4204 for (pair, enabled) in scope.brackets() {
4205 if !enabled || !pair.close {
4206 continue;
4207 }
4208
4209 if buffer.contains_str_at(selection.start, &pair.end) {
4210 let pair_start_len = pair.start.len();
4211 if buffer.contains_str_at(
4212 selection.start.saturating_sub(pair_start_len),
4213 &pair.start,
4214 ) {
4215 selection.start -= pair_start_len;
4216 selection.end += pair.end.len();
4217
4218 return selection;
4219 }
4220 }
4221 }
4222 }
4223
4224 selection
4225 })
4226 .collect();
4227
4228 drop(buffer);
4229 self.change_selections(None, window, cx, |selections| {
4230 selections.select(new_selections)
4231 });
4232 }
4233
4234 /// Iterate the given selections, and for each one, find the smallest surrounding
4235 /// autoclose region. This uses the ordering of the selections and the autoclose
4236 /// regions to avoid repeated comparisons.
4237 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4238 &'a self,
4239 selections: impl IntoIterator<Item = Selection<D>>,
4240 buffer: &'a MultiBufferSnapshot,
4241 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4242 let mut i = 0;
4243 let mut regions = self.autoclose_regions.as_slice();
4244 selections.into_iter().map(move |selection| {
4245 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4246
4247 let mut enclosing = None;
4248 while let Some(pair_state) = regions.get(i) {
4249 if pair_state.range.end.to_offset(buffer) < range.start {
4250 regions = ®ions[i + 1..];
4251 i = 0;
4252 } else if pair_state.range.start.to_offset(buffer) > range.end {
4253 break;
4254 } else {
4255 if pair_state.selection_id == selection.id {
4256 enclosing = Some(pair_state);
4257 }
4258 i += 1;
4259 }
4260 }
4261
4262 (selection, enclosing)
4263 })
4264 }
4265
4266 /// Remove any autoclose regions that no longer contain their selection.
4267 fn invalidate_autoclose_regions(
4268 &mut self,
4269 mut selections: &[Selection<Anchor>],
4270 buffer: &MultiBufferSnapshot,
4271 ) {
4272 self.autoclose_regions.retain(|state| {
4273 let mut i = 0;
4274 while let Some(selection) = selections.get(i) {
4275 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4276 selections = &selections[1..];
4277 continue;
4278 }
4279 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4280 break;
4281 }
4282 if selection.id == state.selection_id {
4283 return true;
4284 } else {
4285 i += 1;
4286 }
4287 }
4288 false
4289 });
4290 }
4291
4292 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4293 let offset = position.to_offset(buffer);
4294 let (word_range, kind) = buffer.surrounding_word(offset, true);
4295 if offset > word_range.start && kind == Some(CharKind::Word) {
4296 Some(
4297 buffer
4298 .text_for_range(word_range.start..offset)
4299 .collect::<String>(),
4300 )
4301 } else {
4302 None
4303 }
4304 }
4305
4306 pub fn toggle_inline_values(
4307 &mut self,
4308 _: &ToggleInlineValues,
4309 _: &mut Window,
4310 cx: &mut Context<Self>,
4311 ) {
4312 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4313
4314 self.refresh_inline_values(cx);
4315 }
4316
4317 pub fn toggle_inlay_hints(
4318 &mut self,
4319 _: &ToggleInlayHints,
4320 _: &mut Window,
4321 cx: &mut Context<Self>,
4322 ) {
4323 self.refresh_inlay_hints(
4324 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4325 cx,
4326 );
4327 }
4328
4329 pub fn inlay_hints_enabled(&self) -> bool {
4330 self.inlay_hint_cache.enabled
4331 }
4332
4333 pub fn inline_values_enabled(&self) -> bool {
4334 self.inline_value_cache.enabled
4335 }
4336
4337 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4338 if self.semantics_provider.is_none() || !self.mode.is_full() {
4339 return;
4340 }
4341
4342 let reason_description = reason.description();
4343 let ignore_debounce = matches!(
4344 reason,
4345 InlayHintRefreshReason::SettingsChange(_)
4346 | InlayHintRefreshReason::Toggle(_)
4347 | InlayHintRefreshReason::ExcerptsRemoved(_)
4348 | InlayHintRefreshReason::ModifiersChanged(_)
4349 );
4350 let (invalidate_cache, required_languages) = match reason {
4351 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4352 match self.inlay_hint_cache.modifiers_override(enabled) {
4353 Some(enabled) => {
4354 if enabled {
4355 (InvalidationStrategy::RefreshRequested, None)
4356 } else {
4357 self.splice_inlays(
4358 &self
4359 .visible_inlay_hints(cx)
4360 .iter()
4361 .map(|inlay| inlay.id)
4362 .collect::<Vec<InlayId>>(),
4363 Vec::new(),
4364 cx,
4365 );
4366 return;
4367 }
4368 }
4369 None => return,
4370 }
4371 }
4372 InlayHintRefreshReason::Toggle(enabled) => {
4373 if self.inlay_hint_cache.toggle(enabled) {
4374 if enabled {
4375 (InvalidationStrategy::RefreshRequested, None)
4376 } else {
4377 self.splice_inlays(
4378 &self
4379 .visible_inlay_hints(cx)
4380 .iter()
4381 .map(|inlay| inlay.id)
4382 .collect::<Vec<InlayId>>(),
4383 Vec::new(),
4384 cx,
4385 );
4386 return;
4387 }
4388 } else {
4389 return;
4390 }
4391 }
4392 InlayHintRefreshReason::SettingsChange(new_settings) => {
4393 match self.inlay_hint_cache.update_settings(
4394 &self.buffer,
4395 new_settings,
4396 self.visible_inlay_hints(cx),
4397 cx,
4398 ) {
4399 ControlFlow::Break(Some(InlaySplice {
4400 to_remove,
4401 to_insert,
4402 })) => {
4403 self.splice_inlays(&to_remove, to_insert, cx);
4404 return;
4405 }
4406 ControlFlow::Break(None) => return,
4407 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4408 }
4409 }
4410 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4411 if let Some(InlaySplice {
4412 to_remove,
4413 to_insert,
4414 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4415 {
4416 self.splice_inlays(&to_remove, to_insert, cx);
4417 }
4418 self.display_map.update(cx, |display_map, _| {
4419 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4420 });
4421 return;
4422 }
4423 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4424 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4425 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4426 }
4427 InlayHintRefreshReason::RefreshRequested => {
4428 (InvalidationStrategy::RefreshRequested, None)
4429 }
4430 };
4431
4432 if let Some(InlaySplice {
4433 to_remove,
4434 to_insert,
4435 }) = self.inlay_hint_cache.spawn_hint_refresh(
4436 reason_description,
4437 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4438 invalidate_cache,
4439 ignore_debounce,
4440 cx,
4441 ) {
4442 self.splice_inlays(&to_remove, to_insert, cx);
4443 }
4444 }
4445
4446 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4447 self.display_map
4448 .read(cx)
4449 .current_inlays()
4450 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4451 .cloned()
4452 .collect()
4453 }
4454
4455 pub fn excerpts_for_inlay_hints_query(
4456 &self,
4457 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4458 cx: &mut Context<Editor>,
4459 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4460 let Some(project) = self.project.as_ref() else {
4461 return HashMap::default();
4462 };
4463 let project = project.read(cx);
4464 let multi_buffer = self.buffer().read(cx);
4465 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4466 let multi_buffer_visible_start = self
4467 .scroll_manager
4468 .anchor()
4469 .anchor
4470 .to_point(&multi_buffer_snapshot);
4471 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4472 multi_buffer_visible_start
4473 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4474 Bias::Left,
4475 );
4476 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4477 multi_buffer_snapshot
4478 .range_to_buffer_ranges(multi_buffer_visible_range)
4479 .into_iter()
4480 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4481 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4482 let buffer_file = project::File::from_dyn(buffer.file())?;
4483 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4484 let worktree_entry = buffer_worktree
4485 .read(cx)
4486 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4487 if worktree_entry.is_ignored {
4488 return None;
4489 }
4490
4491 let language = buffer.language()?;
4492 if let Some(restrict_to_languages) = restrict_to_languages {
4493 if !restrict_to_languages.contains(language) {
4494 return None;
4495 }
4496 }
4497 Some((
4498 excerpt_id,
4499 (
4500 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4501 buffer.version().clone(),
4502 excerpt_visible_range,
4503 ),
4504 ))
4505 })
4506 .collect()
4507 }
4508
4509 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4510 TextLayoutDetails {
4511 text_system: window.text_system().clone(),
4512 editor_style: self.style.clone().unwrap(),
4513 rem_size: window.rem_size(),
4514 scroll_anchor: self.scroll_manager.anchor(),
4515 visible_rows: self.visible_line_count(),
4516 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4517 }
4518 }
4519
4520 pub fn splice_inlays(
4521 &self,
4522 to_remove: &[InlayId],
4523 to_insert: Vec<Inlay>,
4524 cx: &mut Context<Self>,
4525 ) {
4526 self.display_map.update(cx, |display_map, cx| {
4527 display_map.splice_inlays(to_remove, to_insert, cx)
4528 });
4529 cx.notify();
4530 }
4531
4532 fn trigger_on_type_formatting(
4533 &self,
4534 input: String,
4535 window: &mut Window,
4536 cx: &mut Context<Self>,
4537 ) -> Option<Task<Result<()>>> {
4538 if input.len() != 1 {
4539 return None;
4540 }
4541
4542 let project = self.project.as_ref()?;
4543 let position = self.selections.newest_anchor().head();
4544 let (buffer, buffer_position) = self
4545 .buffer
4546 .read(cx)
4547 .text_anchor_for_position(position, cx)?;
4548
4549 let settings = language_settings::language_settings(
4550 buffer
4551 .read(cx)
4552 .language_at(buffer_position)
4553 .map(|l| l.name()),
4554 buffer.read(cx).file(),
4555 cx,
4556 );
4557 if !settings.use_on_type_format {
4558 return None;
4559 }
4560
4561 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4562 // hence we do LSP request & edit on host side only — add formats to host's history.
4563 let push_to_lsp_host_history = true;
4564 // If this is not the host, append its history with new edits.
4565 let push_to_client_history = project.read(cx).is_via_collab();
4566
4567 let on_type_formatting = project.update(cx, |project, cx| {
4568 project.on_type_format(
4569 buffer.clone(),
4570 buffer_position,
4571 input,
4572 push_to_lsp_host_history,
4573 cx,
4574 )
4575 });
4576 Some(cx.spawn_in(window, async move |editor, cx| {
4577 if let Some(transaction) = on_type_formatting.await? {
4578 if push_to_client_history {
4579 buffer
4580 .update(cx, |buffer, _| {
4581 buffer.push_transaction(transaction, Instant::now());
4582 buffer.finalize_last_transaction();
4583 })
4584 .ok();
4585 }
4586 editor.update(cx, |editor, cx| {
4587 editor.refresh_document_highlights(cx);
4588 })?;
4589 }
4590 Ok(())
4591 }))
4592 }
4593
4594 pub fn show_word_completions(
4595 &mut self,
4596 _: &ShowWordCompletions,
4597 window: &mut Window,
4598 cx: &mut Context<Self>,
4599 ) {
4600 self.open_completions_menu(true, None, window, cx);
4601 }
4602
4603 pub fn show_completions(
4604 &mut self,
4605 options: &ShowCompletions,
4606 window: &mut Window,
4607 cx: &mut Context<Self>,
4608 ) {
4609 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4610 }
4611
4612 fn open_completions_menu(
4613 &mut self,
4614 ignore_completion_provider: bool,
4615 trigger: Option<&str>,
4616 window: &mut Window,
4617 cx: &mut Context<Self>,
4618 ) {
4619 if self.pending_rename.is_some() {
4620 return;
4621 }
4622 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4623 return;
4624 }
4625
4626 let position = self.selections.newest_anchor().head();
4627 if position.diff_base_anchor.is_some() {
4628 return;
4629 }
4630 let (buffer, buffer_position) =
4631 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4632 output
4633 } else {
4634 return;
4635 };
4636 let buffer_snapshot = buffer.read(cx).snapshot();
4637 let show_completion_documentation = buffer_snapshot
4638 .settings_at(buffer_position, cx)
4639 .show_completion_documentation;
4640
4641 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4642
4643 let trigger_kind = match trigger {
4644 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4645 CompletionTriggerKind::TRIGGER_CHARACTER
4646 }
4647 _ => CompletionTriggerKind::INVOKED,
4648 };
4649 let completion_context = CompletionContext {
4650 trigger_character: trigger.and_then(|trigger| {
4651 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4652 Some(String::from(trigger))
4653 } else {
4654 None
4655 }
4656 }),
4657 trigger_kind,
4658 };
4659
4660 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4661 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4662 let word_to_exclude = buffer_snapshot
4663 .text_for_range(old_range.clone())
4664 .collect::<String>();
4665 (
4666 buffer_snapshot.anchor_before(old_range.start)
4667 ..buffer_snapshot.anchor_after(old_range.end),
4668 Some(word_to_exclude),
4669 )
4670 } else {
4671 (buffer_position..buffer_position, None)
4672 };
4673
4674 let completion_settings = language_settings(
4675 buffer_snapshot
4676 .language_at(buffer_position)
4677 .map(|language| language.name()),
4678 buffer_snapshot.file(),
4679 cx,
4680 )
4681 .completions;
4682
4683 // The document can be large, so stay in reasonable bounds when searching for words,
4684 // otherwise completion pop-up might be slow to appear.
4685 const WORD_LOOKUP_ROWS: u32 = 5_000;
4686 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4687 let min_word_search = buffer_snapshot.clip_point(
4688 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4689 Bias::Left,
4690 );
4691 let max_word_search = buffer_snapshot.clip_point(
4692 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4693 Bias::Right,
4694 );
4695 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4696 ..buffer_snapshot.point_to_offset(max_word_search);
4697
4698 let provider = self
4699 .completion_provider
4700 .as_ref()
4701 .filter(|_| !ignore_completion_provider);
4702 let skip_digits = query
4703 .as_ref()
4704 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4705
4706 let (mut words, provided_completions) = match provider {
4707 Some(provider) => {
4708 let completions = provider.completions(
4709 position.excerpt_id,
4710 &buffer,
4711 buffer_position,
4712 completion_context,
4713 window,
4714 cx,
4715 );
4716
4717 let words = match completion_settings.words {
4718 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4719 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4720 .background_spawn(async move {
4721 buffer_snapshot.words_in_range(WordsQuery {
4722 fuzzy_contents: None,
4723 range: word_search_range,
4724 skip_digits,
4725 })
4726 }),
4727 };
4728
4729 (words, completions)
4730 }
4731 None => (
4732 cx.background_spawn(async move {
4733 buffer_snapshot.words_in_range(WordsQuery {
4734 fuzzy_contents: None,
4735 range: word_search_range,
4736 skip_digits,
4737 })
4738 }),
4739 Task::ready(Ok(None)),
4740 ),
4741 };
4742
4743 let sort_completions = provider
4744 .as_ref()
4745 .map_or(false, |provider| provider.sort_completions());
4746
4747 let filter_completions = provider
4748 .as_ref()
4749 .map_or(true, |provider| provider.filter_completions());
4750
4751 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4752
4753 let id = post_inc(&mut self.next_completion_id);
4754 let task = cx.spawn_in(window, async move |editor, cx| {
4755 async move {
4756 editor.update(cx, |this, _| {
4757 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4758 })?;
4759
4760 let mut completions = Vec::new();
4761 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4762 completions.extend(provided_completions);
4763 if completion_settings.words == WordsCompletionMode::Fallback {
4764 words = Task::ready(BTreeMap::default());
4765 }
4766 }
4767
4768 let mut words = words.await;
4769 if let Some(word_to_exclude) = &word_to_exclude {
4770 words.remove(word_to_exclude);
4771 }
4772 for lsp_completion in &completions {
4773 words.remove(&lsp_completion.new_text);
4774 }
4775 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4776 replace_range: old_range.clone(),
4777 new_text: word.clone(),
4778 label: CodeLabel::plain(word, None),
4779 icon_path: None,
4780 documentation: None,
4781 source: CompletionSource::BufferWord {
4782 word_range,
4783 resolved: false,
4784 },
4785 insert_text_mode: Some(InsertTextMode::AS_IS),
4786 confirm: None,
4787 }));
4788
4789 let menu = if completions.is_empty() {
4790 None
4791 } else {
4792 let mut menu = CompletionsMenu::new(
4793 id,
4794 sort_completions,
4795 show_completion_documentation,
4796 ignore_completion_provider,
4797 position,
4798 buffer.clone(),
4799 completions.into(),
4800 snippet_sort_order,
4801 );
4802
4803 menu.filter(
4804 if filter_completions {
4805 query.as_deref()
4806 } else {
4807 None
4808 },
4809 cx.background_executor().clone(),
4810 )
4811 .await;
4812
4813 menu.visible().then_some(menu)
4814 };
4815
4816 editor.update_in(cx, |editor, window, cx| {
4817 match editor.context_menu.borrow().as_ref() {
4818 None => {}
4819 Some(CodeContextMenu::Completions(prev_menu)) => {
4820 if prev_menu.id > id {
4821 return;
4822 }
4823 }
4824 _ => return,
4825 }
4826
4827 if editor.focus_handle.is_focused(window) && menu.is_some() {
4828 let mut menu = menu.unwrap();
4829 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4830
4831 *editor.context_menu.borrow_mut() =
4832 Some(CodeContextMenu::Completions(menu));
4833
4834 if editor.show_edit_predictions_in_menu() {
4835 editor.update_visible_inline_completion(window, cx);
4836 } else {
4837 editor.discard_inline_completion(false, cx);
4838 }
4839
4840 cx.notify();
4841 } else if editor.completion_tasks.len() <= 1 {
4842 // If there are no more completion tasks and the last menu was
4843 // empty, we should hide it.
4844 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4845 // If it was already hidden and we don't show inline
4846 // completions in the menu, we should also show the
4847 // inline-completion when available.
4848 if was_hidden && editor.show_edit_predictions_in_menu() {
4849 editor.update_visible_inline_completion(window, cx);
4850 }
4851 }
4852 })?;
4853
4854 anyhow::Ok(())
4855 }
4856 .log_err()
4857 .await
4858 });
4859
4860 self.completion_tasks.push((id, task));
4861 }
4862
4863 #[cfg(feature = "test-support")]
4864 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4865 let menu = self.context_menu.borrow();
4866 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4867 let completions = menu.completions.borrow();
4868 Some(completions.to_vec())
4869 } else {
4870 None
4871 }
4872 }
4873
4874 pub fn confirm_completion(
4875 &mut self,
4876 action: &ConfirmCompletion,
4877 window: &mut Window,
4878 cx: &mut Context<Self>,
4879 ) -> Option<Task<Result<()>>> {
4880 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4881 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4882 }
4883
4884 pub fn confirm_completion_insert(
4885 &mut self,
4886 _: &ConfirmCompletionInsert,
4887 window: &mut Window,
4888 cx: &mut Context<Self>,
4889 ) -> Option<Task<Result<()>>> {
4890 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4891 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4892 }
4893
4894 pub fn confirm_completion_replace(
4895 &mut self,
4896 _: &ConfirmCompletionReplace,
4897 window: &mut Window,
4898 cx: &mut Context<Self>,
4899 ) -> Option<Task<Result<()>>> {
4900 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4901 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4902 }
4903
4904 pub fn compose_completion(
4905 &mut self,
4906 action: &ComposeCompletion,
4907 window: &mut Window,
4908 cx: &mut Context<Self>,
4909 ) -> Option<Task<Result<()>>> {
4910 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4911 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4912 }
4913
4914 fn do_completion(
4915 &mut self,
4916 item_ix: Option<usize>,
4917 intent: CompletionIntent,
4918 window: &mut Window,
4919 cx: &mut Context<Editor>,
4920 ) -> Option<Task<Result<()>>> {
4921 use language::ToOffset as _;
4922
4923 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4924 else {
4925 return None;
4926 };
4927
4928 let candidate_id = {
4929 let entries = completions_menu.entries.borrow();
4930 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4931 if self.show_edit_predictions_in_menu() {
4932 self.discard_inline_completion(true, cx);
4933 }
4934 mat.candidate_id
4935 };
4936
4937 let buffer_handle = completions_menu.buffer;
4938 let completion = completions_menu
4939 .completions
4940 .borrow()
4941 .get(candidate_id)?
4942 .clone();
4943 cx.stop_propagation();
4944
4945 let snippet;
4946 let new_text;
4947 if completion.is_snippet() {
4948 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4949 new_text = snippet.as_ref().unwrap().text.clone();
4950 } else {
4951 snippet = None;
4952 new_text = completion.new_text.clone();
4953 };
4954
4955 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4956 let buffer = buffer_handle.read(cx);
4957 let snapshot = self.buffer.read(cx).snapshot(cx);
4958 let replace_range_multibuffer = {
4959 let excerpt = snapshot
4960 .excerpt_containing(self.selections.newest_anchor().range())
4961 .unwrap();
4962 let multibuffer_anchor = snapshot
4963 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4964 .unwrap()
4965 ..snapshot
4966 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4967 .unwrap();
4968 multibuffer_anchor.start.to_offset(&snapshot)
4969 ..multibuffer_anchor.end.to_offset(&snapshot)
4970 };
4971 let newest_anchor = self.selections.newest_anchor();
4972 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4973 return None;
4974 }
4975
4976 let old_text = buffer
4977 .text_for_range(replace_range.clone())
4978 .collect::<String>();
4979 let lookbehind = newest_anchor
4980 .start
4981 .text_anchor
4982 .to_offset(buffer)
4983 .saturating_sub(replace_range.start);
4984 let lookahead = replace_range
4985 .end
4986 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4987 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4988 let suffix = &old_text[lookbehind.min(old_text.len())..];
4989
4990 let selections = self.selections.all::<usize>(cx);
4991 let mut ranges = Vec::new();
4992 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4993
4994 for selection in &selections {
4995 let range = if selection.id == newest_anchor.id {
4996 replace_range_multibuffer.clone()
4997 } else {
4998 let mut range = selection.range();
4999
5000 // if prefix is present, don't duplicate it
5001 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5002 range.start = range.start.saturating_sub(lookbehind);
5003
5004 // if suffix is also present, mimic the newest cursor and replace it
5005 if selection.id != newest_anchor.id
5006 && snapshot.contains_str_at(range.end, suffix)
5007 {
5008 range.end += lookahead;
5009 }
5010 }
5011 range
5012 };
5013
5014 ranges.push(range.clone());
5015
5016 if !self.linked_edit_ranges.is_empty() {
5017 let start_anchor = snapshot.anchor_before(range.start);
5018 let end_anchor = snapshot.anchor_after(range.end);
5019 if let Some(ranges) = self
5020 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5021 {
5022 for (buffer, edits) in ranges {
5023 linked_edits
5024 .entry(buffer.clone())
5025 .or_default()
5026 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5027 }
5028 }
5029 }
5030 }
5031
5032 cx.emit(EditorEvent::InputHandled {
5033 utf16_range_to_replace: None,
5034 text: new_text.clone().into(),
5035 });
5036
5037 self.transact(window, cx, |this, window, cx| {
5038 if let Some(mut snippet) = snippet {
5039 snippet.text = new_text.to_string();
5040 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5041 } else {
5042 this.buffer.update(cx, |buffer, cx| {
5043 let auto_indent = match completion.insert_text_mode {
5044 Some(InsertTextMode::AS_IS) => None,
5045 _ => this.autoindent_mode.clone(),
5046 };
5047 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5048 buffer.edit(edits, auto_indent, cx);
5049 });
5050 }
5051 for (buffer, edits) in linked_edits {
5052 buffer.update(cx, |buffer, cx| {
5053 let snapshot = buffer.snapshot();
5054 let edits = edits
5055 .into_iter()
5056 .map(|(range, text)| {
5057 use text::ToPoint as TP;
5058 let end_point = TP::to_point(&range.end, &snapshot);
5059 let start_point = TP::to_point(&range.start, &snapshot);
5060 (start_point..end_point, text)
5061 })
5062 .sorted_by_key(|(range, _)| range.start);
5063 buffer.edit(edits, None, cx);
5064 })
5065 }
5066
5067 this.refresh_inline_completion(true, false, window, cx);
5068 });
5069
5070 let show_new_completions_on_confirm = completion
5071 .confirm
5072 .as_ref()
5073 .map_or(false, |confirm| confirm(intent, window, cx));
5074 if show_new_completions_on_confirm {
5075 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5076 }
5077
5078 let provider = self.completion_provider.as_ref()?;
5079 drop(completion);
5080 let apply_edits = provider.apply_additional_edits_for_completion(
5081 buffer_handle,
5082 completions_menu.completions.clone(),
5083 candidate_id,
5084 true,
5085 cx,
5086 );
5087
5088 let editor_settings = EditorSettings::get_global(cx);
5089 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5090 // After the code completion is finished, users often want to know what signatures are needed.
5091 // so we should automatically call signature_help
5092 self.show_signature_help(&ShowSignatureHelp, window, cx);
5093 }
5094
5095 Some(cx.foreground_executor().spawn(async move {
5096 apply_edits.await?;
5097 Ok(())
5098 }))
5099 }
5100
5101 pub fn toggle_code_actions(
5102 &mut self,
5103 action: &ToggleCodeActions,
5104 window: &mut Window,
5105 cx: &mut Context<Self>,
5106 ) {
5107 let quick_launch = action.quick_launch;
5108 let mut context_menu = self.context_menu.borrow_mut();
5109 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5110 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5111 // Toggle if we're selecting the same one
5112 *context_menu = None;
5113 cx.notify();
5114 return;
5115 } else {
5116 // Otherwise, clear it and start a new one
5117 *context_menu = None;
5118 cx.notify();
5119 }
5120 }
5121 drop(context_menu);
5122 let snapshot = self.snapshot(window, cx);
5123 let deployed_from_indicator = action.deployed_from_indicator;
5124 let mut task = self.code_actions_task.take();
5125 let action = action.clone();
5126 cx.spawn_in(window, async move |editor, cx| {
5127 while let Some(prev_task) = task {
5128 prev_task.await.log_err();
5129 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5130 }
5131
5132 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5133 if editor.focus_handle.is_focused(window) {
5134 let multibuffer_point = action
5135 .deployed_from_indicator
5136 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5137 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5138 let (buffer, buffer_row) = snapshot
5139 .buffer_snapshot
5140 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5141 .and_then(|(buffer_snapshot, range)| {
5142 editor
5143 .buffer
5144 .read(cx)
5145 .buffer(buffer_snapshot.remote_id())
5146 .map(|buffer| (buffer, range.start.row))
5147 })?;
5148 let (_, code_actions) = editor
5149 .available_code_actions
5150 .clone()
5151 .and_then(|(location, code_actions)| {
5152 let snapshot = location.buffer.read(cx).snapshot();
5153 let point_range = location.range.to_point(&snapshot);
5154 let point_range = point_range.start.row..=point_range.end.row;
5155 if point_range.contains(&buffer_row) {
5156 Some((location, code_actions))
5157 } else {
5158 None
5159 }
5160 })
5161 .unzip();
5162 let buffer_id = buffer.read(cx).remote_id();
5163 let tasks = editor
5164 .tasks
5165 .get(&(buffer_id, buffer_row))
5166 .map(|t| Arc::new(t.to_owned()));
5167 if tasks.is_none() && code_actions.is_none() {
5168 return None;
5169 }
5170
5171 editor.completion_tasks.clear();
5172 editor.discard_inline_completion(false, cx);
5173 let task_context =
5174 tasks
5175 .as_ref()
5176 .zip(editor.project.clone())
5177 .map(|(tasks, project)| {
5178 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5179 });
5180
5181 Some(cx.spawn_in(window, async move |editor, cx| {
5182 let task_context = match task_context {
5183 Some(task_context) => task_context.await,
5184 None => None,
5185 };
5186 let resolved_tasks =
5187 tasks
5188 .zip(task_context.clone())
5189 .map(|(tasks, task_context)| ResolvedTasks {
5190 templates: tasks.resolve(&task_context).collect(),
5191 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5192 multibuffer_point.row,
5193 tasks.column,
5194 )),
5195 });
5196 let spawn_straight_away = quick_launch
5197 && resolved_tasks
5198 .as_ref()
5199 .map_or(false, |tasks| tasks.templates.len() == 1)
5200 && code_actions
5201 .as_ref()
5202 .map_or(true, |actions| actions.is_empty());
5203 let debug_scenarios = editor.update(cx, |editor, cx| {
5204 if cx.has_flag::<DebuggerFeatureFlag>() {
5205 maybe!({
5206 let project = editor.project.as_ref()?;
5207 let dap_store = project.read(cx).dap_store();
5208 let mut scenarios = vec![];
5209 let resolved_tasks = resolved_tasks.as_ref()?;
5210 let debug_adapter: SharedString = buffer
5211 .read(cx)
5212 .language()?
5213 .context_provider()?
5214 .debug_adapter()?
5215 .into();
5216 dap_store.update(cx, |this, cx| {
5217 for (_, task) in &resolved_tasks.templates {
5218 if let Some(scenario) = this
5219 .debug_scenario_for_build_task(
5220 task.resolved.clone(),
5221 SharedString::from(
5222 task.original_task().label.clone(),
5223 ),
5224 debug_adapter.clone(),
5225 cx,
5226 )
5227 {
5228 scenarios.push(scenario);
5229 }
5230 }
5231 });
5232 Some(scenarios)
5233 })
5234 .unwrap_or_default()
5235 } else {
5236 vec![]
5237 }
5238 })?;
5239 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5240 *editor.context_menu.borrow_mut() =
5241 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5242 buffer,
5243 actions: CodeActionContents::new(
5244 resolved_tasks,
5245 code_actions,
5246 debug_scenarios,
5247 task_context.unwrap_or_default(),
5248 ),
5249 selected_item: Default::default(),
5250 scroll_handle: UniformListScrollHandle::default(),
5251 deployed_from_indicator,
5252 }));
5253 if spawn_straight_away {
5254 if let Some(task) = editor.confirm_code_action(
5255 &ConfirmCodeAction { item_ix: Some(0) },
5256 window,
5257 cx,
5258 ) {
5259 cx.notify();
5260 return task;
5261 }
5262 }
5263 cx.notify();
5264 Task::ready(Ok(()))
5265 }) {
5266 task.await
5267 } else {
5268 Ok(())
5269 }
5270 }))
5271 } else {
5272 Some(Task::ready(Ok(())))
5273 }
5274 })?;
5275 if let Some(task) = spawned_test_task {
5276 task.await?;
5277 }
5278
5279 Ok::<_, anyhow::Error>(())
5280 })
5281 .detach_and_log_err(cx);
5282 }
5283
5284 pub fn confirm_code_action(
5285 &mut self,
5286 action: &ConfirmCodeAction,
5287 window: &mut Window,
5288 cx: &mut Context<Self>,
5289 ) -> Option<Task<Result<()>>> {
5290 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5291
5292 let actions_menu =
5293 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5294 menu
5295 } else {
5296 return None;
5297 };
5298
5299 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5300 let action = actions_menu.actions.get(action_ix)?;
5301 let title = action.label();
5302 let buffer = actions_menu.buffer;
5303 let workspace = self.workspace()?;
5304
5305 match action {
5306 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5307 workspace.update(cx, |workspace, cx| {
5308 workspace.schedule_resolved_task(
5309 task_source_kind,
5310 resolved_task,
5311 false,
5312 window,
5313 cx,
5314 );
5315
5316 Some(Task::ready(Ok(())))
5317 })
5318 }
5319 CodeActionsItem::CodeAction {
5320 excerpt_id,
5321 action,
5322 provider,
5323 } => {
5324 let apply_code_action =
5325 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5326 let workspace = workspace.downgrade();
5327 Some(cx.spawn_in(window, async move |editor, cx| {
5328 let project_transaction = apply_code_action.await?;
5329 Self::open_project_transaction(
5330 &editor,
5331 workspace,
5332 project_transaction,
5333 title,
5334 cx,
5335 )
5336 .await
5337 }))
5338 }
5339 CodeActionsItem::DebugScenario(scenario) => {
5340 let context = actions_menu.actions.context.clone();
5341
5342 workspace.update(cx, |workspace, cx| {
5343 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5344 });
5345 Some(Task::ready(Ok(())))
5346 }
5347 }
5348 }
5349
5350 pub async fn open_project_transaction(
5351 this: &WeakEntity<Editor>,
5352 workspace: WeakEntity<Workspace>,
5353 transaction: ProjectTransaction,
5354 title: String,
5355 cx: &mut AsyncWindowContext,
5356 ) -> Result<()> {
5357 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5358 cx.update(|_, cx| {
5359 entries.sort_unstable_by_key(|(buffer, _)| {
5360 buffer.read(cx).file().map(|f| f.path().clone())
5361 });
5362 })?;
5363
5364 // If the project transaction's edits are all contained within this editor, then
5365 // avoid opening a new editor to display them.
5366
5367 if let Some((buffer, transaction)) = entries.first() {
5368 if entries.len() == 1 {
5369 let excerpt = this.update(cx, |editor, cx| {
5370 editor
5371 .buffer()
5372 .read(cx)
5373 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5374 })?;
5375 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5376 if excerpted_buffer == *buffer {
5377 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5378 let excerpt_range = excerpt_range.to_offset(buffer);
5379 buffer
5380 .edited_ranges_for_transaction::<usize>(transaction)
5381 .all(|range| {
5382 excerpt_range.start <= range.start
5383 && excerpt_range.end >= range.end
5384 })
5385 })?;
5386
5387 if all_edits_within_excerpt {
5388 return Ok(());
5389 }
5390 }
5391 }
5392 }
5393 } else {
5394 return Ok(());
5395 }
5396
5397 let mut ranges_to_highlight = Vec::new();
5398 let excerpt_buffer = cx.new(|cx| {
5399 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5400 for (buffer_handle, transaction) in &entries {
5401 let edited_ranges = buffer_handle
5402 .read(cx)
5403 .edited_ranges_for_transaction::<Point>(transaction)
5404 .collect::<Vec<_>>();
5405 let (ranges, _) = multibuffer.set_excerpts_for_path(
5406 PathKey::for_buffer(buffer_handle, cx),
5407 buffer_handle.clone(),
5408 edited_ranges,
5409 DEFAULT_MULTIBUFFER_CONTEXT,
5410 cx,
5411 );
5412
5413 ranges_to_highlight.extend(ranges);
5414 }
5415 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5416 multibuffer
5417 })?;
5418
5419 workspace.update_in(cx, |workspace, window, cx| {
5420 let project = workspace.project().clone();
5421 let editor =
5422 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5423 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5424 editor.update(cx, |editor, cx| {
5425 editor.highlight_background::<Self>(
5426 &ranges_to_highlight,
5427 |theme| theme.editor_highlighted_line_background,
5428 cx,
5429 );
5430 });
5431 })?;
5432
5433 Ok(())
5434 }
5435
5436 pub fn clear_code_action_providers(&mut self) {
5437 self.code_action_providers.clear();
5438 self.available_code_actions.take();
5439 }
5440
5441 pub fn add_code_action_provider(
5442 &mut self,
5443 provider: Rc<dyn CodeActionProvider>,
5444 window: &mut Window,
5445 cx: &mut Context<Self>,
5446 ) {
5447 if self
5448 .code_action_providers
5449 .iter()
5450 .any(|existing_provider| existing_provider.id() == provider.id())
5451 {
5452 return;
5453 }
5454
5455 self.code_action_providers.push(provider);
5456 self.refresh_code_actions(window, cx);
5457 }
5458
5459 pub fn remove_code_action_provider(
5460 &mut self,
5461 id: Arc<str>,
5462 window: &mut Window,
5463 cx: &mut Context<Self>,
5464 ) {
5465 self.code_action_providers
5466 .retain(|provider| provider.id() != id);
5467 self.refresh_code_actions(window, cx);
5468 }
5469
5470 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5471 let newest_selection = self.selections.newest_anchor().clone();
5472 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5473 let buffer = self.buffer.read(cx);
5474 if newest_selection.head().diff_base_anchor.is_some() {
5475 return None;
5476 }
5477 let (start_buffer, start) =
5478 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5479 let (end_buffer, end) =
5480 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5481 if start_buffer != end_buffer {
5482 return None;
5483 }
5484
5485 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5486 cx.background_executor()
5487 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5488 .await;
5489
5490 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5491 let providers = this.code_action_providers.clone();
5492 let tasks = this
5493 .code_action_providers
5494 .iter()
5495 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5496 .collect::<Vec<_>>();
5497 (providers, tasks)
5498 })?;
5499
5500 let mut actions = Vec::new();
5501 for (provider, provider_actions) in
5502 providers.into_iter().zip(future::join_all(tasks).await)
5503 {
5504 if let Some(provider_actions) = provider_actions.log_err() {
5505 actions.extend(provider_actions.into_iter().map(|action| {
5506 AvailableCodeAction {
5507 excerpt_id: newest_selection.start.excerpt_id,
5508 action,
5509 provider: provider.clone(),
5510 }
5511 }));
5512 }
5513 }
5514
5515 this.update(cx, |this, cx| {
5516 this.available_code_actions = if actions.is_empty() {
5517 None
5518 } else {
5519 Some((
5520 Location {
5521 buffer: start_buffer,
5522 range: start..end,
5523 },
5524 actions.into(),
5525 ))
5526 };
5527 cx.notify();
5528 })
5529 }));
5530 None
5531 }
5532
5533 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5534 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5535 self.show_git_blame_inline = false;
5536
5537 self.show_git_blame_inline_delay_task =
5538 Some(cx.spawn_in(window, async move |this, cx| {
5539 cx.background_executor().timer(delay).await;
5540
5541 this.update(cx, |this, cx| {
5542 this.show_git_blame_inline = true;
5543 cx.notify();
5544 })
5545 .log_err();
5546 }));
5547 }
5548 }
5549
5550 fn show_blame_popover(
5551 &mut self,
5552 blame_entry: &BlameEntry,
5553 position: gpui::Point<Pixels>,
5554 cx: &mut Context<Self>,
5555 ) {
5556 if let Some(state) = &mut self.inline_blame_popover {
5557 state.hide_task.take();
5558 cx.notify();
5559 } else {
5560 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5561 let show_task = cx.spawn(async move |editor, cx| {
5562 cx.background_executor()
5563 .timer(std::time::Duration::from_millis(delay))
5564 .await;
5565 editor
5566 .update(cx, |editor, cx| {
5567 if let Some(state) = &mut editor.inline_blame_popover {
5568 state.show_task = None;
5569 cx.notify();
5570 }
5571 })
5572 .ok();
5573 });
5574 let Some(blame) = self.blame.as_ref() else {
5575 return;
5576 };
5577 let blame = blame.read(cx);
5578 let details = blame.details_for_entry(&blame_entry);
5579 let markdown = cx.new(|cx| {
5580 Markdown::new(
5581 details
5582 .as_ref()
5583 .map(|message| message.message.clone())
5584 .unwrap_or_default(),
5585 None,
5586 None,
5587 cx,
5588 )
5589 });
5590 self.inline_blame_popover = Some(InlineBlamePopover {
5591 position,
5592 show_task: Some(show_task),
5593 hide_task: None,
5594 popover_bounds: None,
5595 popover_state: InlineBlamePopoverState {
5596 scroll_handle: ScrollHandle::new(),
5597 commit_message: details,
5598 markdown,
5599 },
5600 });
5601 }
5602 }
5603
5604 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5605 if let Some(state) = &mut self.inline_blame_popover {
5606 if state.show_task.is_some() {
5607 self.inline_blame_popover.take();
5608 cx.notify();
5609 } else {
5610 let hide_task = cx.spawn(async move |editor, cx| {
5611 cx.background_executor()
5612 .timer(std::time::Duration::from_millis(100))
5613 .await;
5614 editor
5615 .update(cx, |editor, cx| {
5616 editor.inline_blame_popover.take();
5617 cx.notify();
5618 })
5619 .ok();
5620 });
5621 state.hide_task = Some(hide_task);
5622 }
5623 }
5624 }
5625
5626 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5627 if self.pending_rename.is_some() {
5628 return None;
5629 }
5630
5631 let provider = self.semantics_provider.clone()?;
5632 let buffer = self.buffer.read(cx);
5633 let newest_selection = self.selections.newest_anchor().clone();
5634 let cursor_position = newest_selection.head();
5635 let (cursor_buffer, cursor_buffer_position) =
5636 buffer.text_anchor_for_position(cursor_position, cx)?;
5637 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5638 if cursor_buffer != tail_buffer {
5639 return None;
5640 }
5641 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5642 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5643 cx.background_executor()
5644 .timer(Duration::from_millis(debounce))
5645 .await;
5646
5647 let highlights = if let Some(highlights) = cx
5648 .update(|cx| {
5649 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5650 })
5651 .ok()
5652 .flatten()
5653 {
5654 highlights.await.log_err()
5655 } else {
5656 None
5657 };
5658
5659 if let Some(highlights) = highlights {
5660 this.update(cx, |this, cx| {
5661 if this.pending_rename.is_some() {
5662 return;
5663 }
5664
5665 let buffer_id = cursor_position.buffer_id;
5666 let buffer = this.buffer.read(cx);
5667 if !buffer
5668 .text_anchor_for_position(cursor_position, cx)
5669 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5670 {
5671 return;
5672 }
5673
5674 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5675 let mut write_ranges = Vec::new();
5676 let mut read_ranges = Vec::new();
5677 for highlight in highlights {
5678 for (excerpt_id, excerpt_range) in
5679 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5680 {
5681 let start = highlight
5682 .range
5683 .start
5684 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5685 let end = highlight
5686 .range
5687 .end
5688 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5689 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5690 continue;
5691 }
5692
5693 let range = Anchor {
5694 buffer_id,
5695 excerpt_id,
5696 text_anchor: start,
5697 diff_base_anchor: None,
5698 }..Anchor {
5699 buffer_id,
5700 excerpt_id,
5701 text_anchor: end,
5702 diff_base_anchor: None,
5703 };
5704 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5705 write_ranges.push(range);
5706 } else {
5707 read_ranges.push(range);
5708 }
5709 }
5710 }
5711
5712 this.highlight_background::<DocumentHighlightRead>(
5713 &read_ranges,
5714 |theme| theme.editor_document_highlight_read_background,
5715 cx,
5716 );
5717 this.highlight_background::<DocumentHighlightWrite>(
5718 &write_ranges,
5719 |theme| theme.editor_document_highlight_write_background,
5720 cx,
5721 );
5722 cx.notify();
5723 })
5724 .log_err();
5725 }
5726 }));
5727 None
5728 }
5729
5730 fn prepare_highlight_query_from_selection(
5731 &mut self,
5732 cx: &mut Context<Editor>,
5733 ) -> Option<(String, Range<Anchor>)> {
5734 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5735 return None;
5736 }
5737 if !EditorSettings::get_global(cx).selection_highlight {
5738 return None;
5739 }
5740 if self.selections.count() != 1 || self.selections.line_mode {
5741 return None;
5742 }
5743 let selection = self.selections.newest::<Point>(cx);
5744 if selection.is_empty() || selection.start.row != selection.end.row {
5745 return None;
5746 }
5747 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5748 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5749 let query = multi_buffer_snapshot
5750 .text_for_range(selection_anchor_range.clone())
5751 .collect::<String>();
5752 if query.trim().is_empty() {
5753 return None;
5754 }
5755 Some((query, selection_anchor_range))
5756 }
5757
5758 fn update_selection_occurrence_highlights(
5759 &mut self,
5760 query_text: String,
5761 query_range: Range<Anchor>,
5762 multi_buffer_range_to_query: Range<Point>,
5763 use_debounce: bool,
5764 window: &mut Window,
5765 cx: &mut Context<Editor>,
5766 ) -> Task<()> {
5767 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5768 cx.spawn_in(window, async move |editor, cx| {
5769 if use_debounce {
5770 cx.background_executor()
5771 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5772 .await;
5773 }
5774 let match_task = cx.background_spawn(async move {
5775 let buffer_ranges = multi_buffer_snapshot
5776 .range_to_buffer_ranges(multi_buffer_range_to_query)
5777 .into_iter()
5778 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5779 let mut match_ranges = Vec::new();
5780 let Ok(regex) = project::search::SearchQuery::text(
5781 query_text.clone(),
5782 false,
5783 false,
5784 false,
5785 Default::default(),
5786 Default::default(),
5787 false,
5788 None,
5789 ) else {
5790 return Vec::default();
5791 };
5792 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5793 match_ranges.extend(
5794 regex
5795 .search(&buffer_snapshot, Some(search_range.clone()))
5796 .await
5797 .into_iter()
5798 .filter_map(|match_range| {
5799 let match_start = buffer_snapshot
5800 .anchor_after(search_range.start + match_range.start);
5801 let match_end = buffer_snapshot
5802 .anchor_before(search_range.start + match_range.end);
5803 let match_anchor_range = Anchor::range_in_buffer(
5804 excerpt_id,
5805 buffer_snapshot.remote_id(),
5806 match_start..match_end,
5807 );
5808 (match_anchor_range != query_range).then_some(match_anchor_range)
5809 }),
5810 );
5811 }
5812 match_ranges
5813 });
5814 let match_ranges = match_task.await;
5815 editor
5816 .update_in(cx, |editor, _, cx| {
5817 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5818 if !match_ranges.is_empty() {
5819 editor.highlight_background::<SelectedTextHighlight>(
5820 &match_ranges,
5821 |theme| theme.editor_document_highlight_bracket_background,
5822 cx,
5823 )
5824 }
5825 })
5826 .log_err();
5827 })
5828 }
5829
5830 fn refresh_selected_text_highlights(
5831 &mut self,
5832 on_buffer_edit: bool,
5833 window: &mut Window,
5834 cx: &mut Context<Editor>,
5835 ) {
5836 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5837 else {
5838 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5839 self.quick_selection_highlight_task.take();
5840 self.debounced_selection_highlight_task.take();
5841 return;
5842 };
5843 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5844 if on_buffer_edit
5845 || self
5846 .quick_selection_highlight_task
5847 .as_ref()
5848 .map_or(true, |(prev_anchor_range, _)| {
5849 prev_anchor_range != &query_range
5850 })
5851 {
5852 let multi_buffer_visible_start = self
5853 .scroll_manager
5854 .anchor()
5855 .anchor
5856 .to_point(&multi_buffer_snapshot);
5857 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5858 multi_buffer_visible_start
5859 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5860 Bias::Left,
5861 );
5862 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5863 self.quick_selection_highlight_task = Some((
5864 query_range.clone(),
5865 self.update_selection_occurrence_highlights(
5866 query_text.clone(),
5867 query_range.clone(),
5868 multi_buffer_visible_range,
5869 false,
5870 window,
5871 cx,
5872 ),
5873 ));
5874 }
5875 if on_buffer_edit
5876 || self
5877 .debounced_selection_highlight_task
5878 .as_ref()
5879 .map_or(true, |(prev_anchor_range, _)| {
5880 prev_anchor_range != &query_range
5881 })
5882 {
5883 let multi_buffer_start = multi_buffer_snapshot
5884 .anchor_before(0)
5885 .to_point(&multi_buffer_snapshot);
5886 let multi_buffer_end = multi_buffer_snapshot
5887 .anchor_after(multi_buffer_snapshot.len())
5888 .to_point(&multi_buffer_snapshot);
5889 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5890 self.debounced_selection_highlight_task = Some((
5891 query_range.clone(),
5892 self.update_selection_occurrence_highlights(
5893 query_text,
5894 query_range,
5895 multi_buffer_full_range,
5896 true,
5897 window,
5898 cx,
5899 ),
5900 ));
5901 }
5902 }
5903
5904 pub fn refresh_inline_completion(
5905 &mut self,
5906 debounce: bool,
5907 user_requested: bool,
5908 window: &mut Window,
5909 cx: &mut Context<Self>,
5910 ) -> Option<()> {
5911 let provider = self.edit_prediction_provider()?;
5912 let cursor = self.selections.newest_anchor().head();
5913 let (buffer, cursor_buffer_position) =
5914 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5915
5916 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5917 self.discard_inline_completion(false, cx);
5918 return None;
5919 }
5920
5921 if !user_requested
5922 && (!self.should_show_edit_predictions()
5923 || !self.is_focused(window)
5924 || buffer.read(cx).is_empty())
5925 {
5926 self.discard_inline_completion(false, cx);
5927 return None;
5928 }
5929
5930 self.update_visible_inline_completion(window, cx);
5931 provider.refresh(
5932 self.project.clone(),
5933 buffer,
5934 cursor_buffer_position,
5935 debounce,
5936 cx,
5937 );
5938 Some(())
5939 }
5940
5941 fn show_edit_predictions_in_menu(&self) -> bool {
5942 match self.edit_prediction_settings {
5943 EditPredictionSettings::Disabled => false,
5944 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5945 }
5946 }
5947
5948 pub fn edit_predictions_enabled(&self) -> bool {
5949 match self.edit_prediction_settings {
5950 EditPredictionSettings::Disabled => false,
5951 EditPredictionSettings::Enabled { .. } => true,
5952 }
5953 }
5954
5955 fn edit_prediction_requires_modifier(&self) -> bool {
5956 match self.edit_prediction_settings {
5957 EditPredictionSettings::Disabled => false,
5958 EditPredictionSettings::Enabled {
5959 preview_requires_modifier,
5960 ..
5961 } => preview_requires_modifier,
5962 }
5963 }
5964
5965 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5966 if self.edit_prediction_provider.is_none() {
5967 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5968 } else {
5969 let selection = self.selections.newest_anchor();
5970 let cursor = selection.head();
5971
5972 if let Some((buffer, cursor_buffer_position)) =
5973 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5974 {
5975 self.edit_prediction_settings =
5976 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5977 }
5978 }
5979 }
5980
5981 fn edit_prediction_settings_at_position(
5982 &self,
5983 buffer: &Entity<Buffer>,
5984 buffer_position: language::Anchor,
5985 cx: &App,
5986 ) -> EditPredictionSettings {
5987 if !self.mode.is_full()
5988 || !self.show_inline_completions_override.unwrap_or(true)
5989 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5990 {
5991 return EditPredictionSettings::Disabled;
5992 }
5993
5994 let buffer = buffer.read(cx);
5995
5996 let file = buffer.file();
5997
5998 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5999 return EditPredictionSettings::Disabled;
6000 };
6001
6002 let by_provider = matches!(
6003 self.menu_inline_completions_policy,
6004 MenuInlineCompletionsPolicy::ByProvider
6005 );
6006
6007 let show_in_menu = by_provider
6008 && self
6009 .edit_prediction_provider
6010 .as_ref()
6011 .map_or(false, |provider| {
6012 provider.provider.show_completions_in_menu()
6013 });
6014
6015 let preview_requires_modifier =
6016 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6017
6018 EditPredictionSettings::Enabled {
6019 show_in_menu,
6020 preview_requires_modifier,
6021 }
6022 }
6023
6024 fn should_show_edit_predictions(&self) -> bool {
6025 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6026 }
6027
6028 pub fn edit_prediction_preview_is_active(&self) -> bool {
6029 matches!(
6030 self.edit_prediction_preview,
6031 EditPredictionPreview::Active { .. }
6032 )
6033 }
6034
6035 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6036 let cursor = self.selections.newest_anchor().head();
6037 if let Some((buffer, cursor_position)) =
6038 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6039 {
6040 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6041 } else {
6042 false
6043 }
6044 }
6045
6046 fn edit_predictions_enabled_in_buffer(
6047 &self,
6048 buffer: &Entity<Buffer>,
6049 buffer_position: language::Anchor,
6050 cx: &App,
6051 ) -> bool {
6052 maybe!({
6053 if self.read_only(cx) {
6054 return Some(false);
6055 }
6056 let provider = self.edit_prediction_provider()?;
6057 if !provider.is_enabled(&buffer, buffer_position, cx) {
6058 return Some(false);
6059 }
6060 let buffer = buffer.read(cx);
6061 let Some(file) = buffer.file() else {
6062 return Some(true);
6063 };
6064 let settings = all_language_settings(Some(file), cx);
6065 Some(settings.edit_predictions_enabled_for_file(file, cx))
6066 })
6067 .unwrap_or(false)
6068 }
6069
6070 fn cycle_inline_completion(
6071 &mut self,
6072 direction: Direction,
6073 window: &mut Window,
6074 cx: &mut Context<Self>,
6075 ) -> Option<()> {
6076 let provider = self.edit_prediction_provider()?;
6077 let cursor = self.selections.newest_anchor().head();
6078 let (buffer, cursor_buffer_position) =
6079 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6080 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6081 return None;
6082 }
6083
6084 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6085 self.update_visible_inline_completion(window, cx);
6086
6087 Some(())
6088 }
6089
6090 pub fn show_inline_completion(
6091 &mut self,
6092 _: &ShowEditPrediction,
6093 window: &mut Window,
6094 cx: &mut Context<Self>,
6095 ) {
6096 if !self.has_active_inline_completion() {
6097 self.refresh_inline_completion(false, true, window, cx);
6098 return;
6099 }
6100
6101 self.update_visible_inline_completion(window, cx);
6102 }
6103
6104 pub fn display_cursor_names(
6105 &mut self,
6106 _: &DisplayCursorNames,
6107 window: &mut Window,
6108 cx: &mut Context<Self>,
6109 ) {
6110 self.show_cursor_names(window, cx);
6111 }
6112
6113 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6114 self.show_cursor_names = true;
6115 cx.notify();
6116 cx.spawn_in(window, async move |this, cx| {
6117 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6118 this.update(cx, |this, cx| {
6119 this.show_cursor_names = false;
6120 cx.notify()
6121 })
6122 .ok()
6123 })
6124 .detach();
6125 }
6126
6127 pub fn next_edit_prediction(
6128 &mut self,
6129 _: &NextEditPrediction,
6130 window: &mut Window,
6131 cx: &mut Context<Self>,
6132 ) {
6133 if self.has_active_inline_completion() {
6134 self.cycle_inline_completion(Direction::Next, window, cx);
6135 } else {
6136 let is_copilot_disabled = self
6137 .refresh_inline_completion(false, true, window, cx)
6138 .is_none();
6139 if is_copilot_disabled {
6140 cx.propagate();
6141 }
6142 }
6143 }
6144
6145 pub fn previous_edit_prediction(
6146 &mut self,
6147 _: &PreviousEditPrediction,
6148 window: &mut Window,
6149 cx: &mut Context<Self>,
6150 ) {
6151 if self.has_active_inline_completion() {
6152 self.cycle_inline_completion(Direction::Prev, window, cx);
6153 } else {
6154 let is_copilot_disabled = self
6155 .refresh_inline_completion(false, true, window, cx)
6156 .is_none();
6157 if is_copilot_disabled {
6158 cx.propagate();
6159 }
6160 }
6161 }
6162
6163 pub fn accept_edit_prediction(
6164 &mut self,
6165 _: &AcceptEditPrediction,
6166 window: &mut Window,
6167 cx: &mut Context<Self>,
6168 ) {
6169 if self.show_edit_predictions_in_menu() {
6170 self.hide_context_menu(window, cx);
6171 }
6172
6173 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6174 return;
6175 };
6176
6177 self.report_inline_completion_event(
6178 active_inline_completion.completion_id.clone(),
6179 true,
6180 cx,
6181 );
6182
6183 match &active_inline_completion.completion {
6184 InlineCompletion::Move { target, .. } => {
6185 let target = *target;
6186
6187 if let Some(position_map) = &self.last_position_map {
6188 if position_map
6189 .visible_row_range
6190 .contains(&target.to_display_point(&position_map.snapshot).row())
6191 || !self.edit_prediction_requires_modifier()
6192 {
6193 self.unfold_ranges(&[target..target], true, false, cx);
6194 // Note that this is also done in vim's handler of the Tab action.
6195 self.change_selections(
6196 Some(Autoscroll::newest()),
6197 window,
6198 cx,
6199 |selections| {
6200 selections.select_anchor_ranges([target..target]);
6201 },
6202 );
6203 self.clear_row_highlights::<EditPredictionPreview>();
6204
6205 self.edit_prediction_preview
6206 .set_previous_scroll_position(None);
6207 } else {
6208 self.edit_prediction_preview
6209 .set_previous_scroll_position(Some(
6210 position_map.snapshot.scroll_anchor,
6211 ));
6212
6213 self.highlight_rows::<EditPredictionPreview>(
6214 target..target,
6215 cx.theme().colors().editor_highlighted_line_background,
6216 RowHighlightOptions {
6217 autoscroll: true,
6218 ..Default::default()
6219 },
6220 cx,
6221 );
6222 self.request_autoscroll(Autoscroll::fit(), cx);
6223 }
6224 }
6225 }
6226 InlineCompletion::Edit { edits, .. } => {
6227 if let Some(provider) = self.edit_prediction_provider() {
6228 provider.accept(cx);
6229 }
6230
6231 let snapshot = self.buffer.read(cx).snapshot(cx);
6232 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6233
6234 self.buffer.update(cx, |buffer, cx| {
6235 buffer.edit(edits.iter().cloned(), None, cx)
6236 });
6237
6238 self.change_selections(None, window, cx, |s| {
6239 s.select_anchor_ranges([last_edit_end..last_edit_end])
6240 });
6241
6242 self.update_visible_inline_completion(window, cx);
6243 if self.active_inline_completion.is_none() {
6244 self.refresh_inline_completion(true, true, window, cx);
6245 }
6246
6247 cx.notify();
6248 }
6249 }
6250
6251 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6252 }
6253
6254 pub fn accept_partial_inline_completion(
6255 &mut self,
6256 _: &AcceptPartialEditPrediction,
6257 window: &mut Window,
6258 cx: &mut Context<Self>,
6259 ) {
6260 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6261 return;
6262 };
6263 if self.selections.count() != 1 {
6264 return;
6265 }
6266
6267 self.report_inline_completion_event(
6268 active_inline_completion.completion_id.clone(),
6269 true,
6270 cx,
6271 );
6272
6273 match &active_inline_completion.completion {
6274 InlineCompletion::Move { target, .. } => {
6275 let target = *target;
6276 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6277 selections.select_anchor_ranges([target..target]);
6278 });
6279 }
6280 InlineCompletion::Edit { edits, .. } => {
6281 // Find an insertion that starts at the cursor position.
6282 let snapshot = self.buffer.read(cx).snapshot(cx);
6283 let cursor_offset = self.selections.newest::<usize>(cx).head();
6284 let insertion = edits.iter().find_map(|(range, text)| {
6285 let range = range.to_offset(&snapshot);
6286 if range.is_empty() && range.start == cursor_offset {
6287 Some(text)
6288 } else {
6289 None
6290 }
6291 });
6292
6293 if let Some(text) = insertion {
6294 let mut partial_completion = text
6295 .chars()
6296 .by_ref()
6297 .take_while(|c| c.is_alphabetic())
6298 .collect::<String>();
6299 if partial_completion.is_empty() {
6300 partial_completion = text
6301 .chars()
6302 .by_ref()
6303 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6304 .collect::<String>();
6305 }
6306
6307 cx.emit(EditorEvent::InputHandled {
6308 utf16_range_to_replace: None,
6309 text: partial_completion.clone().into(),
6310 });
6311
6312 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6313
6314 self.refresh_inline_completion(true, true, window, cx);
6315 cx.notify();
6316 } else {
6317 self.accept_edit_prediction(&Default::default(), window, cx);
6318 }
6319 }
6320 }
6321 }
6322
6323 fn discard_inline_completion(
6324 &mut self,
6325 should_report_inline_completion_event: bool,
6326 cx: &mut Context<Self>,
6327 ) -> bool {
6328 if should_report_inline_completion_event {
6329 let completion_id = self
6330 .active_inline_completion
6331 .as_ref()
6332 .and_then(|active_completion| active_completion.completion_id.clone());
6333
6334 self.report_inline_completion_event(completion_id, false, cx);
6335 }
6336
6337 if let Some(provider) = self.edit_prediction_provider() {
6338 provider.discard(cx);
6339 }
6340
6341 self.take_active_inline_completion(cx)
6342 }
6343
6344 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6345 let Some(provider) = self.edit_prediction_provider() else {
6346 return;
6347 };
6348
6349 let Some((_, buffer, _)) = self
6350 .buffer
6351 .read(cx)
6352 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6353 else {
6354 return;
6355 };
6356
6357 let extension = buffer
6358 .read(cx)
6359 .file()
6360 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6361
6362 let event_type = match accepted {
6363 true => "Edit Prediction Accepted",
6364 false => "Edit Prediction Discarded",
6365 };
6366 telemetry::event!(
6367 event_type,
6368 provider = provider.name(),
6369 prediction_id = id,
6370 suggestion_accepted = accepted,
6371 file_extension = extension,
6372 );
6373 }
6374
6375 pub fn has_active_inline_completion(&self) -> bool {
6376 self.active_inline_completion.is_some()
6377 }
6378
6379 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6380 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6381 return false;
6382 };
6383
6384 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6385 self.clear_highlights::<InlineCompletionHighlight>(cx);
6386 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6387 true
6388 }
6389
6390 /// Returns true when we're displaying the edit prediction popover below the cursor
6391 /// like we are not previewing and the LSP autocomplete menu is visible
6392 /// or we are in `when_holding_modifier` mode.
6393 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6394 if self.edit_prediction_preview_is_active()
6395 || !self.show_edit_predictions_in_menu()
6396 || !self.edit_predictions_enabled()
6397 {
6398 return false;
6399 }
6400
6401 if self.has_visible_completions_menu() {
6402 return true;
6403 }
6404
6405 has_completion && self.edit_prediction_requires_modifier()
6406 }
6407
6408 fn handle_modifiers_changed(
6409 &mut self,
6410 modifiers: Modifiers,
6411 position_map: &PositionMap,
6412 window: &mut Window,
6413 cx: &mut Context<Self>,
6414 ) {
6415 if self.show_edit_predictions_in_menu() {
6416 self.update_edit_prediction_preview(&modifiers, window, cx);
6417 }
6418
6419 self.update_selection_mode(&modifiers, position_map, window, cx);
6420
6421 let mouse_position = window.mouse_position();
6422 if !position_map.text_hitbox.is_hovered(window) {
6423 return;
6424 }
6425
6426 self.update_hovered_link(
6427 position_map.point_for_position(mouse_position),
6428 &position_map.snapshot,
6429 modifiers,
6430 window,
6431 cx,
6432 )
6433 }
6434
6435 fn update_selection_mode(
6436 &mut self,
6437 modifiers: &Modifiers,
6438 position_map: &PositionMap,
6439 window: &mut Window,
6440 cx: &mut Context<Self>,
6441 ) {
6442 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6443 return;
6444 }
6445
6446 let mouse_position = window.mouse_position();
6447 let point_for_position = position_map.point_for_position(mouse_position);
6448 let position = point_for_position.previous_valid;
6449
6450 self.select(
6451 SelectPhase::BeginColumnar {
6452 position,
6453 reset: false,
6454 goal_column: point_for_position.exact_unclipped.column(),
6455 },
6456 window,
6457 cx,
6458 );
6459 }
6460
6461 fn update_edit_prediction_preview(
6462 &mut self,
6463 modifiers: &Modifiers,
6464 window: &mut Window,
6465 cx: &mut Context<Self>,
6466 ) {
6467 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6468 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6469 return;
6470 };
6471
6472 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6473 if matches!(
6474 self.edit_prediction_preview,
6475 EditPredictionPreview::Inactive { .. }
6476 ) {
6477 self.edit_prediction_preview = EditPredictionPreview::Active {
6478 previous_scroll_position: None,
6479 since: Instant::now(),
6480 };
6481
6482 self.update_visible_inline_completion(window, cx);
6483 cx.notify();
6484 }
6485 } else if let EditPredictionPreview::Active {
6486 previous_scroll_position,
6487 since,
6488 } = self.edit_prediction_preview
6489 {
6490 if let (Some(previous_scroll_position), Some(position_map)) =
6491 (previous_scroll_position, self.last_position_map.as_ref())
6492 {
6493 self.set_scroll_position(
6494 previous_scroll_position
6495 .scroll_position(&position_map.snapshot.display_snapshot),
6496 window,
6497 cx,
6498 );
6499 }
6500
6501 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6502 released_too_fast: since.elapsed() < Duration::from_millis(200),
6503 };
6504 self.clear_row_highlights::<EditPredictionPreview>();
6505 self.update_visible_inline_completion(window, cx);
6506 cx.notify();
6507 }
6508 }
6509
6510 fn update_visible_inline_completion(
6511 &mut self,
6512 _window: &mut Window,
6513 cx: &mut Context<Self>,
6514 ) -> Option<()> {
6515 let selection = self.selections.newest_anchor();
6516 let cursor = selection.head();
6517 let multibuffer = self.buffer.read(cx).snapshot(cx);
6518 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6519 let excerpt_id = cursor.excerpt_id;
6520
6521 let show_in_menu = self.show_edit_predictions_in_menu();
6522 let completions_menu_has_precedence = !show_in_menu
6523 && (self.context_menu.borrow().is_some()
6524 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6525
6526 if completions_menu_has_precedence
6527 || !offset_selection.is_empty()
6528 || self
6529 .active_inline_completion
6530 .as_ref()
6531 .map_or(false, |completion| {
6532 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6533 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6534 !invalidation_range.contains(&offset_selection.head())
6535 })
6536 {
6537 self.discard_inline_completion(false, cx);
6538 return None;
6539 }
6540
6541 self.take_active_inline_completion(cx);
6542 let Some(provider) = self.edit_prediction_provider() else {
6543 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6544 return None;
6545 };
6546
6547 let (buffer, cursor_buffer_position) =
6548 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6549
6550 self.edit_prediction_settings =
6551 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6552
6553 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6554
6555 if self.edit_prediction_indent_conflict {
6556 let cursor_point = cursor.to_point(&multibuffer);
6557
6558 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6559
6560 if let Some((_, indent)) = indents.iter().next() {
6561 if indent.len == cursor_point.column {
6562 self.edit_prediction_indent_conflict = false;
6563 }
6564 }
6565 }
6566
6567 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6568 let edits = inline_completion
6569 .edits
6570 .into_iter()
6571 .flat_map(|(range, new_text)| {
6572 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6573 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6574 Some((start..end, new_text))
6575 })
6576 .collect::<Vec<_>>();
6577 if edits.is_empty() {
6578 return None;
6579 }
6580
6581 let first_edit_start = edits.first().unwrap().0.start;
6582 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6583 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6584
6585 let last_edit_end = edits.last().unwrap().0.end;
6586 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6587 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6588
6589 let cursor_row = cursor.to_point(&multibuffer).row;
6590
6591 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6592
6593 let mut inlay_ids = Vec::new();
6594 let invalidation_row_range;
6595 let move_invalidation_row_range = if cursor_row < edit_start_row {
6596 Some(cursor_row..edit_end_row)
6597 } else if cursor_row > edit_end_row {
6598 Some(edit_start_row..cursor_row)
6599 } else {
6600 None
6601 };
6602 let is_move =
6603 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6604 let completion = if is_move {
6605 invalidation_row_range =
6606 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6607 let target = first_edit_start;
6608 InlineCompletion::Move { target, snapshot }
6609 } else {
6610 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6611 && !self.inline_completions_hidden_for_vim_mode;
6612
6613 if show_completions_in_buffer {
6614 if edits
6615 .iter()
6616 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6617 {
6618 let mut inlays = Vec::new();
6619 for (range, new_text) in &edits {
6620 let inlay = Inlay::inline_completion(
6621 post_inc(&mut self.next_inlay_id),
6622 range.start,
6623 new_text.as_str(),
6624 );
6625 inlay_ids.push(inlay.id);
6626 inlays.push(inlay);
6627 }
6628
6629 self.splice_inlays(&[], inlays, cx);
6630 } else {
6631 let background_color = cx.theme().status().deleted_background;
6632 self.highlight_text::<InlineCompletionHighlight>(
6633 edits.iter().map(|(range, _)| range.clone()).collect(),
6634 HighlightStyle {
6635 background_color: Some(background_color),
6636 ..Default::default()
6637 },
6638 cx,
6639 );
6640 }
6641 }
6642
6643 invalidation_row_range = edit_start_row..edit_end_row;
6644
6645 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6646 if provider.show_tab_accept_marker() {
6647 EditDisplayMode::TabAccept
6648 } else {
6649 EditDisplayMode::Inline
6650 }
6651 } else {
6652 EditDisplayMode::DiffPopover
6653 };
6654
6655 InlineCompletion::Edit {
6656 edits,
6657 edit_preview: inline_completion.edit_preview,
6658 display_mode,
6659 snapshot,
6660 }
6661 };
6662
6663 let invalidation_range = multibuffer
6664 .anchor_before(Point::new(invalidation_row_range.start, 0))
6665 ..multibuffer.anchor_after(Point::new(
6666 invalidation_row_range.end,
6667 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6668 ));
6669
6670 self.stale_inline_completion_in_menu = None;
6671 self.active_inline_completion = Some(InlineCompletionState {
6672 inlay_ids,
6673 completion,
6674 completion_id: inline_completion.id,
6675 invalidation_range,
6676 });
6677
6678 cx.notify();
6679
6680 Some(())
6681 }
6682
6683 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6684 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6685 }
6686
6687 fn render_code_actions_indicator(
6688 &self,
6689 _style: &EditorStyle,
6690 row: DisplayRow,
6691 is_active: bool,
6692 breakpoint: Option<&(Anchor, Breakpoint)>,
6693 cx: &mut Context<Self>,
6694 ) -> Option<IconButton> {
6695 let color = Color::Muted;
6696 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6697 let show_tooltip = !self.context_menu_visible();
6698
6699 if self.available_code_actions.is_some() {
6700 Some(
6701 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6702 .shape(ui::IconButtonShape::Square)
6703 .icon_size(IconSize::XSmall)
6704 .icon_color(color)
6705 .toggle_state(is_active)
6706 .when(show_tooltip, |this| {
6707 this.tooltip({
6708 let focus_handle = self.focus_handle.clone();
6709 move |window, cx| {
6710 Tooltip::for_action_in(
6711 "Toggle Code Actions",
6712 &ToggleCodeActions {
6713 deployed_from_indicator: None,
6714 quick_launch: false,
6715 },
6716 &focus_handle,
6717 window,
6718 cx,
6719 )
6720 }
6721 })
6722 })
6723 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6724 let quick_launch = e.down.button == MouseButton::Left;
6725 window.focus(&editor.focus_handle(cx));
6726 editor.toggle_code_actions(
6727 &ToggleCodeActions {
6728 deployed_from_indicator: Some(row),
6729 quick_launch,
6730 },
6731 window,
6732 cx,
6733 );
6734 }))
6735 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6736 editor.set_breakpoint_context_menu(
6737 row,
6738 position,
6739 event.down.position,
6740 window,
6741 cx,
6742 );
6743 })),
6744 )
6745 } else {
6746 None
6747 }
6748 }
6749
6750 fn clear_tasks(&mut self) {
6751 self.tasks.clear()
6752 }
6753
6754 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6755 if self.tasks.insert(key, value).is_some() {
6756 // This case should hopefully be rare, but just in case...
6757 log::error!(
6758 "multiple different run targets found on a single line, only the last target will be rendered"
6759 )
6760 }
6761 }
6762
6763 /// Get all display points of breakpoints that will be rendered within editor
6764 ///
6765 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6766 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6767 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6768 fn active_breakpoints(
6769 &self,
6770 range: Range<DisplayRow>,
6771 window: &mut Window,
6772 cx: &mut Context<Self>,
6773 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6774 let mut breakpoint_display_points = HashMap::default();
6775
6776 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6777 return breakpoint_display_points;
6778 };
6779
6780 let snapshot = self.snapshot(window, cx);
6781
6782 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6783 let Some(project) = self.project.as_ref() else {
6784 return breakpoint_display_points;
6785 };
6786
6787 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6788 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6789
6790 for (buffer_snapshot, range, excerpt_id) in
6791 multi_buffer_snapshot.range_to_buffer_ranges(range)
6792 {
6793 let Some(buffer) = project.read_with(cx, |this, cx| {
6794 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6795 }) else {
6796 continue;
6797 };
6798 let breakpoints = breakpoint_store.read(cx).breakpoints(
6799 &buffer,
6800 Some(
6801 buffer_snapshot.anchor_before(range.start)
6802 ..buffer_snapshot.anchor_after(range.end),
6803 ),
6804 buffer_snapshot,
6805 cx,
6806 );
6807 for (anchor, breakpoint) in breakpoints {
6808 let multi_buffer_anchor =
6809 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6810 let position = multi_buffer_anchor
6811 .to_point(&multi_buffer_snapshot)
6812 .to_display_point(&snapshot);
6813
6814 breakpoint_display_points
6815 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6816 }
6817 }
6818
6819 breakpoint_display_points
6820 }
6821
6822 fn breakpoint_context_menu(
6823 &self,
6824 anchor: Anchor,
6825 window: &mut Window,
6826 cx: &mut Context<Self>,
6827 ) -> Entity<ui::ContextMenu> {
6828 let weak_editor = cx.weak_entity();
6829 let focus_handle = self.focus_handle(cx);
6830
6831 let row = self
6832 .buffer
6833 .read(cx)
6834 .snapshot(cx)
6835 .summary_for_anchor::<Point>(&anchor)
6836 .row;
6837
6838 let breakpoint = self
6839 .breakpoint_at_row(row, window, cx)
6840 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6841
6842 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6843 "Edit Log Breakpoint"
6844 } else {
6845 "Set Log Breakpoint"
6846 };
6847
6848 let condition_breakpoint_msg = if breakpoint
6849 .as_ref()
6850 .is_some_and(|bp| bp.1.condition.is_some())
6851 {
6852 "Edit Condition Breakpoint"
6853 } else {
6854 "Set Condition Breakpoint"
6855 };
6856
6857 let hit_condition_breakpoint_msg = if breakpoint
6858 .as_ref()
6859 .is_some_and(|bp| bp.1.hit_condition.is_some())
6860 {
6861 "Edit Hit Condition Breakpoint"
6862 } else {
6863 "Set Hit Condition Breakpoint"
6864 };
6865
6866 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6867 "Unset Breakpoint"
6868 } else {
6869 "Set Breakpoint"
6870 };
6871
6872 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6873 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6874
6875 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6876 BreakpointState::Enabled => Some("Disable"),
6877 BreakpointState::Disabled => Some("Enable"),
6878 });
6879
6880 let (anchor, breakpoint) =
6881 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6882
6883 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6884 menu.on_blur_subscription(Subscription::new(|| {}))
6885 .context(focus_handle)
6886 .when(run_to_cursor, |this| {
6887 let weak_editor = weak_editor.clone();
6888 this.entry("Run to cursor", None, move |window, cx| {
6889 weak_editor
6890 .update(cx, |editor, cx| {
6891 editor.change_selections(None, window, cx, |s| {
6892 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6893 });
6894 })
6895 .ok();
6896
6897 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6898 })
6899 .separator()
6900 })
6901 .when_some(toggle_state_msg, |this, msg| {
6902 this.entry(msg, None, {
6903 let weak_editor = weak_editor.clone();
6904 let breakpoint = breakpoint.clone();
6905 move |_window, cx| {
6906 weak_editor
6907 .update(cx, |this, cx| {
6908 this.edit_breakpoint_at_anchor(
6909 anchor,
6910 breakpoint.as_ref().clone(),
6911 BreakpointEditAction::InvertState,
6912 cx,
6913 );
6914 })
6915 .log_err();
6916 }
6917 })
6918 })
6919 .entry(set_breakpoint_msg, None, {
6920 let weak_editor = weak_editor.clone();
6921 let breakpoint = breakpoint.clone();
6922 move |_window, cx| {
6923 weak_editor
6924 .update(cx, |this, cx| {
6925 this.edit_breakpoint_at_anchor(
6926 anchor,
6927 breakpoint.as_ref().clone(),
6928 BreakpointEditAction::Toggle,
6929 cx,
6930 );
6931 })
6932 .log_err();
6933 }
6934 })
6935 .entry(log_breakpoint_msg, None, {
6936 let breakpoint = breakpoint.clone();
6937 let weak_editor = weak_editor.clone();
6938 move |window, cx| {
6939 weak_editor
6940 .update(cx, |this, cx| {
6941 this.add_edit_breakpoint_block(
6942 anchor,
6943 breakpoint.as_ref(),
6944 BreakpointPromptEditAction::Log,
6945 window,
6946 cx,
6947 );
6948 })
6949 .log_err();
6950 }
6951 })
6952 .entry(condition_breakpoint_msg, None, {
6953 let breakpoint = breakpoint.clone();
6954 let weak_editor = weak_editor.clone();
6955 move |window, cx| {
6956 weak_editor
6957 .update(cx, |this, cx| {
6958 this.add_edit_breakpoint_block(
6959 anchor,
6960 breakpoint.as_ref(),
6961 BreakpointPromptEditAction::Condition,
6962 window,
6963 cx,
6964 );
6965 })
6966 .log_err();
6967 }
6968 })
6969 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6970 weak_editor
6971 .update(cx, |this, cx| {
6972 this.add_edit_breakpoint_block(
6973 anchor,
6974 breakpoint.as_ref(),
6975 BreakpointPromptEditAction::HitCondition,
6976 window,
6977 cx,
6978 );
6979 })
6980 .log_err();
6981 })
6982 })
6983 }
6984
6985 fn render_breakpoint(
6986 &self,
6987 position: Anchor,
6988 row: DisplayRow,
6989 breakpoint: &Breakpoint,
6990 cx: &mut Context<Self>,
6991 ) -> IconButton {
6992 // Is it a breakpoint that shows up when hovering over gutter?
6993 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6994 (false, false),
6995 |PhantomBreakpointIndicator {
6996 is_active,
6997 display_row,
6998 collides_with_existing_breakpoint,
6999 }| {
7000 (
7001 is_active && display_row == row,
7002 collides_with_existing_breakpoint,
7003 )
7004 },
7005 );
7006
7007 let (color, icon) = {
7008 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7009 (false, false) => ui::IconName::DebugBreakpoint,
7010 (true, false) => ui::IconName::DebugLogBreakpoint,
7011 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7012 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7013 };
7014
7015 let color = if is_phantom {
7016 Color::Hint
7017 } else {
7018 Color::Debugger
7019 };
7020
7021 (color, icon)
7022 };
7023
7024 let breakpoint = Arc::from(breakpoint.clone());
7025
7026 let alt_as_text = gpui::Keystroke {
7027 modifiers: Modifiers::secondary_key(),
7028 ..Default::default()
7029 };
7030 let primary_action_text = if breakpoint.is_disabled() {
7031 "enable"
7032 } else if is_phantom && !collides_with_existing {
7033 "set"
7034 } else {
7035 "unset"
7036 };
7037 let mut primary_text = format!("Click to {primary_action_text}");
7038 if collides_with_existing && !breakpoint.is_disabled() {
7039 use std::fmt::Write;
7040 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7041 }
7042 let primary_text = SharedString::from(primary_text);
7043 let focus_handle = self.focus_handle.clone();
7044 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7045 .icon_size(IconSize::XSmall)
7046 .size(ui::ButtonSize::None)
7047 .icon_color(color)
7048 .style(ButtonStyle::Transparent)
7049 .on_click(cx.listener({
7050 let breakpoint = breakpoint.clone();
7051
7052 move |editor, event: &ClickEvent, window, cx| {
7053 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7054 BreakpointEditAction::InvertState
7055 } else {
7056 BreakpointEditAction::Toggle
7057 };
7058
7059 window.focus(&editor.focus_handle(cx));
7060 editor.edit_breakpoint_at_anchor(
7061 position,
7062 breakpoint.as_ref().clone(),
7063 edit_action,
7064 cx,
7065 );
7066 }
7067 }))
7068 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7069 editor.set_breakpoint_context_menu(
7070 row,
7071 Some(position),
7072 event.down.position,
7073 window,
7074 cx,
7075 );
7076 }))
7077 .tooltip(move |window, cx| {
7078 Tooltip::with_meta_in(
7079 primary_text.clone(),
7080 None,
7081 "Right-click for more options",
7082 &focus_handle,
7083 window,
7084 cx,
7085 )
7086 })
7087 }
7088
7089 fn build_tasks_context(
7090 project: &Entity<Project>,
7091 buffer: &Entity<Buffer>,
7092 buffer_row: u32,
7093 tasks: &Arc<RunnableTasks>,
7094 cx: &mut Context<Self>,
7095 ) -> Task<Option<task::TaskContext>> {
7096 let position = Point::new(buffer_row, tasks.column);
7097 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7098 let location = Location {
7099 buffer: buffer.clone(),
7100 range: range_start..range_start,
7101 };
7102 // Fill in the environmental variables from the tree-sitter captures
7103 let mut captured_task_variables = TaskVariables::default();
7104 for (capture_name, value) in tasks.extra_variables.clone() {
7105 captured_task_variables.insert(
7106 task::VariableName::Custom(capture_name.into()),
7107 value.clone(),
7108 );
7109 }
7110 project.update(cx, |project, cx| {
7111 project.task_store().update(cx, |task_store, cx| {
7112 task_store.task_context_for_location(captured_task_variables, location, cx)
7113 })
7114 })
7115 }
7116
7117 pub fn spawn_nearest_task(
7118 &mut self,
7119 action: &SpawnNearestTask,
7120 window: &mut Window,
7121 cx: &mut Context<Self>,
7122 ) {
7123 let Some((workspace, _)) = self.workspace.clone() else {
7124 return;
7125 };
7126 let Some(project) = self.project.clone() else {
7127 return;
7128 };
7129
7130 // Try to find a closest, enclosing node using tree-sitter that has a
7131 // task
7132 let Some((buffer, buffer_row, tasks)) = self
7133 .find_enclosing_node_task(cx)
7134 // Or find the task that's closest in row-distance.
7135 .or_else(|| self.find_closest_task(cx))
7136 else {
7137 return;
7138 };
7139
7140 let reveal_strategy = action.reveal;
7141 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7142 cx.spawn_in(window, async move |_, cx| {
7143 let context = task_context.await?;
7144 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7145
7146 let resolved = &mut resolved_task.resolved;
7147 resolved.reveal = reveal_strategy;
7148
7149 workspace
7150 .update_in(cx, |workspace, window, cx| {
7151 workspace.schedule_resolved_task(
7152 task_source_kind,
7153 resolved_task,
7154 false,
7155 window,
7156 cx,
7157 );
7158 })
7159 .ok()
7160 })
7161 .detach();
7162 }
7163
7164 fn find_closest_task(
7165 &mut self,
7166 cx: &mut Context<Self>,
7167 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7168 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7169
7170 let ((buffer_id, row), tasks) = self
7171 .tasks
7172 .iter()
7173 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7174
7175 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7176 let tasks = Arc::new(tasks.to_owned());
7177 Some((buffer, *row, tasks))
7178 }
7179
7180 fn find_enclosing_node_task(
7181 &mut self,
7182 cx: &mut Context<Self>,
7183 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7184 let snapshot = self.buffer.read(cx).snapshot(cx);
7185 let offset = self.selections.newest::<usize>(cx).head();
7186 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7187 let buffer_id = excerpt.buffer().remote_id();
7188
7189 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7190 let mut cursor = layer.node().walk();
7191
7192 while cursor.goto_first_child_for_byte(offset).is_some() {
7193 if cursor.node().end_byte() == offset {
7194 cursor.goto_next_sibling();
7195 }
7196 }
7197
7198 // Ascend to the smallest ancestor that contains the range and has a task.
7199 loop {
7200 let node = cursor.node();
7201 let node_range = node.byte_range();
7202 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7203
7204 // Check if this node contains our offset
7205 if node_range.start <= offset && node_range.end >= offset {
7206 // If it contains offset, check for task
7207 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7208 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7209 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7210 }
7211 }
7212
7213 if !cursor.goto_parent() {
7214 break;
7215 }
7216 }
7217 None
7218 }
7219
7220 fn render_run_indicator(
7221 &self,
7222 _style: &EditorStyle,
7223 is_active: bool,
7224 row: DisplayRow,
7225 breakpoint: Option<(Anchor, Breakpoint)>,
7226 cx: &mut Context<Self>,
7227 ) -> IconButton {
7228 let color = Color::Muted;
7229 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7230
7231 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7232 .shape(ui::IconButtonShape::Square)
7233 .icon_size(IconSize::XSmall)
7234 .icon_color(color)
7235 .toggle_state(is_active)
7236 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7237 let quick_launch = e.down.button == MouseButton::Left;
7238 window.focus(&editor.focus_handle(cx));
7239 editor.toggle_code_actions(
7240 &ToggleCodeActions {
7241 deployed_from_indicator: Some(row),
7242 quick_launch,
7243 },
7244 window,
7245 cx,
7246 );
7247 }))
7248 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7249 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7250 }))
7251 }
7252
7253 pub fn context_menu_visible(&self) -> bool {
7254 !self.edit_prediction_preview_is_active()
7255 && self
7256 .context_menu
7257 .borrow()
7258 .as_ref()
7259 .map_or(false, |menu| menu.visible())
7260 }
7261
7262 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7263 self.context_menu
7264 .borrow()
7265 .as_ref()
7266 .map(|menu| menu.origin())
7267 }
7268
7269 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7270 self.context_menu_options = Some(options);
7271 }
7272
7273 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7274 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7275
7276 fn render_edit_prediction_popover(
7277 &mut self,
7278 text_bounds: &Bounds<Pixels>,
7279 content_origin: gpui::Point<Pixels>,
7280 editor_snapshot: &EditorSnapshot,
7281 visible_row_range: Range<DisplayRow>,
7282 scroll_top: f32,
7283 scroll_bottom: f32,
7284 line_layouts: &[LineWithInvisibles],
7285 line_height: Pixels,
7286 scroll_pixel_position: gpui::Point<Pixels>,
7287 newest_selection_head: Option<DisplayPoint>,
7288 editor_width: Pixels,
7289 style: &EditorStyle,
7290 window: &mut Window,
7291 cx: &mut App,
7292 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7293 let active_inline_completion = self.active_inline_completion.as_ref()?;
7294
7295 if self.edit_prediction_visible_in_cursor_popover(true) {
7296 return None;
7297 }
7298
7299 match &active_inline_completion.completion {
7300 InlineCompletion::Move { target, .. } => {
7301 let target_display_point = target.to_display_point(editor_snapshot);
7302
7303 if self.edit_prediction_requires_modifier() {
7304 if !self.edit_prediction_preview_is_active() {
7305 return None;
7306 }
7307
7308 self.render_edit_prediction_modifier_jump_popover(
7309 text_bounds,
7310 content_origin,
7311 visible_row_range,
7312 line_layouts,
7313 line_height,
7314 scroll_pixel_position,
7315 newest_selection_head,
7316 target_display_point,
7317 window,
7318 cx,
7319 )
7320 } else {
7321 self.render_edit_prediction_eager_jump_popover(
7322 text_bounds,
7323 content_origin,
7324 editor_snapshot,
7325 visible_row_range,
7326 scroll_top,
7327 scroll_bottom,
7328 line_height,
7329 scroll_pixel_position,
7330 target_display_point,
7331 editor_width,
7332 window,
7333 cx,
7334 )
7335 }
7336 }
7337 InlineCompletion::Edit {
7338 display_mode: EditDisplayMode::Inline,
7339 ..
7340 } => None,
7341 InlineCompletion::Edit {
7342 display_mode: EditDisplayMode::TabAccept,
7343 edits,
7344 ..
7345 } => {
7346 let range = &edits.first()?.0;
7347 let target_display_point = range.end.to_display_point(editor_snapshot);
7348
7349 self.render_edit_prediction_end_of_line_popover(
7350 "Accept",
7351 editor_snapshot,
7352 visible_row_range,
7353 target_display_point,
7354 line_height,
7355 scroll_pixel_position,
7356 content_origin,
7357 editor_width,
7358 window,
7359 cx,
7360 )
7361 }
7362 InlineCompletion::Edit {
7363 edits,
7364 edit_preview,
7365 display_mode: EditDisplayMode::DiffPopover,
7366 snapshot,
7367 } => self.render_edit_prediction_diff_popover(
7368 text_bounds,
7369 content_origin,
7370 editor_snapshot,
7371 visible_row_range,
7372 line_layouts,
7373 line_height,
7374 scroll_pixel_position,
7375 newest_selection_head,
7376 editor_width,
7377 style,
7378 edits,
7379 edit_preview,
7380 snapshot,
7381 window,
7382 cx,
7383 ),
7384 }
7385 }
7386
7387 fn render_edit_prediction_modifier_jump_popover(
7388 &mut self,
7389 text_bounds: &Bounds<Pixels>,
7390 content_origin: gpui::Point<Pixels>,
7391 visible_row_range: Range<DisplayRow>,
7392 line_layouts: &[LineWithInvisibles],
7393 line_height: Pixels,
7394 scroll_pixel_position: gpui::Point<Pixels>,
7395 newest_selection_head: Option<DisplayPoint>,
7396 target_display_point: DisplayPoint,
7397 window: &mut Window,
7398 cx: &mut App,
7399 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7400 let scrolled_content_origin =
7401 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7402
7403 const SCROLL_PADDING_Y: Pixels = px(12.);
7404
7405 if target_display_point.row() < visible_row_range.start {
7406 return self.render_edit_prediction_scroll_popover(
7407 |_| SCROLL_PADDING_Y,
7408 IconName::ArrowUp,
7409 visible_row_range,
7410 line_layouts,
7411 newest_selection_head,
7412 scrolled_content_origin,
7413 window,
7414 cx,
7415 );
7416 } else if target_display_point.row() >= visible_row_range.end {
7417 return self.render_edit_prediction_scroll_popover(
7418 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7419 IconName::ArrowDown,
7420 visible_row_range,
7421 line_layouts,
7422 newest_selection_head,
7423 scrolled_content_origin,
7424 window,
7425 cx,
7426 );
7427 }
7428
7429 const POLE_WIDTH: Pixels = px(2.);
7430
7431 let line_layout =
7432 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7433 let target_column = target_display_point.column() as usize;
7434
7435 let target_x = line_layout.x_for_index(target_column);
7436 let target_y =
7437 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7438
7439 let flag_on_right = target_x < text_bounds.size.width / 2.;
7440
7441 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7442 border_color.l += 0.001;
7443
7444 let mut element = v_flex()
7445 .items_end()
7446 .when(flag_on_right, |el| el.items_start())
7447 .child(if flag_on_right {
7448 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7449 .rounded_bl(px(0.))
7450 .rounded_tl(px(0.))
7451 .border_l_2()
7452 .border_color(border_color)
7453 } else {
7454 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7455 .rounded_br(px(0.))
7456 .rounded_tr(px(0.))
7457 .border_r_2()
7458 .border_color(border_color)
7459 })
7460 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7461 .into_any();
7462
7463 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7464
7465 let mut origin = scrolled_content_origin + point(target_x, target_y)
7466 - point(
7467 if flag_on_right {
7468 POLE_WIDTH
7469 } else {
7470 size.width - POLE_WIDTH
7471 },
7472 size.height - line_height,
7473 );
7474
7475 origin.x = origin.x.max(content_origin.x);
7476
7477 element.prepaint_at(origin, window, cx);
7478
7479 Some((element, origin))
7480 }
7481
7482 fn render_edit_prediction_scroll_popover(
7483 &mut self,
7484 to_y: impl Fn(Size<Pixels>) -> Pixels,
7485 scroll_icon: IconName,
7486 visible_row_range: Range<DisplayRow>,
7487 line_layouts: &[LineWithInvisibles],
7488 newest_selection_head: Option<DisplayPoint>,
7489 scrolled_content_origin: gpui::Point<Pixels>,
7490 window: &mut Window,
7491 cx: &mut App,
7492 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7493 let mut element = self
7494 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7495 .into_any();
7496
7497 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7498
7499 let cursor = newest_selection_head?;
7500 let cursor_row_layout =
7501 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7502 let cursor_column = cursor.column() as usize;
7503
7504 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7505
7506 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7507
7508 element.prepaint_at(origin, window, cx);
7509 Some((element, origin))
7510 }
7511
7512 fn render_edit_prediction_eager_jump_popover(
7513 &mut self,
7514 text_bounds: &Bounds<Pixels>,
7515 content_origin: gpui::Point<Pixels>,
7516 editor_snapshot: &EditorSnapshot,
7517 visible_row_range: Range<DisplayRow>,
7518 scroll_top: f32,
7519 scroll_bottom: f32,
7520 line_height: Pixels,
7521 scroll_pixel_position: gpui::Point<Pixels>,
7522 target_display_point: DisplayPoint,
7523 editor_width: Pixels,
7524 window: &mut Window,
7525 cx: &mut App,
7526 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7527 if target_display_point.row().as_f32() < scroll_top {
7528 let mut element = self
7529 .render_edit_prediction_line_popover(
7530 "Jump to Edit",
7531 Some(IconName::ArrowUp),
7532 window,
7533 cx,
7534 )?
7535 .into_any();
7536
7537 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7538 let offset = point(
7539 (text_bounds.size.width - size.width) / 2.,
7540 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7541 );
7542
7543 let origin = text_bounds.origin + offset;
7544 element.prepaint_at(origin, window, cx);
7545 Some((element, origin))
7546 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7547 let mut element = self
7548 .render_edit_prediction_line_popover(
7549 "Jump to Edit",
7550 Some(IconName::ArrowDown),
7551 window,
7552 cx,
7553 )?
7554 .into_any();
7555
7556 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7557 let offset = point(
7558 (text_bounds.size.width - size.width) / 2.,
7559 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7560 );
7561
7562 let origin = text_bounds.origin + offset;
7563 element.prepaint_at(origin, window, cx);
7564 Some((element, origin))
7565 } else {
7566 self.render_edit_prediction_end_of_line_popover(
7567 "Jump to Edit",
7568 editor_snapshot,
7569 visible_row_range,
7570 target_display_point,
7571 line_height,
7572 scroll_pixel_position,
7573 content_origin,
7574 editor_width,
7575 window,
7576 cx,
7577 )
7578 }
7579 }
7580
7581 fn render_edit_prediction_end_of_line_popover(
7582 self: &mut Editor,
7583 label: &'static str,
7584 editor_snapshot: &EditorSnapshot,
7585 visible_row_range: Range<DisplayRow>,
7586 target_display_point: DisplayPoint,
7587 line_height: Pixels,
7588 scroll_pixel_position: gpui::Point<Pixels>,
7589 content_origin: gpui::Point<Pixels>,
7590 editor_width: Pixels,
7591 window: &mut Window,
7592 cx: &mut App,
7593 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7594 let target_line_end = DisplayPoint::new(
7595 target_display_point.row(),
7596 editor_snapshot.line_len(target_display_point.row()),
7597 );
7598
7599 let mut element = self
7600 .render_edit_prediction_line_popover(label, None, window, cx)?
7601 .into_any();
7602
7603 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7604
7605 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7606
7607 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7608 let mut origin = start_point
7609 + line_origin
7610 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7611 origin.x = origin.x.max(content_origin.x);
7612
7613 let max_x = content_origin.x + editor_width - size.width;
7614
7615 if origin.x > max_x {
7616 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7617
7618 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7619 origin.y += offset;
7620 IconName::ArrowUp
7621 } else {
7622 origin.y -= offset;
7623 IconName::ArrowDown
7624 };
7625
7626 element = self
7627 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7628 .into_any();
7629
7630 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7631
7632 origin.x = content_origin.x + editor_width - size.width - px(2.);
7633 }
7634
7635 element.prepaint_at(origin, window, cx);
7636 Some((element, origin))
7637 }
7638
7639 fn render_edit_prediction_diff_popover(
7640 self: &Editor,
7641 text_bounds: &Bounds<Pixels>,
7642 content_origin: gpui::Point<Pixels>,
7643 editor_snapshot: &EditorSnapshot,
7644 visible_row_range: Range<DisplayRow>,
7645 line_layouts: &[LineWithInvisibles],
7646 line_height: Pixels,
7647 scroll_pixel_position: gpui::Point<Pixels>,
7648 newest_selection_head: Option<DisplayPoint>,
7649 editor_width: Pixels,
7650 style: &EditorStyle,
7651 edits: &Vec<(Range<Anchor>, String)>,
7652 edit_preview: &Option<language::EditPreview>,
7653 snapshot: &language::BufferSnapshot,
7654 window: &mut Window,
7655 cx: &mut App,
7656 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7657 let edit_start = edits
7658 .first()
7659 .unwrap()
7660 .0
7661 .start
7662 .to_display_point(editor_snapshot);
7663 let edit_end = edits
7664 .last()
7665 .unwrap()
7666 .0
7667 .end
7668 .to_display_point(editor_snapshot);
7669
7670 let is_visible = visible_row_range.contains(&edit_start.row())
7671 || visible_row_range.contains(&edit_end.row());
7672 if !is_visible {
7673 return None;
7674 }
7675
7676 let highlighted_edits =
7677 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7678
7679 let styled_text = highlighted_edits.to_styled_text(&style.text);
7680 let line_count = highlighted_edits.text.lines().count();
7681
7682 const BORDER_WIDTH: Pixels = px(1.);
7683
7684 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7685 let has_keybind = keybind.is_some();
7686
7687 let mut element = h_flex()
7688 .items_start()
7689 .child(
7690 h_flex()
7691 .bg(cx.theme().colors().editor_background)
7692 .border(BORDER_WIDTH)
7693 .shadow_sm()
7694 .border_color(cx.theme().colors().border)
7695 .rounded_l_lg()
7696 .when(line_count > 1, |el| el.rounded_br_lg())
7697 .pr_1()
7698 .child(styled_text),
7699 )
7700 .child(
7701 h_flex()
7702 .h(line_height + BORDER_WIDTH * 2.)
7703 .px_1p5()
7704 .gap_1()
7705 // Workaround: For some reason, there's a gap if we don't do this
7706 .ml(-BORDER_WIDTH)
7707 .shadow(smallvec![gpui::BoxShadow {
7708 color: gpui::black().opacity(0.05),
7709 offset: point(px(1.), px(1.)),
7710 blur_radius: px(2.),
7711 spread_radius: px(0.),
7712 }])
7713 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7714 .border(BORDER_WIDTH)
7715 .border_color(cx.theme().colors().border)
7716 .rounded_r_lg()
7717 .id("edit_prediction_diff_popover_keybind")
7718 .when(!has_keybind, |el| {
7719 let status_colors = cx.theme().status();
7720
7721 el.bg(status_colors.error_background)
7722 .border_color(status_colors.error.opacity(0.6))
7723 .child(Icon::new(IconName::Info).color(Color::Error))
7724 .cursor_default()
7725 .hoverable_tooltip(move |_window, cx| {
7726 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7727 })
7728 })
7729 .children(keybind),
7730 )
7731 .into_any();
7732
7733 let longest_row =
7734 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7735 let longest_line_width = if visible_row_range.contains(&longest_row) {
7736 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7737 } else {
7738 layout_line(
7739 longest_row,
7740 editor_snapshot,
7741 style,
7742 editor_width,
7743 |_| false,
7744 window,
7745 cx,
7746 )
7747 .width
7748 };
7749
7750 let viewport_bounds =
7751 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7752 right: -EditorElement::SCROLLBAR_WIDTH,
7753 ..Default::default()
7754 });
7755
7756 let x_after_longest =
7757 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7758 - scroll_pixel_position.x;
7759
7760 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7761
7762 // Fully visible if it can be displayed within the window (allow overlapping other
7763 // panes). However, this is only allowed if the popover starts within text_bounds.
7764 let can_position_to_the_right = x_after_longest < text_bounds.right()
7765 && x_after_longest + element_bounds.width < viewport_bounds.right();
7766
7767 let mut origin = if can_position_to_the_right {
7768 point(
7769 x_after_longest,
7770 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7771 - scroll_pixel_position.y,
7772 )
7773 } else {
7774 let cursor_row = newest_selection_head.map(|head| head.row());
7775 let above_edit = edit_start
7776 .row()
7777 .0
7778 .checked_sub(line_count as u32)
7779 .map(DisplayRow);
7780 let below_edit = Some(edit_end.row() + 1);
7781 let above_cursor =
7782 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7783 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7784
7785 // Place the edit popover adjacent to the edit if there is a location
7786 // available that is onscreen and does not obscure the cursor. Otherwise,
7787 // place it adjacent to the cursor.
7788 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7789 .into_iter()
7790 .flatten()
7791 .find(|&start_row| {
7792 let end_row = start_row + line_count as u32;
7793 visible_row_range.contains(&start_row)
7794 && visible_row_range.contains(&end_row)
7795 && cursor_row.map_or(true, |cursor_row| {
7796 !((start_row..end_row).contains(&cursor_row))
7797 })
7798 })?;
7799
7800 content_origin
7801 + point(
7802 -scroll_pixel_position.x,
7803 row_target.as_f32() * line_height - scroll_pixel_position.y,
7804 )
7805 };
7806
7807 origin.x -= BORDER_WIDTH;
7808
7809 window.defer_draw(element, origin, 1);
7810
7811 // Do not return an element, since it will already be drawn due to defer_draw.
7812 None
7813 }
7814
7815 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7816 px(30.)
7817 }
7818
7819 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7820 if self.read_only(cx) {
7821 cx.theme().players().read_only()
7822 } else {
7823 self.style.as_ref().unwrap().local_player
7824 }
7825 }
7826
7827 fn render_edit_prediction_accept_keybind(
7828 &self,
7829 window: &mut Window,
7830 cx: &App,
7831 ) -> Option<AnyElement> {
7832 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7833 let accept_keystroke = accept_binding.keystroke()?;
7834
7835 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7836
7837 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7838 Color::Accent
7839 } else {
7840 Color::Muted
7841 };
7842
7843 h_flex()
7844 .px_0p5()
7845 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7846 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7847 .text_size(TextSize::XSmall.rems(cx))
7848 .child(h_flex().children(ui::render_modifiers(
7849 &accept_keystroke.modifiers,
7850 PlatformStyle::platform(),
7851 Some(modifiers_color),
7852 Some(IconSize::XSmall.rems().into()),
7853 true,
7854 )))
7855 .when(is_platform_style_mac, |parent| {
7856 parent.child(accept_keystroke.key.clone())
7857 })
7858 .when(!is_platform_style_mac, |parent| {
7859 parent.child(
7860 Key::new(
7861 util::capitalize(&accept_keystroke.key),
7862 Some(Color::Default),
7863 )
7864 .size(Some(IconSize::XSmall.rems().into())),
7865 )
7866 })
7867 .into_any()
7868 .into()
7869 }
7870
7871 fn render_edit_prediction_line_popover(
7872 &self,
7873 label: impl Into<SharedString>,
7874 icon: Option<IconName>,
7875 window: &mut Window,
7876 cx: &App,
7877 ) -> Option<Stateful<Div>> {
7878 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7879
7880 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7881 let has_keybind = keybind.is_some();
7882
7883 let result = h_flex()
7884 .id("ep-line-popover")
7885 .py_0p5()
7886 .pl_1()
7887 .pr(padding_right)
7888 .gap_1()
7889 .rounded_md()
7890 .border_1()
7891 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7892 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7893 .shadow_sm()
7894 .when(!has_keybind, |el| {
7895 let status_colors = cx.theme().status();
7896
7897 el.bg(status_colors.error_background)
7898 .border_color(status_colors.error.opacity(0.6))
7899 .pl_2()
7900 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7901 .cursor_default()
7902 .hoverable_tooltip(move |_window, cx| {
7903 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7904 })
7905 })
7906 .children(keybind)
7907 .child(
7908 Label::new(label)
7909 .size(LabelSize::Small)
7910 .when(!has_keybind, |el| {
7911 el.color(cx.theme().status().error.into()).strikethrough()
7912 }),
7913 )
7914 .when(!has_keybind, |el| {
7915 el.child(
7916 h_flex().ml_1().child(
7917 Icon::new(IconName::Info)
7918 .size(IconSize::Small)
7919 .color(cx.theme().status().error.into()),
7920 ),
7921 )
7922 })
7923 .when_some(icon, |element, icon| {
7924 element.child(
7925 div()
7926 .mt(px(1.5))
7927 .child(Icon::new(icon).size(IconSize::Small)),
7928 )
7929 });
7930
7931 Some(result)
7932 }
7933
7934 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7935 let accent_color = cx.theme().colors().text_accent;
7936 let editor_bg_color = cx.theme().colors().editor_background;
7937 editor_bg_color.blend(accent_color.opacity(0.1))
7938 }
7939
7940 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7941 let accent_color = cx.theme().colors().text_accent;
7942 let editor_bg_color = cx.theme().colors().editor_background;
7943 editor_bg_color.blend(accent_color.opacity(0.6))
7944 }
7945
7946 fn render_edit_prediction_cursor_popover(
7947 &self,
7948 min_width: Pixels,
7949 max_width: Pixels,
7950 cursor_point: Point,
7951 style: &EditorStyle,
7952 accept_keystroke: Option<&gpui::Keystroke>,
7953 _window: &Window,
7954 cx: &mut Context<Editor>,
7955 ) -> Option<AnyElement> {
7956 let provider = self.edit_prediction_provider.as_ref()?;
7957
7958 if provider.provider.needs_terms_acceptance(cx) {
7959 return Some(
7960 h_flex()
7961 .min_w(min_width)
7962 .flex_1()
7963 .px_2()
7964 .py_1()
7965 .gap_3()
7966 .elevation_2(cx)
7967 .hover(|style| style.bg(cx.theme().colors().element_hover))
7968 .id("accept-terms")
7969 .cursor_pointer()
7970 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7971 .on_click(cx.listener(|this, _event, window, cx| {
7972 cx.stop_propagation();
7973 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7974 window.dispatch_action(
7975 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7976 cx,
7977 );
7978 }))
7979 .child(
7980 h_flex()
7981 .flex_1()
7982 .gap_2()
7983 .child(Icon::new(IconName::ZedPredict))
7984 .child(Label::new("Accept Terms of Service"))
7985 .child(div().w_full())
7986 .child(
7987 Icon::new(IconName::ArrowUpRight)
7988 .color(Color::Muted)
7989 .size(IconSize::Small),
7990 )
7991 .into_any_element(),
7992 )
7993 .into_any(),
7994 );
7995 }
7996
7997 let is_refreshing = provider.provider.is_refreshing(cx);
7998
7999 fn pending_completion_container() -> Div {
8000 h_flex()
8001 .h_full()
8002 .flex_1()
8003 .gap_2()
8004 .child(Icon::new(IconName::ZedPredict))
8005 }
8006
8007 let completion = match &self.active_inline_completion {
8008 Some(prediction) => {
8009 if !self.has_visible_completions_menu() {
8010 const RADIUS: Pixels = px(6.);
8011 const BORDER_WIDTH: Pixels = px(1.);
8012
8013 return Some(
8014 h_flex()
8015 .elevation_2(cx)
8016 .border(BORDER_WIDTH)
8017 .border_color(cx.theme().colors().border)
8018 .when(accept_keystroke.is_none(), |el| {
8019 el.border_color(cx.theme().status().error)
8020 })
8021 .rounded(RADIUS)
8022 .rounded_tl(px(0.))
8023 .overflow_hidden()
8024 .child(div().px_1p5().child(match &prediction.completion {
8025 InlineCompletion::Move { target, snapshot } => {
8026 use text::ToPoint as _;
8027 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8028 {
8029 Icon::new(IconName::ZedPredictDown)
8030 } else {
8031 Icon::new(IconName::ZedPredictUp)
8032 }
8033 }
8034 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8035 }))
8036 .child(
8037 h_flex()
8038 .gap_1()
8039 .py_1()
8040 .px_2()
8041 .rounded_r(RADIUS - BORDER_WIDTH)
8042 .border_l_1()
8043 .border_color(cx.theme().colors().border)
8044 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8045 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8046 el.child(
8047 Label::new("Hold")
8048 .size(LabelSize::Small)
8049 .when(accept_keystroke.is_none(), |el| {
8050 el.strikethrough()
8051 })
8052 .line_height_style(LineHeightStyle::UiLabel),
8053 )
8054 })
8055 .id("edit_prediction_cursor_popover_keybind")
8056 .when(accept_keystroke.is_none(), |el| {
8057 let status_colors = cx.theme().status();
8058
8059 el.bg(status_colors.error_background)
8060 .border_color(status_colors.error.opacity(0.6))
8061 .child(Icon::new(IconName::Info).color(Color::Error))
8062 .cursor_default()
8063 .hoverable_tooltip(move |_window, cx| {
8064 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8065 .into()
8066 })
8067 })
8068 .when_some(
8069 accept_keystroke.as_ref(),
8070 |el, accept_keystroke| {
8071 el.child(h_flex().children(ui::render_modifiers(
8072 &accept_keystroke.modifiers,
8073 PlatformStyle::platform(),
8074 Some(Color::Default),
8075 Some(IconSize::XSmall.rems().into()),
8076 false,
8077 )))
8078 },
8079 ),
8080 )
8081 .into_any(),
8082 );
8083 }
8084
8085 self.render_edit_prediction_cursor_popover_preview(
8086 prediction,
8087 cursor_point,
8088 style,
8089 cx,
8090 )?
8091 }
8092
8093 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8094 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8095 stale_completion,
8096 cursor_point,
8097 style,
8098 cx,
8099 )?,
8100
8101 None => {
8102 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8103 }
8104 },
8105
8106 None => pending_completion_container().child(Label::new("No Prediction")),
8107 };
8108
8109 let completion = if is_refreshing {
8110 completion
8111 .with_animation(
8112 "loading-completion",
8113 Animation::new(Duration::from_secs(2))
8114 .repeat()
8115 .with_easing(pulsating_between(0.4, 0.8)),
8116 |label, delta| label.opacity(delta),
8117 )
8118 .into_any_element()
8119 } else {
8120 completion.into_any_element()
8121 };
8122
8123 let has_completion = self.active_inline_completion.is_some();
8124
8125 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8126 Some(
8127 h_flex()
8128 .min_w(min_width)
8129 .max_w(max_width)
8130 .flex_1()
8131 .elevation_2(cx)
8132 .border_color(cx.theme().colors().border)
8133 .child(
8134 div()
8135 .flex_1()
8136 .py_1()
8137 .px_2()
8138 .overflow_hidden()
8139 .child(completion),
8140 )
8141 .when_some(accept_keystroke, |el, accept_keystroke| {
8142 if !accept_keystroke.modifiers.modified() {
8143 return el;
8144 }
8145
8146 el.child(
8147 h_flex()
8148 .h_full()
8149 .border_l_1()
8150 .rounded_r_lg()
8151 .border_color(cx.theme().colors().border)
8152 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8153 .gap_1()
8154 .py_1()
8155 .px_2()
8156 .child(
8157 h_flex()
8158 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8159 .when(is_platform_style_mac, |parent| parent.gap_1())
8160 .child(h_flex().children(ui::render_modifiers(
8161 &accept_keystroke.modifiers,
8162 PlatformStyle::platform(),
8163 Some(if !has_completion {
8164 Color::Muted
8165 } else {
8166 Color::Default
8167 }),
8168 None,
8169 false,
8170 ))),
8171 )
8172 .child(Label::new("Preview").into_any_element())
8173 .opacity(if has_completion { 1.0 } else { 0.4 }),
8174 )
8175 })
8176 .into_any(),
8177 )
8178 }
8179
8180 fn render_edit_prediction_cursor_popover_preview(
8181 &self,
8182 completion: &InlineCompletionState,
8183 cursor_point: Point,
8184 style: &EditorStyle,
8185 cx: &mut Context<Editor>,
8186 ) -> Option<Div> {
8187 use text::ToPoint as _;
8188
8189 fn render_relative_row_jump(
8190 prefix: impl Into<String>,
8191 current_row: u32,
8192 target_row: u32,
8193 ) -> Div {
8194 let (row_diff, arrow) = if target_row < current_row {
8195 (current_row - target_row, IconName::ArrowUp)
8196 } else {
8197 (target_row - current_row, IconName::ArrowDown)
8198 };
8199
8200 h_flex()
8201 .child(
8202 Label::new(format!("{}{}", prefix.into(), row_diff))
8203 .color(Color::Muted)
8204 .size(LabelSize::Small),
8205 )
8206 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8207 }
8208
8209 match &completion.completion {
8210 InlineCompletion::Move {
8211 target, snapshot, ..
8212 } => Some(
8213 h_flex()
8214 .px_2()
8215 .gap_2()
8216 .flex_1()
8217 .child(
8218 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8219 Icon::new(IconName::ZedPredictDown)
8220 } else {
8221 Icon::new(IconName::ZedPredictUp)
8222 },
8223 )
8224 .child(Label::new("Jump to Edit")),
8225 ),
8226
8227 InlineCompletion::Edit {
8228 edits,
8229 edit_preview,
8230 snapshot,
8231 display_mode: _,
8232 } => {
8233 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8234
8235 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8236 &snapshot,
8237 &edits,
8238 edit_preview.as_ref()?,
8239 true,
8240 cx,
8241 )
8242 .first_line_preview();
8243
8244 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8245 .with_default_highlights(&style.text, highlighted_edits.highlights);
8246
8247 let preview = h_flex()
8248 .gap_1()
8249 .min_w_16()
8250 .child(styled_text)
8251 .when(has_more_lines, |parent| parent.child("…"));
8252
8253 let left = if first_edit_row != cursor_point.row {
8254 render_relative_row_jump("", cursor_point.row, first_edit_row)
8255 .into_any_element()
8256 } else {
8257 Icon::new(IconName::ZedPredict).into_any_element()
8258 };
8259
8260 Some(
8261 h_flex()
8262 .h_full()
8263 .flex_1()
8264 .gap_2()
8265 .pr_1()
8266 .overflow_x_hidden()
8267 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8268 .child(left)
8269 .child(preview),
8270 )
8271 }
8272 }
8273 }
8274
8275 fn render_context_menu(
8276 &self,
8277 style: &EditorStyle,
8278 max_height_in_lines: u32,
8279 window: &mut Window,
8280 cx: &mut Context<Editor>,
8281 ) -> Option<AnyElement> {
8282 let menu = self.context_menu.borrow();
8283 let menu = menu.as_ref()?;
8284 if !menu.visible() {
8285 return None;
8286 };
8287 Some(menu.render(style, max_height_in_lines, window, cx))
8288 }
8289
8290 fn render_context_menu_aside(
8291 &mut self,
8292 max_size: Size<Pixels>,
8293 window: &mut Window,
8294 cx: &mut Context<Editor>,
8295 ) -> Option<AnyElement> {
8296 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8297 if menu.visible() {
8298 menu.render_aside(self, max_size, window, cx)
8299 } else {
8300 None
8301 }
8302 })
8303 }
8304
8305 fn hide_context_menu(
8306 &mut self,
8307 window: &mut Window,
8308 cx: &mut Context<Self>,
8309 ) -> Option<CodeContextMenu> {
8310 cx.notify();
8311 self.completion_tasks.clear();
8312 let context_menu = self.context_menu.borrow_mut().take();
8313 self.stale_inline_completion_in_menu.take();
8314 self.update_visible_inline_completion(window, cx);
8315 context_menu
8316 }
8317
8318 fn show_snippet_choices(
8319 &mut self,
8320 choices: &Vec<String>,
8321 selection: Range<Anchor>,
8322 cx: &mut Context<Self>,
8323 ) {
8324 if selection.start.buffer_id.is_none() {
8325 return;
8326 }
8327 let buffer_id = selection.start.buffer_id.unwrap();
8328 let buffer = self.buffer().read(cx).buffer(buffer_id);
8329 let id = post_inc(&mut self.next_completion_id);
8330 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8331
8332 if let Some(buffer) = buffer {
8333 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8334 CompletionsMenu::new_snippet_choices(
8335 id,
8336 true,
8337 choices,
8338 selection,
8339 buffer,
8340 snippet_sort_order,
8341 ),
8342 ));
8343 }
8344 }
8345
8346 pub fn insert_snippet(
8347 &mut self,
8348 insertion_ranges: &[Range<usize>],
8349 snippet: Snippet,
8350 window: &mut Window,
8351 cx: &mut Context<Self>,
8352 ) -> Result<()> {
8353 struct Tabstop<T> {
8354 is_end_tabstop: bool,
8355 ranges: Vec<Range<T>>,
8356 choices: Option<Vec<String>>,
8357 }
8358
8359 let tabstops = self.buffer.update(cx, |buffer, cx| {
8360 let snippet_text: Arc<str> = snippet.text.clone().into();
8361 let edits = insertion_ranges
8362 .iter()
8363 .cloned()
8364 .map(|range| (range, snippet_text.clone()));
8365 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8366
8367 let snapshot = &*buffer.read(cx);
8368 let snippet = &snippet;
8369 snippet
8370 .tabstops
8371 .iter()
8372 .map(|tabstop| {
8373 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8374 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8375 });
8376 let mut tabstop_ranges = tabstop
8377 .ranges
8378 .iter()
8379 .flat_map(|tabstop_range| {
8380 let mut delta = 0_isize;
8381 insertion_ranges.iter().map(move |insertion_range| {
8382 let insertion_start = insertion_range.start as isize + delta;
8383 delta +=
8384 snippet.text.len() as isize - insertion_range.len() as isize;
8385
8386 let start = ((insertion_start + tabstop_range.start) as usize)
8387 .min(snapshot.len());
8388 let end = ((insertion_start + tabstop_range.end) as usize)
8389 .min(snapshot.len());
8390 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8391 })
8392 })
8393 .collect::<Vec<_>>();
8394 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8395
8396 Tabstop {
8397 is_end_tabstop,
8398 ranges: tabstop_ranges,
8399 choices: tabstop.choices.clone(),
8400 }
8401 })
8402 .collect::<Vec<_>>()
8403 });
8404 if let Some(tabstop) = tabstops.first() {
8405 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8406 s.select_ranges(tabstop.ranges.iter().cloned());
8407 });
8408
8409 if let Some(choices) = &tabstop.choices {
8410 if let Some(selection) = tabstop.ranges.first() {
8411 self.show_snippet_choices(choices, selection.clone(), cx)
8412 }
8413 }
8414
8415 // If we're already at the last tabstop and it's at the end of the snippet,
8416 // we're done, we don't need to keep the state around.
8417 if !tabstop.is_end_tabstop {
8418 let choices = tabstops
8419 .iter()
8420 .map(|tabstop| tabstop.choices.clone())
8421 .collect();
8422
8423 let ranges = tabstops
8424 .into_iter()
8425 .map(|tabstop| tabstop.ranges)
8426 .collect::<Vec<_>>();
8427
8428 self.snippet_stack.push(SnippetState {
8429 active_index: 0,
8430 ranges,
8431 choices,
8432 });
8433 }
8434
8435 // Check whether the just-entered snippet ends with an auto-closable bracket.
8436 if self.autoclose_regions.is_empty() {
8437 let snapshot = self.buffer.read(cx).snapshot(cx);
8438 for selection in &mut self.selections.all::<Point>(cx) {
8439 let selection_head = selection.head();
8440 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8441 continue;
8442 };
8443
8444 let mut bracket_pair = None;
8445 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8446 let prev_chars = snapshot
8447 .reversed_chars_at(selection_head)
8448 .collect::<String>();
8449 for (pair, enabled) in scope.brackets() {
8450 if enabled
8451 && pair.close
8452 && prev_chars.starts_with(pair.start.as_str())
8453 && next_chars.starts_with(pair.end.as_str())
8454 {
8455 bracket_pair = Some(pair.clone());
8456 break;
8457 }
8458 }
8459 if let Some(pair) = bracket_pair {
8460 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8461 let autoclose_enabled =
8462 self.use_autoclose && snapshot_settings.use_autoclose;
8463 if autoclose_enabled {
8464 let start = snapshot.anchor_after(selection_head);
8465 let end = snapshot.anchor_after(selection_head);
8466 self.autoclose_regions.push(AutocloseRegion {
8467 selection_id: selection.id,
8468 range: start..end,
8469 pair,
8470 });
8471 }
8472 }
8473 }
8474 }
8475 }
8476 Ok(())
8477 }
8478
8479 pub fn move_to_next_snippet_tabstop(
8480 &mut self,
8481 window: &mut Window,
8482 cx: &mut Context<Self>,
8483 ) -> bool {
8484 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8485 }
8486
8487 pub fn move_to_prev_snippet_tabstop(
8488 &mut self,
8489 window: &mut Window,
8490 cx: &mut Context<Self>,
8491 ) -> bool {
8492 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8493 }
8494
8495 pub fn move_to_snippet_tabstop(
8496 &mut self,
8497 bias: Bias,
8498 window: &mut Window,
8499 cx: &mut Context<Self>,
8500 ) -> bool {
8501 if let Some(mut snippet) = self.snippet_stack.pop() {
8502 match bias {
8503 Bias::Left => {
8504 if snippet.active_index > 0 {
8505 snippet.active_index -= 1;
8506 } else {
8507 self.snippet_stack.push(snippet);
8508 return false;
8509 }
8510 }
8511 Bias::Right => {
8512 if snippet.active_index + 1 < snippet.ranges.len() {
8513 snippet.active_index += 1;
8514 } else {
8515 self.snippet_stack.push(snippet);
8516 return false;
8517 }
8518 }
8519 }
8520 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8521 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8522 s.select_anchor_ranges(current_ranges.iter().cloned())
8523 });
8524
8525 if let Some(choices) = &snippet.choices[snippet.active_index] {
8526 if let Some(selection) = current_ranges.first() {
8527 self.show_snippet_choices(&choices, selection.clone(), cx);
8528 }
8529 }
8530
8531 // If snippet state is not at the last tabstop, push it back on the stack
8532 if snippet.active_index + 1 < snippet.ranges.len() {
8533 self.snippet_stack.push(snippet);
8534 }
8535 return true;
8536 }
8537 }
8538
8539 false
8540 }
8541
8542 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8543 self.transact(window, cx, |this, window, cx| {
8544 this.select_all(&SelectAll, window, cx);
8545 this.insert("", window, cx);
8546 });
8547 }
8548
8549 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8550 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8551 self.transact(window, cx, |this, window, cx| {
8552 this.select_autoclose_pair(window, cx);
8553 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8554 if !this.linked_edit_ranges.is_empty() {
8555 let selections = this.selections.all::<MultiBufferPoint>(cx);
8556 let snapshot = this.buffer.read(cx).snapshot(cx);
8557
8558 for selection in selections.iter() {
8559 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8560 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8561 if selection_start.buffer_id != selection_end.buffer_id {
8562 continue;
8563 }
8564 if let Some(ranges) =
8565 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8566 {
8567 for (buffer, entries) in ranges {
8568 linked_ranges.entry(buffer).or_default().extend(entries);
8569 }
8570 }
8571 }
8572 }
8573
8574 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8575 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8576 for selection in &mut selections {
8577 if selection.is_empty() {
8578 let old_head = selection.head();
8579 let mut new_head =
8580 movement::left(&display_map, old_head.to_display_point(&display_map))
8581 .to_point(&display_map);
8582 if let Some((buffer, line_buffer_range)) = display_map
8583 .buffer_snapshot
8584 .buffer_line_for_row(MultiBufferRow(old_head.row))
8585 {
8586 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8587 let indent_len = match indent_size.kind {
8588 IndentKind::Space => {
8589 buffer.settings_at(line_buffer_range.start, cx).tab_size
8590 }
8591 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8592 };
8593 if old_head.column <= indent_size.len && old_head.column > 0 {
8594 let indent_len = indent_len.get();
8595 new_head = cmp::min(
8596 new_head,
8597 MultiBufferPoint::new(
8598 old_head.row,
8599 ((old_head.column - 1) / indent_len) * indent_len,
8600 ),
8601 );
8602 }
8603 }
8604
8605 selection.set_head(new_head, SelectionGoal::None);
8606 }
8607 }
8608
8609 this.signature_help_state.set_backspace_pressed(true);
8610 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8611 s.select(selections)
8612 });
8613 this.insert("", window, cx);
8614 let empty_str: Arc<str> = Arc::from("");
8615 for (buffer, edits) in linked_ranges {
8616 let snapshot = buffer.read(cx).snapshot();
8617 use text::ToPoint as TP;
8618
8619 let edits = edits
8620 .into_iter()
8621 .map(|range| {
8622 let end_point = TP::to_point(&range.end, &snapshot);
8623 let mut start_point = TP::to_point(&range.start, &snapshot);
8624
8625 if end_point == start_point {
8626 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8627 .saturating_sub(1);
8628 start_point =
8629 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8630 };
8631
8632 (start_point..end_point, empty_str.clone())
8633 })
8634 .sorted_by_key(|(range, _)| range.start)
8635 .collect::<Vec<_>>();
8636 buffer.update(cx, |this, cx| {
8637 this.edit(edits, None, cx);
8638 })
8639 }
8640 this.refresh_inline_completion(true, false, window, cx);
8641 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8642 });
8643 }
8644
8645 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8646 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8647 self.transact(window, cx, |this, window, cx| {
8648 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8649 s.move_with(|map, selection| {
8650 if selection.is_empty() {
8651 let cursor = movement::right(map, selection.head());
8652 selection.end = cursor;
8653 selection.reversed = true;
8654 selection.goal = SelectionGoal::None;
8655 }
8656 })
8657 });
8658 this.insert("", window, cx);
8659 this.refresh_inline_completion(true, false, window, cx);
8660 });
8661 }
8662
8663 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8664 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8665 if self.move_to_prev_snippet_tabstop(window, cx) {
8666 return;
8667 }
8668 self.outdent(&Outdent, window, cx);
8669 }
8670
8671 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8672 if self.move_to_next_snippet_tabstop(window, cx) {
8673 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8674 return;
8675 }
8676 if self.read_only(cx) {
8677 return;
8678 }
8679 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8680 let mut selections = self.selections.all_adjusted(cx);
8681 let buffer = self.buffer.read(cx);
8682 let snapshot = buffer.snapshot(cx);
8683 let rows_iter = selections.iter().map(|s| s.head().row);
8684 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8685
8686 let has_some_cursor_in_whitespace = selections
8687 .iter()
8688 .filter(|selection| selection.is_empty())
8689 .any(|selection| {
8690 let cursor = selection.head();
8691 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8692 cursor.column < current_indent.len
8693 });
8694
8695 let mut edits = Vec::new();
8696 let mut prev_edited_row = 0;
8697 let mut row_delta = 0;
8698 for selection in &mut selections {
8699 if selection.start.row != prev_edited_row {
8700 row_delta = 0;
8701 }
8702 prev_edited_row = selection.end.row;
8703
8704 // If the selection is non-empty, then increase the indentation of the selected lines.
8705 if !selection.is_empty() {
8706 row_delta =
8707 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8708 continue;
8709 }
8710
8711 // If the selection is empty and the cursor is in the leading whitespace before the
8712 // suggested indentation, then auto-indent the line.
8713 let cursor = selection.head();
8714 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8715 if let Some(suggested_indent) =
8716 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8717 {
8718 // If there exist any empty selection in the leading whitespace, then skip
8719 // indent for selections at the boundary.
8720 if has_some_cursor_in_whitespace
8721 && cursor.column == current_indent.len
8722 && current_indent.len == suggested_indent.len
8723 {
8724 continue;
8725 }
8726
8727 if cursor.column < suggested_indent.len
8728 && cursor.column <= current_indent.len
8729 && current_indent.len <= suggested_indent.len
8730 {
8731 selection.start = Point::new(cursor.row, suggested_indent.len);
8732 selection.end = selection.start;
8733 if row_delta == 0 {
8734 edits.extend(Buffer::edit_for_indent_size_adjustment(
8735 cursor.row,
8736 current_indent,
8737 suggested_indent,
8738 ));
8739 row_delta = suggested_indent.len - current_indent.len;
8740 }
8741 continue;
8742 }
8743 }
8744
8745 // Otherwise, insert a hard or soft tab.
8746 let settings = buffer.language_settings_at(cursor, cx);
8747 let tab_size = if settings.hard_tabs {
8748 IndentSize::tab()
8749 } else {
8750 let tab_size = settings.tab_size.get();
8751 let indent_remainder = snapshot
8752 .text_for_range(Point::new(cursor.row, 0)..cursor)
8753 .flat_map(str::chars)
8754 .fold(row_delta % tab_size, |counter: u32, c| {
8755 if c == '\t' {
8756 0
8757 } else {
8758 (counter + 1) % tab_size
8759 }
8760 });
8761
8762 let chars_to_next_tab_stop = tab_size - indent_remainder;
8763 IndentSize::spaces(chars_to_next_tab_stop)
8764 };
8765 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8766 selection.end = selection.start;
8767 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8768 row_delta += tab_size.len;
8769 }
8770
8771 self.transact(window, cx, |this, window, cx| {
8772 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8773 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8774 s.select(selections)
8775 });
8776 this.refresh_inline_completion(true, false, window, cx);
8777 });
8778 }
8779
8780 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8781 if self.read_only(cx) {
8782 return;
8783 }
8784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8785 let mut selections = self.selections.all::<Point>(cx);
8786 let mut prev_edited_row = 0;
8787 let mut row_delta = 0;
8788 let mut edits = Vec::new();
8789 let buffer = self.buffer.read(cx);
8790 let snapshot = buffer.snapshot(cx);
8791 for selection in &mut selections {
8792 if selection.start.row != prev_edited_row {
8793 row_delta = 0;
8794 }
8795 prev_edited_row = selection.end.row;
8796
8797 row_delta =
8798 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8799 }
8800
8801 self.transact(window, cx, |this, window, cx| {
8802 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8803 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8804 s.select(selections)
8805 });
8806 });
8807 }
8808
8809 fn indent_selection(
8810 buffer: &MultiBuffer,
8811 snapshot: &MultiBufferSnapshot,
8812 selection: &mut Selection<Point>,
8813 edits: &mut Vec<(Range<Point>, String)>,
8814 delta_for_start_row: u32,
8815 cx: &App,
8816 ) -> u32 {
8817 let settings = buffer.language_settings_at(selection.start, cx);
8818 let tab_size = settings.tab_size.get();
8819 let indent_kind = if settings.hard_tabs {
8820 IndentKind::Tab
8821 } else {
8822 IndentKind::Space
8823 };
8824 let mut start_row = selection.start.row;
8825 let mut end_row = selection.end.row + 1;
8826
8827 // If a selection ends at the beginning of a line, don't indent
8828 // that last line.
8829 if selection.end.column == 0 && selection.end.row > selection.start.row {
8830 end_row -= 1;
8831 }
8832
8833 // Avoid re-indenting a row that has already been indented by a
8834 // previous selection, but still update this selection's column
8835 // to reflect that indentation.
8836 if delta_for_start_row > 0 {
8837 start_row += 1;
8838 selection.start.column += delta_for_start_row;
8839 if selection.end.row == selection.start.row {
8840 selection.end.column += delta_for_start_row;
8841 }
8842 }
8843
8844 let mut delta_for_end_row = 0;
8845 let has_multiple_rows = start_row + 1 != end_row;
8846 for row in start_row..end_row {
8847 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8848 let indent_delta = match (current_indent.kind, indent_kind) {
8849 (IndentKind::Space, IndentKind::Space) => {
8850 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8851 IndentSize::spaces(columns_to_next_tab_stop)
8852 }
8853 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8854 (_, IndentKind::Tab) => IndentSize::tab(),
8855 };
8856
8857 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8858 0
8859 } else {
8860 selection.start.column
8861 };
8862 let row_start = Point::new(row, start);
8863 edits.push((
8864 row_start..row_start,
8865 indent_delta.chars().collect::<String>(),
8866 ));
8867
8868 // Update this selection's endpoints to reflect the indentation.
8869 if row == selection.start.row {
8870 selection.start.column += indent_delta.len;
8871 }
8872 if row == selection.end.row {
8873 selection.end.column += indent_delta.len;
8874 delta_for_end_row = indent_delta.len;
8875 }
8876 }
8877
8878 if selection.start.row == selection.end.row {
8879 delta_for_start_row + delta_for_end_row
8880 } else {
8881 delta_for_end_row
8882 }
8883 }
8884
8885 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8886 if self.read_only(cx) {
8887 return;
8888 }
8889 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8891 let selections = self.selections.all::<Point>(cx);
8892 let mut deletion_ranges = Vec::new();
8893 let mut last_outdent = None;
8894 {
8895 let buffer = self.buffer.read(cx);
8896 let snapshot = buffer.snapshot(cx);
8897 for selection in &selections {
8898 let settings = buffer.language_settings_at(selection.start, cx);
8899 let tab_size = settings.tab_size.get();
8900 let mut rows = selection.spanned_rows(false, &display_map);
8901
8902 // Avoid re-outdenting a row that has already been outdented by a
8903 // previous selection.
8904 if let Some(last_row) = last_outdent {
8905 if last_row == rows.start {
8906 rows.start = rows.start.next_row();
8907 }
8908 }
8909 let has_multiple_rows = rows.len() > 1;
8910 for row in rows.iter_rows() {
8911 let indent_size = snapshot.indent_size_for_line(row);
8912 if indent_size.len > 0 {
8913 let deletion_len = match indent_size.kind {
8914 IndentKind::Space => {
8915 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8916 if columns_to_prev_tab_stop == 0 {
8917 tab_size
8918 } else {
8919 columns_to_prev_tab_stop
8920 }
8921 }
8922 IndentKind::Tab => 1,
8923 };
8924 let start = if has_multiple_rows
8925 || deletion_len > selection.start.column
8926 || indent_size.len < selection.start.column
8927 {
8928 0
8929 } else {
8930 selection.start.column - deletion_len
8931 };
8932 deletion_ranges.push(
8933 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8934 );
8935 last_outdent = Some(row);
8936 }
8937 }
8938 }
8939 }
8940
8941 self.transact(window, cx, |this, window, cx| {
8942 this.buffer.update(cx, |buffer, cx| {
8943 let empty_str: Arc<str> = Arc::default();
8944 buffer.edit(
8945 deletion_ranges
8946 .into_iter()
8947 .map(|range| (range, empty_str.clone())),
8948 None,
8949 cx,
8950 );
8951 });
8952 let selections = this.selections.all::<usize>(cx);
8953 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8954 s.select(selections)
8955 });
8956 });
8957 }
8958
8959 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8960 if self.read_only(cx) {
8961 return;
8962 }
8963 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8964 let selections = self
8965 .selections
8966 .all::<usize>(cx)
8967 .into_iter()
8968 .map(|s| s.range());
8969
8970 self.transact(window, cx, |this, window, cx| {
8971 this.buffer.update(cx, |buffer, cx| {
8972 buffer.autoindent_ranges(selections, cx);
8973 });
8974 let selections = this.selections.all::<usize>(cx);
8975 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8976 s.select(selections)
8977 });
8978 });
8979 }
8980
8981 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8982 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8983 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8984 let selections = self.selections.all::<Point>(cx);
8985
8986 let mut new_cursors = Vec::new();
8987 let mut edit_ranges = Vec::new();
8988 let mut selections = selections.iter().peekable();
8989 while let Some(selection) = selections.next() {
8990 let mut rows = selection.spanned_rows(false, &display_map);
8991 let goal_display_column = selection.head().to_display_point(&display_map).column();
8992
8993 // Accumulate contiguous regions of rows that we want to delete.
8994 while let Some(next_selection) = selections.peek() {
8995 let next_rows = next_selection.spanned_rows(false, &display_map);
8996 if next_rows.start <= rows.end {
8997 rows.end = next_rows.end;
8998 selections.next().unwrap();
8999 } else {
9000 break;
9001 }
9002 }
9003
9004 let buffer = &display_map.buffer_snapshot;
9005 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9006 let edit_end;
9007 let cursor_buffer_row;
9008 if buffer.max_point().row >= rows.end.0 {
9009 // If there's a line after the range, delete the \n from the end of the row range
9010 // and position the cursor on the next line.
9011 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9012 cursor_buffer_row = rows.end;
9013 } else {
9014 // If there isn't a line after the range, delete the \n from the line before the
9015 // start of the row range and position the cursor there.
9016 edit_start = edit_start.saturating_sub(1);
9017 edit_end = buffer.len();
9018 cursor_buffer_row = rows.start.previous_row();
9019 }
9020
9021 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9022 *cursor.column_mut() =
9023 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9024
9025 new_cursors.push((
9026 selection.id,
9027 buffer.anchor_after(cursor.to_point(&display_map)),
9028 ));
9029 edit_ranges.push(edit_start..edit_end);
9030 }
9031
9032 self.transact(window, cx, |this, window, cx| {
9033 let buffer = this.buffer.update(cx, |buffer, cx| {
9034 let empty_str: Arc<str> = Arc::default();
9035 buffer.edit(
9036 edit_ranges
9037 .into_iter()
9038 .map(|range| (range, empty_str.clone())),
9039 None,
9040 cx,
9041 );
9042 buffer.snapshot(cx)
9043 });
9044 let new_selections = new_cursors
9045 .into_iter()
9046 .map(|(id, cursor)| {
9047 let cursor = cursor.to_point(&buffer);
9048 Selection {
9049 id,
9050 start: cursor,
9051 end: cursor,
9052 reversed: false,
9053 goal: SelectionGoal::None,
9054 }
9055 })
9056 .collect();
9057
9058 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9059 s.select(new_selections);
9060 });
9061 });
9062 }
9063
9064 pub fn join_lines_impl(
9065 &mut self,
9066 insert_whitespace: bool,
9067 window: &mut Window,
9068 cx: &mut Context<Self>,
9069 ) {
9070 if self.read_only(cx) {
9071 return;
9072 }
9073 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9074 for selection in self.selections.all::<Point>(cx) {
9075 let start = MultiBufferRow(selection.start.row);
9076 // Treat single line selections as if they include the next line. Otherwise this action
9077 // would do nothing for single line selections individual cursors.
9078 let end = if selection.start.row == selection.end.row {
9079 MultiBufferRow(selection.start.row + 1)
9080 } else {
9081 MultiBufferRow(selection.end.row)
9082 };
9083
9084 if let Some(last_row_range) = row_ranges.last_mut() {
9085 if start <= last_row_range.end {
9086 last_row_range.end = end;
9087 continue;
9088 }
9089 }
9090 row_ranges.push(start..end);
9091 }
9092
9093 let snapshot = self.buffer.read(cx).snapshot(cx);
9094 let mut cursor_positions = Vec::new();
9095 for row_range in &row_ranges {
9096 let anchor = snapshot.anchor_before(Point::new(
9097 row_range.end.previous_row().0,
9098 snapshot.line_len(row_range.end.previous_row()),
9099 ));
9100 cursor_positions.push(anchor..anchor);
9101 }
9102
9103 self.transact(window, cx, |this, window, cx| {
9104 for row_range in row_ranges.into_iter().rev() {
9105 for row in row_range.iter_rows().rev() {
9106 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9107 let next_line_row = row.next_row();
9108 let indent = snapshot.indent_size_for_line(next_line_row);
9109 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9110
9111 let replace =
9112 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9113 " "
9114 } else {
9115 ""
9116 };
9117
9118 this.buffer.update(cx, |buffer, cx| {
9119 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9120 });
9121 }
9122 }
9123
9124 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9125 s.select_anchor_ranges(cursor_positions)
9126 });
9127 });
9128 }
9129
9130 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9131 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9132 self.join_lines_impl(true, window, cx);
9133 }
9134
9135 pub fn sort_lines_case_sensitive(
9136 &mut self,
9137 _: &SortLinesCaseSensitive,
9138 window: &mut Window,
9139 cx: &mut Context<Self>,
9140 ) {
9141 self.manipulate_lines(window, cx, |lines| lines.sort())
9142 }
9143
9144 pub fn sort_lines_case_insensitive(
9145 &mut self,
9146 _: &SortLinesCaseInsensitive,
9147 window: &mut Window,
9148 cx: &mut Context<Self>,
9149 ) {
9150 self.manipulate_lines(window, cx, |lines| {
9151 lines.sort_by_key(|line| line.to_lowercase())
9152 })
9153 }
9154
9155 pub fn unique_lines_case_insensitive(
9156 &mut self,
9157 _: &UniqueLinesCaseInsensitive,
9158 window: &mut Window,
9159 cx: &mut Context<Self>,
9160 ) {
9161 self.manipulate_lines(window, cx, |lines| {
9162 let mut seen = HashSet::default();
9163 lines.retain(|line| seen.insert(line.to_lowercase()));
9164 })
9165 }
9166
9167 pub fn unique_lines_case_sensitive(
9168 &mut self,
9169 _: &UniqueLinesCaseSensitive,
9170 window: &mut Window,
9171 cx: &mut Context<Self>,
9172 ) {
9173 self.manipulate_lines(window, cx, |lines| {
9174 let mut seen = HashSet::default();
9175 lines.retain(|line| seen.insert(*line));
9176 })
9177 }
9178
9179 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9180 let Some(project) = self.project.clone() else {
9181 return;
9182 };
9183 self.reload(project, window, cx)
9184 .detach_and_notify_err(window, cx);
9185 }
9186
9187 pub fn restore_file(
9188 &mut self,
9189 _: &::git::RestoreFile,
9190 window: &mut Window,
9191 cx: &mut Context<Self>,
9192 ) {
9193 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9194 let mut buffer_ids = HashSet::default();
9195 let snapshot = self.buffer().read(cx).snapshot(cx);
9196 for selection in self.selections.all::<usize>(cx) {
9197 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9198 }
9199
9200 let buffer = self.buffer().read(cx);
9201 let ranges = buffer_ids
9202 .into_iter()
9203 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9204 .collect::<Vec<_>>();
9205
9206 self.restore_hunks_in_ranges(ranges, window, cx);
9207 }
9208
9209 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9210 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9211 let selections = self
9212 .selections
9213 .all(cx)
9214 .into_iter()
9215 .map(|s| s.range())
9216 .collect();
9217 self.restore_hunks_in_ranges(selections, window, cx);
9218 }
9219
9220 pub fn restore_hunks_in_ranges(
9221 &mut self,
9222 ranges: Vec<Range<Point>>,
9223 window: &mut Window,
9224 cx: &mut Context<Editor>,
9225 ) {
9226 let mut revert_changes = HashMap::default();
9227 let chunk_by = self
9228 .snapshot(window, cx)
9229 .hunks_for_ranges(ranges)
9230 .into_iter()
9231 .chunk_by(|hunk| hunk.buffer_id);
9232 for (buffer_id, hunks) in &chunk_by {
9233 let hunks = hunks.collect::<Vec<_>>();
9234 for hunk in &hunks {
9235 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9236 }
9237 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9238 }
9239 drop(chunk_by);
9240 if !revert_changes.is_empty() {
9241 self.transact(window, cx, |editor, window, cx| {
9242 editor.restore(revert_changes, window, cx);
9243 });
9244 }
9245 }
9246
9247 pub fn open_active_item_in_terminal(
9248 &mut self,
9249 _: &OpenInTerminal,
9250 window: &mut Window,
9251 cx: &mut Context<Self>,
9252 ) {
9253 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9254 let project_path = buffer.read(cx).project_path(cx)?;
9255 let project = self.project.as_ref()?.read(cx);
9256 let entry = project.entry_for_path(&project_path, cx)?;
9257 let parent = match &entry.canonical_path {
9258 Some(canonical_path) => canonical_path.to_path_buf(),
9259 None => project.absolute_path(&project_path, cx)?,
9260 }
9261 .parent()?
9262 .to_path_buf();
9263 Some(parent)
9264 }) {
9265 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9266 }
9267 }
9268
9269 fn set_breakpoint_context_menu(
9270 &mut self,
9271 display_row: DisplayRow,
9272 position: Option<Anchor>,
9273 clicked_point: gpui::Point<Pixels>,
9274 window: &mut Window,
9275 cx: &mut Context<Self>,
9276 ) {
9277 if !cx.has_flag::<DebuggerFeatureFlag>() {
9278 return;
9279 }
9280 let source = self
9281 .buffer
9282 .read(cx)
9283 .snapshot(cx)
9284 .anchor_before(Point::new(display_row.0, 0u32));
9285
9286 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9287
9288 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9289 self,
9290 source,
9291 clicked_point,
9292 context_menu,
9293 window,
9294 cx,
9295 );
9296 }
9297
9298 fn add_edit_breakpoint_block(
9299 &mut self,
9300 anchor: Anchor,
9301 breakpoint: &Breakpoint,
9302 edit_action: BreakpointPromptEditAction,
9303 window: &mut Window,
9304 cx: &mut Context<Self>,
9305 ) {
9306 let weak_editor = cx.weak_entity();
9307 let bp_prompt = cx.new(|cx| {
9308 BreakpointPromptEditor::new(
9309 weak_editor,
9310 anchor,
9311 breakpoint.clone(),
9312 edit_action,
9313 window,
9314 cx,
9315 )
9316 });
9317
9318 let height = bp_prompt.update(cx, |this, cx| {
9319 this.prompt
9320 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9321 });
9322 let cloned_prompt = bp_prompt.clone();
9323 let blocks = vec![BlockProperties {
9324 style: BlockStyle::Sticky,
9325 placement: BlockPlacement::Above(anchor),
9326 height: Some(height),
9327 render: Arc::new(move |cx| {
9328 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9329 cloned_prompt.clone().into_any_element()
9330 }),
9331 priority: 0,
9332 }];
9333
9334 let focus_handle = bp_prompt.focus_handle(cx);
9335 window.focus(&focus_handle);
9336
9337 let block_ids = self.insert_blocks(blocks, None, cx);
9338 bp_prompt.update(cx, |prompt, _| {
9339 prompt.add_block_ids(block_ids);
9340 });
9341 }
9342
9343 pub(crate) fn breakpoint_at_row(
9344 &self,
9345 row: u32,
9346 window: &mut Window,
9347 cx: &mut Context<Self>,
9348 ) -> Option<(Anchor, Breakpoint)> {
9349 let snapshot = self.snapshot(window, cx);
9350 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9351
9352 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9353 }
9354
9355 pub(crate) fn breakpoint_at_anchor(
9356 &self,
9357 breakpoint_position: Anchor,
9358 snapshot: &EditorSnapshot,
9359 cx: &mut Context<Self>,
9360 ) -> Option<(Anchor, Breakpoint)> {
9361 let project = self.project.clone()?;
9362
9363 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9364 snapshot
9365 .buffer_snapshot
9366 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9367 })?;
9368
9369 let enclosing_excerpt = breakpoint_position.excerpt_id;
9370 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9371 let buffer_snapshot = buffer.read(cx).snapshot();
9372
9373 let row = buffer_snapshot
9374 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9375 .row;
9376
9377 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9378 let anchor_end = snapshot
9379 .buffer_snapshot
9380 .anchor_after(Point::new(row, line_len));
9381
9382 let bp = self
9383 .breakpoint_store
9384 .as_ref()?
9385 .read_with(cx, |breakpoint_store, cx| {
9386 breakpoint_store
9387 .breakpoints(
9388 &buffer,
9389 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9390 &buffer_snapshot,
9391 cx,
9392 )
9393 .next()
9394 .and_then(|(anchor, bp)| {
9395 let breakpoint_row = buffer_snapshot
9396 .summary_for_anchor::<text::PointUtf16>(anchor)
9397 .row;
9398
9399 if breakpoint_row == row {
9400 snapshot
9401 .buffer_snapshot
9402 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9403 .map(|anchor| (anchor, bp.clone()))
9404 } else {
9405 None
9406 }
9407 })
9408 });
9409 bp
9410 }
9411
9412 pub fn edit_log_breakpoint(
9413 &mut self,
9414 _: &EditLogBreakpoint,
9415 window: &mut Window,
9416 cx: &mut Context<Self>,
9417 ) {
9418 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9419 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9420 message: None,
9421 state: BreakpointState::Enabled,
9422 condition: None,
9423 hit_condition: None,
9424 });
9425
9426 self.add_edit_breakpoint_block(
9427 anchor,
9428 &breakpoint,
9429 BreakpointPromptEditAction::Log,
9430 window,
9431 cx,
9432 );
9433 }
9434 }
9435
9436 fn breakpoints_at_cursors(
9437 &self,
9438 window: &mut Window,
9439 cx: &mut Context<Self>,
9440 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9441 let snapshot = self.snapshot(window, cx);
9442 let cursors = self
9443 .selections
9444 .disjoint_anchors()
9445 .into_iter()
9446 .map(|selection| {
9447 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9448
9449 let breakpoint_position = self
9450 .breakpoint_at_row(cursor_position.row, window, cx)
9451 .map(|bp| bp.0)
9452 .unwrap_or_else(|| {
9453 snapshot
9454 .display_snapshot
9455 .buffer_snapshot
9456 .anchor_after(Point::new(cursor_position.row, 0))
9457 });
9458
9459 let breakpoint = self
9460 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9461 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9462
9463 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9464 })
9465 // 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.
9466 .collect::<HashMap<Anchor, _>>();
9467
9468 cursors.into_iter().collect()
9469 }
9470
9471 pub fn enable_breakpoint(
9472 &mut self,
9473 _: &crate::actions::EnableBreakpoint,
9474 window: &mut Window,
9475 cx: &mut Context<Self>,
9476 ) {
9477 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9478 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9479 continue;
9480 };
9481 self.edit_breakpoint_at_anchor(
9482 anchor,
9483 breakpoint,
9484 BreakpointEditAction::InvertState,
9485 cx,
9486 );
9487 }
9488 }
9489
9490 pub fn disable_breakpoint(
9491 &mut self,
9492 _: &crate::actions::DisableBreakpoint,
9493 window: &mut Window,
9494 cx: &mut Context<Self>,
9495 ) {
9496 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9497 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9498 continue;
9499 };
9500 self.edit_breakpoint_at_anchor(
9501 anchor,
9502 breakpoint,
9503 BreakpointEditAction::InvertState,
9504 cx,
9505 );
9506 }
9507 }
9508
9509 pub fn toggle_breakpoint(
9510 &mut self,
9511 _: &crate::actions::ToggleBreakpoint,
9512 window: &mut Window,
9513 cx: &mut Context<Self>,
9514 ) {
9515 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9516 if let Some(breakpoint) = breakpoint {
9517 self.edit_breakpoint_at_anchor(
9518 anchor,
9519 breakpoint,
9520 BreakpointEditAction::Toggle,
9521 cx,
9522 );
9523 } else {
9524 self.edit_breakpoint_at_anchor(
9525 anchor,
9526 Breakpoint::new_standard(),
9527 BreakpointEditAction::Toggle,
9528 cx,
9529 );
9530 }
9531 }
9532 }
9533
9534 pub fn edit_breakpoint_at_anchor(
9535 &mut self,
9536 breakpoint_position: Anchor,
9537 breakpoint: Breakpoint,
9538 edit_action: BreakpointEditAction,
9539 cx: &mut Context<Self>,
9540 ) {
9541 let Some(breakpoint_store) = &self.breakpoint_store else {
9542 return;
9543 };
9544
9545 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9546 if breakpoint_position == Anchor::min() {
9547 self.buffer()
9548 .read(cx)
9549 .excerpt_buffer_ids()
9550 .into_iter()
9551 .next()
9552 } else {
9553 None
9554 }
9555 }) else {
9556 return;
9557 };
9558
9559 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9560 return;
9561 };
9562
9563 breakpoint_store.update(cx, |breakpoint_store, cx| {
9564 breakpoint_store.toggle_breakpoint(
9565 buffer,
9566 (breakpoint_position.text_anchor, breakpoint),
9567 edit_action,
9568 cx,
9569 );
9570 });
9571
9572 cx.notify();
9573 }
9574
9575 #[cfg(any(test, feature = "test-support"))]
9576 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9577 self.breakpoint_store.clone()
9578 }
9579
9580 pub fn prepare_restore_change(
9581 &self,
9582 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9583 hunk: &MultiBufferDiffHunk,
9584 cx: &mut App,
9585 ) -> Option<()> {
9586 if hunk.is_created_file() {
9587 return None;
9588 }
9589 let buffer = self.buffer.read(cx);
9590 let diff = buffer.diff_for(hunk.buffer_id)?;
9591 let buffer = buffer.buffer(hunk.buffer_id)?;
9592 let buffer = buffer.read(cx);
9593 let original_text = diff
9594 .read(cx)
9595 .base_text()
9596 .as_rope()
9597 .slice(hunk.diff_base_byte_range.clone());
9598 let buffer_snapshot = buffer.snapshot();
9599 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9600 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9601 probe
9602 .0
9603 .start
9604 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9605 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9606 }) {
9607 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9608 Some(())
9609 } else {
9610 None
9611 }
9612 }
9613
9614 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9615 self.manipulate_lines(window, cx, |lines| lines.reverse())
9616 }
9617
9618 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9619 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9620 }
9621
9622 fn manipulate_lines<Fn>(
9623 &mut self,
9624 window: &mut Window,
9625 cx: &mut Context<Self>,
9626 mut callback: Fn,
9627 ) where
9628 Fn: FnMut(&mut Vec<&str>),
9629 {
9630 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9631
9632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9633 let buffer = self.buffer.read(cx).snapshot(cx);
9634
9635 let mut edits = Vec::new();
9636
9637 let selections = self.selections.all::<Point>(cx);
9638 let mut selections = selections.iter().peekable();
9639 let mut contiguous_row_selections = Vec::new();
9640 let mut new_selections = Vec::new();
9641 let mut added_lines = 0;
9642 let mut removed_lines = 0;
9643
9644 while let Some(selection) = selections.next() {
9645 let (start_row, end_row) = consume_contiguous_rows(
9646 &mut contiguous_row_selections,
9647 selection,
9648 &display_map,
9649 &mut selections,
9650 );
9651
9652 let start_point = Point::new(start_row.0, 0);
9653 let end_point = Point::new(
9654 end_row.previous_row().0,
9655 buffer.line_len(end_row.previous_row()),
9656 );
9657 let text = buffer
9658 .text_for_range(start_point..end_point)
9659 .collect::<String>();
9660
9661 let mut lines = text.split('\n').collect_vec();
9662
9663 let lines_before = lines.len();
9664 callback(&mut lines);
9665 let lines_after = lines.len();
9666
9667 edits.push((start_point..end_point, lines.join("\n")));
9668
9669 // Selections must change based on added and removed line count
9670 let start_row =
9671 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9672 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9673 new_selections.push(Selection {
9674 id: selection.id,
9675 start: start_row,
9676 end: end_row,
9677 goal: SelectionGoal::None,
9678 reversed: selection.reversed,
9679 });
9680
9681 if lines_after > lines_before {
9682 added_lines += lines_after - lines_before;
9683 } else if lines_before > lines_after {
9684 removed_lines += lines_before - lines_after;
9685 }
9686 }
9687
9688 self.transact(window, cx, |this, window, cx| {
9689 let buffer = this.buffer.update(cx, |buffer, cx| {
9690 buffer.edit(edits, None, cx);
9691 buffer.snapshot(cx)
9692 });
9693
9694 // Recalculate offsets on newly edited buffer
9695 let new_selections = new_selections
9696 .iter()
9697 .map(|s| {
9698 let start_point = Point::new(s.start.0, 0);
9699 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9700 Selection {
9701 id: s.id,
9702 start: buffer.point_to_offset(start_point),
9703 end: buffer.point_to_offset(end_point),
9704 goal: s.goal,
9705 reversed: s.reversed,
9706 }
9707 })
9708 .collect();
9709
9710 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9711 s.select(new_selections);
9712 });
9713
9714 this.request_autoscroll(Autoscroll::fit(), cx);
9715 });
9716 }
9717
9718 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9719 self.manipulate_text(window, cx, |text| {
9720 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9721 if has_upper_case_characters {
9722 text.to_lowercase()
9723 } else {
9724 text.to_uppercase()
9725 }
9726 })
9727 }
9728
9729 pub fn convert_to_upper_case(
9730 &mut self,
9731 _: &ConvertToUpperCase,
9732 window: &mut Window,
9733 cx: &mut Context<Self>,
9734 ) {
9735 self.manipulate_text(window, cx, |text| text.to_uppercase())
9736 }
9737
9738 pub fn convert_to_lower_case(
9739 &mut self,
9740 _: &ConvertToLowerCase,
9741 window: &mut Window,
9742 cx: &mut Context<Self>,
9743 ) {
9744 self.manipulate_text(window, cx, |text| text.to_lowercase())
9745 }
9746
9747 pub fn convert_to_title_case(
9748 &mut self,
9749 _: &ConvertToTitleCase,
9750 window: &mut Window,
9751 cx: &mut Context<Self>,
9752 ) {
9753 self.manipulate_text(window, cx, |text| {
9754 text.split('\n')
9755 .map(|line| line.to_case(Case::Title))
9756 .join("\n")
9757 })
9758 }
9759
9760 pub fn convert_to_snake_case(
9761 &mut self,
9762 _: &ConvertToSnakeCase,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) {
9766 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9767 }
9768
9769 pub fn convert_to_kebab_case(
9770 &mut self,
9771 _: &ConvertToKebabCase,
9772 window: &mut Window,
9773 cx: &mut Context<Self>,
9774 ) {
9775 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9776 }
9777
9778 pub fn convert_to_upper_camel_case(
9779 &mut self,
9780 _: &ConvertToUpperCamelCase,
9781 window: &mut Window,
9782 cx: &mut Context<Self>,
9783 ) {
9784 self.manipulate_text(window, cx, |text| {
9785 text.split('\n')
9786 .map(|line| line.to_case(Case::UpperCamel))
9787 .join("\n")
9788 })
9789 }
9790
9791 pub fn convert_to_lower_camel_case(
9792 &mut self,
9793 _: &ConvertToLowerCamelCase,
9794 window: &mut Window,
9795 cx: &mut Context<Self>,
9796 ) {
9797 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9798 }
9799
9800 pub fn convert_to_opposite_case(
9801 &mut self,
9802 _: &ConvertToOppositeCase,
9803 window: &mut Window,
9804 cx: &mut Context<Self>,
9805 ) {
9806 self.manipulate_text(window, cx, |text| {
9807 text.chars()
9808 .fold(String::with_capacity(text.len()), |mut t, c| {
9809 if c.is_uppercase() {
9810 t.extend(c.to_lowercase());
9811 } else {
9812 t.extend(c.to_uppercase());
9813 }
9814 t
9815 })
9816 })
9817 }
9818
9819 pub fn convert_to_rot13(
9820 &mut self,
9821 _: &ConvertToRot13,
9822 window: &mut Window,
9823 cx: &mut Context<Self>,
9824 ) {
9825 self.manipulate_text(window, cx, |text| {
9826 text.chars()
9827 .map(|c| match c {
9828 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9829 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9830 _ => c,
9831 })
9832 .collect()
9833 })
9834 }
9835
9836 pub fn convert_to_rot47(
9837 &mut self,
9838 _: &ConvertToRot47,
9839 window: &mut Window,
9840 cx: &mut Context<Self>,
9841 ) {
9842 self.manipulate_text(window, cx, |text| {
9843 text.chars()
9844 .map(|c| {
9845 let code_point = c as u32;
9846 if code_point >= 33 && code_point <= 126 {
9847 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9848 }
9849 c
9850 })
9851 .collect()
9852 })
9853 }
9854
9855 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9856 where
9857 Fn: FnMut(&str) -> String,
9858 {
9859 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9860 let buffer = self.buffer.read(cx).snapshot(cx);
9861
9862 let mut new_selections = Vec::new();
9863 let mut edits = Vec::new();
9864 let mut selection_adjustment = 0i32;
9865
9866 for selection in self.selections.all::<usize>(cx) {
9867 let selection_is_empty = selection.is_empty();
9868
9869 let (start, end) = if selection_is_empty {
9870 let word_range = movement::surrounding_word(
9871 &display_map,
9872 selection.start.to_display_point(&display_map),
9873 );
9874 let start = word_range.start.to_offset(&display_map, Bias::Left);
9875 let end = word_range.end.to_offset(&display_map, Bias::Left);
9876 (start, end)
9877 } else {
9878 (selection.start, selection.end)
9879 };
9880
9881 let text = buffer.text_for_range(start..end).collect::<String>();
9882 let old_length = text.len() as i32;
9883 let text = callback(&text);
9884
9885 new_selections.push(Selection {
9886 start: (start as i32 - selection_adjustment) as usize,
9887 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9888 goal: SelectionGoal::None,
9889 ..selection
9890 });
9891
9892 selection_adjustment += old_length - text.len() as i32;
9893
9894 edits.push((start..end, text));
9895 }
9896
9897 self.transact(window, cx, |this, window, cx| {
9898 this.buffer.update(cx, |buffer, cx| {
9899 buffer.edit(edits, None, cx);
9900 });
9901
9902 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9903 s.select(new_selections);
9904 });
9905
9906 this.request_autoscroll(Autoscroll::fit(), cx);
9907 });
9908 }
9909
9910 pub fn duplicate(
9911 &mut self,
9912 upwards: bool,
9913 whole_lines: bool,
9914 window: &mut Window,
9915 cx: &mut Context<Self>,
9916 ) {
9917 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9918
9919 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9920 let buffer = &display_map.buffer_snapshot;
9921 let selections = self.selections.all::<Point>(cx);
9922
9923 let mut edits = Vec::new();
9924 let mut selections_iter = selections.iter().peekable();
9925 while let Some(selection) = selections_iter.next() {
9926 let mut rows = selection.spanned_rows(false, &display_map);
9927 // duplicate line-wise
9928 if whole_lines || selection.start == selection.end {
9929 // Avoid duplicating the same lines twice.
9930 while let Some(next_selection) = selections_iter.peek() {
9931 let next_rows = next_selection.spanned_rows(false, &display_map);
9932 if next_rows.start < rows.end {
9933 rows.end = next_rows.end;
9934 selections_iter.next().unwrap();
9935 } else {
9936 break;
9937 }
9938 }
9939
9940 // Copy the text from the selected row region and splice it either at the start
9941 // or end of the region.
9942 let start = Point::new(rows.start.0, 0);
9943 let end = Point::new(
9944 rows.end.previous_row().0,
9945 buffer.line_len(rows.end.previous_row()),
9946 );
9947 let text = buffer
9948 .text_for_range(start..end)
9949 .chain(Some("\n"))
9950 .collect::<String>();
9951 let insert_location = if upwards {
9952 Point::new(rows.end.0, 0)
9953 } else {
9954 start
9955 };
9956 edits.push((insert_location..insert_location, text));
9957 } else {
9958 // duplicate character-wise
9959 let start = selection.start;
9960 let end = selection.end;
9961 let text = buffer.text_for_range(start..end).collect::<String>();
9962 edits.push((selection.end..selection.end, text));
9963 }
9964 }
9965
9966 self.transact(window, cx, |this, _, cx| {
9967 this.buffer.update(cx, |buffer, cx| {
9968 buffer.edit(edits, None, cx);
9969 });
9970
9971 this.request_autoscroll(Autoscroll::fit(), cx);
9972 });
9973 }
9974
9975 pub fn duplicate_line_up(
9976 &mut self,
9977 _: &DuplicateLineUp,
9978 window: &mut Window,
9979 cx: &mut Context<Self>,
9980 ) {
9981 self.duplicate(true, true, window, cx);
9982 }
9983
9984 pub fn duplicate_line_down(
9985 &mut self,
9986 _: &DuplicateLineDown,
9987 window: &mut Window,
9988 cx: &mut Context<Self>,
9989 ) {
9990 self.duplicate(false, true, window, cx);
9991 }
9992
9993 pub fn duplicate_selection(
9994 &mut self,
9995 _: &DuplicateSelection,
9996 window: &mut Window,
9997 cx: &mut Context<Self>,
9998 ) {
9999 self.duplicate(false, false, window, cx);
10000 }
10001
10002 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10003 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10004
10005 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10006 let buffer = self.buffer.read(cx).snapshot(cx);
10007
10008 let mut edits = Vec::new();
10009 let mut unfold_ranges = Vec::new();
10010 let mut refold_creases = Vec::new();
10011
10012 let selections = self.selections.all::<Point>(cx);
10013 let mut selections = selections.iter().peekable();
10014 let mut contiguous_row_selections = Vec::new();
10015 let mut new_selections = Vec::new();
10016
10017 while let Some(selection) = selections.next() {
10018 // Find all the selections that span a contiguous row range
10019 let (start_row, end_row) = consume_contiguous_rows(
10020 &mut contiguous_row_selections,
10021 selection,
10022 &display_map,
10023 &mut selections,
10024 );
10025
10026 // Move the text spanned by the row range to be before the line preceding the row range
10027 if start_row.0 > 0 {
10028 let range_to_move = Point::new(
10029 start_row.previous_row().0,
10030 buffer.line_len(start_row.previous_row()),
10031 )
10032 ..Point::new(
10033 end_row.previous_row().0,
10034 buffer.line_len(end_row.previous_row()),
10035 );
10036 let insertion_point = display_map
10037 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10038 .0;
10039
10040 // Don't move lines across excerpts
10041 if buffer
10042 .excerpt_containing(insertion_point..range_to_move.end)
10043 .is_some()
10044 {
10045 let text = buffer
10046 .text_for_range(range_to_move.clone())
10047 .flat_map(|s| s.chars())
10048 .skip(1)
10049 .chain(['\n'])
10050 .collect::<String>();
10051
10052 edits.push((
10053 buffer.anchor_after(range_to_move.start)
10054 ..buffer.anchor_before(range_to_move.end),
10055 String::new(),
10056 ));
10057 let insertion_anchor = buffer.anchor_after(insertion_point);
10058 edits.push((insertion_anchor..insertion_anchor, text));
10059
10060 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10061
10062 // Move selections up
10063 new_selections.extend(contiguous_row_selections.drain(..).map(
10064 |mut selection| {
10065 selection.start.row -= row_delta;
10066 selection.end.row -= row_delta;
10067 selection
10068 },
10069 ));
10070
10071 // Move folds up
10072 unfold_ranges.push(range_to_move.clone());
10073 for fold in display_map.folds_in_range(
10074 buffer.anchor_before(range_to_move.start)
10075 ..buffer.anchor_after(range_to_move.end),
10076 ) {
10077 let mut start = fold.range.start.to_point(&buffer);
10078 let mut end = fold.range.end.to_point(&buffer);
10079 start.row -= row_delta;
10080 end.row -= row_delta;
10081 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10082 }
10083 }
10084 }
10085
10086 // If we didn't move line(s), preserve the existing selections
10087 new_selections.append(&mut contiguous_row_selections);
10088 }
10089
10090 self.transact(window, cx, |this, window, cx| {
10091 this.unfold_ranges(&unfold_ranges, true, true, cx);
10092 this.buffer.update(cx, |buffer, cx| {
10093 for (range, text) in edits {
10094 buffer.edit([(range, text)], None, cx);
10095 }
10096 });
10097 this.fold_creases(refold_creases, true, window, cx);
10098 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10099 s.select(new_selections);
10100 })
10101 });
10102 }
10103
10104 pub fn move_line_down(
10105 &mut self,
10106 _: &MoveLineDown,
10107 window: &mut Window,
10108 cx: &mut Context<Self>,
10109 ) {
10110 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10111
10112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10113 let buffer = self.buffer.read(cx).snapshot(cx);
10114
10115 let mut edits = Vec::new();
10116 let mut unfold_ranges = Vec::new();
10117 let mut refold_creases = Vec::new();
10118
10119 let selections = self.selections.all::<Point>(cx);
10120 let mut selections = selections.iter().peekable();
10121 let mut contiguous_row_selections = Vec::new();
10122 let mut new_selections = Vec::new();
10123
10124 while let Some(selection) = selections.next() {
10125 // Find all the selections that span a contiguous row range
10126 let (start_row, end_row) = consume_contiguous_rows(
10127 &mut contiguous_row_selections,
10128 selection,
10129 &display_map,
10130 &mut selections,
10131 );
10132
10133 // Move the text spanned by the row range to be after the last line of the row range
10134 if end_row.0 <= buffer.max_point().row {
10135 let range_to_move =
10136 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10137 let insertion_point = display_map
10138 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10139 .0;
10140
10141 // Don't move lines across excerpt boundaries
10142 if buffer
10143 .excerpt_containing(range_to_move.start..insertion_point)
10144 .is_some()
10145 {
10146 let mut text = String::from("\n");
10147 text.extend(buffer.text_for_range(range_to_move.clone()));
10148 text.pop(); // Drop trailing newline
10149 edits.push((
10150 buffer.anchor_after(range_to_move.start)
10151 ..buffer.anchor_before(range_to_move.end),
10152 String::new(),
10153 ));
10154 let insertion_anchor = buffer.anchor_after(insertion_point);
10155 edits.push((insertion_anchor..insertion_anchor, text));
10156
10157 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10158
10159 // Move selections down
10160 new_selections.extend(contiguous_row_selections.drain(..).map(
10161 |mut selection| {
10162 selection.start.row += row_delta;
10163 selection.end.row += row_delta;
10164 selection
10165 },
10166 ));
10167
10168 // Move folds down
10169 unfold_ranges.push(range_to_move.clone());
10170 for fold in display_map.folds_in_range(
10171 buffer.anchor_before(range_to_move.start)
10172 ..buffer.anchor_after(range_to_move.end),
10173 ) {
10174 let mut start = fold.range.start.to_point(&buffer);
10175 let mut end = fold.range.end.to_point(&buffer);
10176 start.row += row_delta;
10177 end.row += row_delta;
10178 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10179 }
10180 }
10181 }
10182
10183 // If we didn't move line(s), preserve the existing selections
10184 new_selections.append(&mut contiguous_row_selections);
10185 }
10186
10187 self.transact(window, cx, |this, window, cx| {
10188 this.unfold_ranges(&unfold_ranges, true, true, cx);
10189 this.buffer.update(cx, |buffer, cx| {
10190 for (range, text) in edits {
10191 buffer.edit([(range, text)], None, cx);
10192 }
10193 });
10194 this.fold_creases(refold_creases, true, window, cx);
10195 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10196 s.select(new_selections)
10197 });
10198 });
10199 }
10200
10201 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10203 let text_layout_details = &self.text_layout_details(window);
10204 self.transact(window, cx, |this, window, cx| {
10205 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10206 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10207 s.move_with(|display_map, selection| {
10208 if !selection.is_empty() {
10209 return;
10210 }
10211
10212 let mut head = selection.head();
10213 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10214 if head.column() == display_map.line_len(head.row()) {
10215 transpose_offset = display_map
10216 .buffer_snapshot
10217 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10218 }
10219
10220 if transpose_offset == 0 {
10221 return;
10222 }
10223
10224 *head.column_mut() += 1;
10225 head = display_map.clip_point(head, Bias::Right);
10226 let goal = SelectionGoal::HorizontalPosition(
10227 display_map
10228 .x_for_display_point(head, text_layout_details)
10229 .into(),
10230 );
10231 selection.collapse_to(head, goal);
10232
10233 let transpose_start = display_map
10234 .buffer_snapshot
10235 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10236 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10237 let transpose_end = display_map
10238 .buffer_snapshot
10239 .clip_offset(transpose_offset + 1, Bias::Right);
10240 if let Some(ch) =
10241 display_map.buffer_snapshot.chars_at(transpose_start).next()
10242 {
10243 edits.push((transpose_start..transpose_offset, String::new()));
10244 edits.push((transpose_end..transpose_end, ch.to_string()));
10245 }
10246 }
10247 });
10248 edits
10249 });
10250 this.buffer
10251 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10252 let selections = this.selections.all::<usize>(cx);
10253 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10254 s.select(selections);
10255 });
10256 });
10257 }
10258
10259 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10260 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10261 self.rewrap_impl(RewrapOptions::default(), cx)
10262 }
10263
10264 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10265 let buffer = self.buffer.read(cx).snapshot(cx);
10266 let selections = self.selections.all::<Point>(cx);
10267 let mut selections = selections.iter().peekable();
10268
10269 let mut edits = Vec::new();
10270 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10271
10272 while let Some(selection) = selections.next() {
10273 let mut start_row = selection.start.row;
10274 let mut end_row = selection.end.row;
10275
10276 // Skip selections that overlap with a range that has already been rewrapped.
10277 let selection_range = start_row..end_row;
10278 if rewrapped_row_ranges
10279 .iter()
10280 .any(|range| range.overlaps(&selection_range))
10281 {
10282 continue;
10283 }
10284
10285 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10286
10287 // Since not all lines in the selection may be at the same indent
10288 // level, choose the indent size that is the most common between all
10289 // of the lines.
10290 //
10291 // If there is a tie, we use the deepest indent.
10292 let (indent_size, indent_end) = {
10293 let mut indent_size_occurrences = HashMap::default();
10294 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10295
10296 for row in start_row..=end_row {
10297 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10298 rows_by_indent_size.entry(indent).or_default().push(row);
10299 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10300 }
10301
10302 let indent_size = indent_size_occurrences
10303 .into_iter()
10304 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10305 .map(|(indent, _)| indent)
10306 .unwrap_or_default();
10307 let row = rows_by_indent_size[&indent_size][0];
10308 let indent_end = Point::new(row, indent_size.len);
10309
10310 (indent_size, indent_end)
10311 };
10312
10313 let mut line_prefix = indent_size.chars().collect::<String>();
10314
10315 let mut inside_comment = false;
10316 if let Some(comment_prefix) =
10317 buffer
10318 .language_scope_at(selection.head())
10319 .and_then(|language| {
10320 language
10321 .line_comment_prefixes()
10322 .iter()
10323 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10324 .cloned()
10325 })
10326 {
10327 line_prefix.push_str(&comment_prefix);
10328 inside_comment = true;
10329 }
10330
10331 let language_settings = buffer.language_settings_at(selection.head(), cx);
10332 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10333 RewrapBehavior::InComments => inside_comment,
10334 RewrapBehavior::InSelections => !selection.is_empty(),
10335 RewrapBehavior::Anywhere => true,
10336 };
10337
10338 let should_rewrap = options.override_language_settings
10339 || allow_rewrap_based_on_language
10340 || self.hard_wrap.is_some();
10341 if !should_rewrap {
10342 continue;
10343 }
10344
10345 if selection.is_empty() {
10346 'expand_upwards: while start_row > 0 {
10347 let prev_row = start_row - 1;
10348 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10349 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10350 {
10351 start_row = prev_row;
10352 } else {
10353 break 'expand_upwards;
10354 }
10355 }
10356
10357 'expand_downwards: while end_row < buffer.max_point().row {
10358 let next_row = end_row + 1;
10359 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10360 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10361 {
10362 end_row = next_row;
10363 } else {
10364 break 'expand_downwards;
10365 }
10366 }
10367 }
10368
10369 let start = Point::new(start_row, 0);
10370 let start_offset = start.to_offset(&buffer);
10371 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10372 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10373 let Some(lines_without_prefixes) = selection_text
10374 .lines()
10375 .map(|line| {
10376 line.strip_prefix(&line_prefix)
10377 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10378 .ok_or_else(|| {
10379 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10380 })
10381 })
10382 .collect::<Result<Vec<_>, _>>()
10383 .log_err()
10384 else {
10385 continue;
10386 };
10387
10388 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10389 buffer
10390 .language_settings_at(Point::new(start_row, 0), cx)
10391 .preferred_line_length as usize
10392 });
10393 let wrapped_text = wrap_with_prefix(
10394 line_prefix,
10395 lines_without_prefixes.join("\n"),
10396 wrap_column,
10397 tab_size,
10398 options.preserve_existing_whitespace,
10399 );
10400
10401 // TODO: should always use char-based diff while still supporting cursor behavior that
10402 // matches vim.
10403 let mut diff_options = DiffOptions::default();
10404 if options.override_language_settings {
10405 diff_options.max_word_diff_len = 0;
10406 diff_options.max_word_diff_line_count = 0;
10407 } else {
10408 diff_options.max_word_diff_len = usize::MAX;
10409 diff_options.max_word_diff_line_count = usize::MAX;
10410 }
10411
10412 for (old_range, new_text) in
10413 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10414 {
10415 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10416 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10417 edits.push((edit_start..edit_end, new_text));
10418 }
10419
10420 rewrapped_row_ranges.push(start_row..=end_row);
10421 }
10422
10423 self.buffer
10424 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10425 }
10426
10427 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10428 let mut text = String::new();
10429 let buffer = self.buffer.read(cx).snapshot(cx);
10430 let mut selections = self.selections.all::<Point>(cx);
10431 let mut clipboard_selections = Vec::with_capacity(selections.len());
10432 {
10433 let max_point = buffer.max_point();
10434 let mut is_first = true;
10435 for selection in &mut selections {
10436 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10437 if is_entire_line {
10438 selection.start = Point::new(selection.start.row, 0);
10439 if !selection.is_empty() && selection.end.column == 0 {
10440 selection.end = cmp::min(max_point, selection.end);
10441 } else {
10442 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10443 }
10444 selection.goal = SelectionGoal::None;
10445 }
10446 if is_first {
10447 is_first = false;
10448 } else {
10449 text += "\n";
10450 }
10451 let mut len = 0;
10452 for chunk in buffer.text_for_range(selection.start..selection.end) {
10453 text.push_str(chunk);
10454 len += chunk.len();
10455 }
10456 clipboard_selections.push(ClipboardSelection {
10457 len,
10458 is_entire_line,
10459 first_line_indent: buffer
10460 .indent_size_for_line(MultiBufferRow(selection.start.row))
10461 .len,
10462 });
10463 }
10464 }
10465
10466 self.transact(window, cx, |this, window, cx| {
10467 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10468 s.select(selections);
10469 });
10470 this.insert("", window, cx);
10471 });
10472 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10473 }
10474
10475 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10476 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10477 let item = self.cut_common(window, cx);
10478 cx.write_to_clipboard(item);
10479 }
10480
10481 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10482 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10483 self.change_selections(None, window, cx, |s| {
10484 s.move_with(|snapshot, sel| {
10485 if sel.is_empty() {
10486 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10487 }
10488 });
10489 });
10490 let item = self.cut_common(window, cx);
10491 cx.set_global(KillRing(item))
10492 }
10493
10494 pub fn kill_ring_yank(
10495 &mut self,
10496 _: &KillRingYank,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10501 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10502 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10503 (kill_ring.text().to_string(), kill_ring.metadata_json())
10504 } else {
10505 return;
10506 }
10507 } else {
10508 return;
10509 };
10510 self.do_paste(&text, metadata, false, window, cx);
10511 }
10512
10513 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10514 self.do_copy(true, cx);
10515 }
10516
10517 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10518 self.do_copy(false, cx);
10519 }
10520
10521 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10522 let selections = self.selections.all::<Point>(cx);
10523 let buffer = self.buffer.read(cx).read(cx);
10524 let mut text = String::new();
10525
10526 let mut clipboard_selections = Vec::with_capacity(selections.len());
10527 {
10528 let max_point = buffer.max_point();
10529 let mut is_first = true;
10530 for selection in &selections {
10531 let mut start = selection.start;
10532 let mut end = selection.end;
10533 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10534 if is_entire_line {
10535 start = Point::new(start.row, 0);
10536 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10537 }
10538
10539 let mut trimmed_selections = Vec::new();
10540 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10541 let row = MultiBufferRow(start.row);
10542 let first_indent = buffer.indent_size_for_line(row);
10543 if first_indent.len == 0 || start.column > first_indent.len {
10544 trimmed_selections.push(start..end);
10545 } else {
10546 trimmed_selections.push(
10547 Point::new(row.0, first_indent.len)
10548 ..Point::new(row.0, buffer.line_len(row)),
10549 );
10550 for row in start.row + 1..=end.row {
10551 let mut line_len = buffer.line_len(MultiBufferRow(row));
10552 if row == end.row {
10553 line_len = end.column;
10554 }
10555 if line_len == 0 {
10556 trimmed_selections
10557 .push(Point::new(row, 0)..Point::new(row, line_len));
10558 continue;
10559 }
10560 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10561 if row_indent_size.len >= first_indent.len {
10562 trimmed_selections.push(
10563 Point::new(row, first_indent.len)..Point::new(row, line_len),
10564 );
10565 } else {
10566 trimmed_selections.clear();
10567 trimmed_selections.push(start..end);
10568 break;
10569 }
10570 }
10571 }
10572 } else {
10573 trimmed_selections.push(start..end);
10574 }
10575
10576 for trimmed_range in trimmed_selections {
10577 if is_first {
10578 is_first = false;
10579 } else {
10580 text += "\n";
10581 }
10582 let mut len = 0;
10583 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10584 text.push_str(chunk);
10585 len += chunk.len();
10586 }
10587 clipboard_selections.push(ClipboardSelection {
10588 len,
10589 is_entire_line,
10590 first_line_indent: buffer
10591 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10592 .len,
10593 });
10594 }
10595 }
10596 }
10597
10598 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10599 text,
10600 clipboard_selections,
10601 ));
10602 }
10603
10604 pub fn do_paste(
10605 &mut self,
10606 text: &String,
10607 clipboard_selections: Option<Vec<ClipboardSelection>>,
10608 handle_entire_lines: bool,
10609 window: &mut Window,
10610 cx: &mut Context<Self>,
10611 ) {
10612 if self.read_only(cx) {
10613 return;
10614 }
10615
10616 let clipboard_text = Cow::Borrowed(text);
10617
10618 self.transact(window, cx, |this, window, cx| {
10619 if let Some(mut clipboard_selections) = clipboard_selections {
10620 let old_selections = this.selections.all::<usize>(cx);
10621 let all_selections_were_entire_line =
10622 clipboard_selections.iter().all(|s| s.is_entire_line);
10623 let first_selection_indent_column =
10624 clipboard_selections.first().map(|s| s.first_line_indent);
10625 if clipboard_selections.len() != old_selections.len() {
10626 clipboard_selections.drain(..);
10627 }
10628 let cursor_offset = this.selections.last::<usize>(cx).head();
10629 let mut auto_indent_on_paste = true;
10630
10631 this.buffer.update(cx, |buffer, cx| {
10632 let snapshot = buffer.read(cx);
10633 auto_indent_on_paste = snapshot
10634 .language_settings_at(cursor_offset, cx)
10635 .auto_indent_on_paste;
10636
10637 let mut start_offset = 0;
10638 let mut edits = Vec::new();
10639 let mut original_indent_columns = Vec::new();
10640 for (ix, selection) in old_selections.iter().enumerate() {
10641 let to_insert;
10642 let entire_line;
10643 let original_indent_column;
10644 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10645 let end_offset = start_offset + clipboard_selection.len;
10646 to_insert = &clipboard_text[start_offset..end_offset];
10647 entire_line = clipboard_selection.is_entire_line;
10648 start_offset = end_offset + 1;
10649 original_indent_column = Some(clipboard_selection.first_line_indent);
10650 } else {
10651 to_insert = clipboard_text.as_str();
10652 entire_line = all_selections_were_entire_line;
10653 original_indent_column = first_selection_indent_column
10654 }
10655
10656 // If the corresponding selection was empty when this slice of the
10657 // clipboard text was written, then the entire line containing the
10658 // selection was copied. If this selection is also currently empty,
10659 // then paste the line before the current line of the buffer.
10660 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10661 let column = selection.start.to_point(&snapshot).column as usize;
10662 let line_start = selection.start - column;
10663 line_start..line_start
10664 } else {
10665 selection.range()
10666 };
10667
10668 edits.push((range, to_insert));
10669 original_indent_columns.push(original_indent_column);
10670 }
10671 drop(snapshot);
10672
10673 buffer.edit(
10674 edits,
10675 if auto_indent_on_paste {
10676 Some(AutoindentMode::Block {
10677 original_indent_columns,
10678 })
10679 } else {
10680 None
10681 },
10682 cx,
10683 );
10684 });
10685
10686 let selections = this.selections.all::<usize>(cx);
10687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10688 s.select(selections)
10689 });
10690 } else {
10691 this.insert(&clipboard_text, window, cx);
10692 }
10693 });
10694 }
10695
10696 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10697 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10698 if let Some(item) = cx.read_from_clipboard() {
10699 let entries = item.entries();
10700
10701 match entries.first() {
10702 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10703 // of all the pasted entries.
10704 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10705 .do_paste(
10706 clipboard_string.text(),
10707 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10708 true,
10709 window,
10710 cx,
10711 ),
10712 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10713 }
10714 }
10715 }
10716
10717 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10718 if self.read_only(cx) {
10719 return;
10720 }
10721
10722 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10723
10724 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10725 if let Some((selections, _)) =
10726 self.selection_history.transaction(transaction_id).cloned()
10727 {
10728 self.change_selections(None, window, cx, |s| {
10729 s.select_anchors(selections.to_vec());
10730 });
10731 } else {
10732 log::error!(
10733 "No entry in selection_history found for undo. \
10734 This may correspond to a bug where undo does not update the selection. \
10735 If this is occurring, please add details to \
10736 https://github.com/zed-industries/zed/issues/22692"
10737 );
10738 }
10739 self.request_autoscroll(Autoscroll::fit(), cx);
10740 self.unmark_text(window, cx);
10741 self.refresh_inline_completion(true, false, window, cx);
10742 cx.emit(EditorEvent::Edited { transaction_id });
10743 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10744 }
10745 }
10746
10747 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10748 if self.read_only(cx) {
10749 return;
10750 }
10751
10752 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10753
10754 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10755 if let Some((_, Some(selections))) =
10756 self.selection_history.transaction(transaction_id).cloned()
10757 {
10758 self.change_selections(None, window, cx, |s| {
10759 s.select_anchors(selections.to_vec());
10760 });
10761 } else {
10762 log::error!(
10763 "No entry in selection_history found for redo. \
10764 This may correspond to a bug where undo does not update the selection. \
10765 If this is occurring, please add details to \
10766 https://github.com/zed-industries/zed/issues/22692"
10767 );
10768 }
10769 self.request_autoscroll(Autoscroll::fit(), cx);
10770 self.unmark_text(window, cx);
10771 self.refresh_inline_completion(true, false, window, cx);
10772 cx.emit(EditorEvent::Edited { transaction_id });
10773 }
10774 }
10775
10776 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10777 self.buffer
10778 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10779 }
10780
10781 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10782 self.buffer
10783 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10784 }
10785
10786 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10788 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10789 s.move_with(|map, selection| {
10790 let cursor = if selection.is_empty() {
10791 movement::left(map, selection.start)
10792 } else {
10793 selection.start
10794 };
10795 selection.collapse_to(cursor, SelectionGoal::None);
10796 });
10797 })
10798 }
10799
10800 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10801 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10803 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10804 })
10805 }
10806
10807 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10809 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10810 s.move_with(|map, selection| {
10811 let cursor = if selection.is_empty() {
10812 movement::right(map, selection.end)
10813 } else {
10814 selection.end
10815 };
10816 selection.collapse_to(cursor, SelectionGoal::None)
10817 });
10818 })
10819 }
10820
10821 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10822 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10824 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10825 })
10826 }
10827
10828 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10829 if self.take_rename(true, window, cx).is_some() {
10830 return;
10831 }
10832
10833 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10834 cx.propagate();
10835 return;
10836 }
10837
10838 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10839
10840 let text_layout_details = &self.text_layout_details(window);
10841 let selection_count = self.selections.count();
10842 let first_selection = self.selections.first_anchor();
10843
10844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10845 s.move_with(|map, selection| {
10846 if !selection.is_empty() {
10847 selection.goal = SelectionGoal::None;
10848 }
10849 let (cursor, goal) = movement::up(
10850 map,
10851 selection.start,
10852 selection.goal,
10853 false,
10854 text_layout_details,
10855 );
10856 selection.collapse_to(cursor, goal);
10857 });
10858 });
10859
10860 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10861 {
10862 cx.propagate();
10863 }
10864 }
10865
10866 pub fn move_up_by_lines(
10867 &mut self,
10868 action: &MoveUpByLines,
10869 window: &mut Window,
10870 cx: &mut Context<Self>,
10871 ) {
10872 if self.take_rename(true, window, cx).is_some() {
10873 return;
10874 }
10875
10876 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10877 cx.propagate();
10878 return;
10879 }
10880
10881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10882
10883 let text_layout_details = &self.text_layout_details(window);
10884
10885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10886 s.move_with(|map, selection| {
10887 if !selection.is_empty() {
10888 selection.goal = SelectionGoal::None;
10889 }
10890 let (cursor, goal) = movement::up_by_rows(
10891 map,
10892 selection.start,
10893 action.lines,
10894 selection.goal,
10895 false,
10896 text_layout_details,
10897 );
10898 selection.collapse_to(cursor, goal);
10899 });
10900 })
10901 }
10902
10903 pub fn move_down_by_lines(
10904 &mut self,
10905 action: &MoveDownByLines,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) {
10909 if self.take_rename(true, window, cx).is_some() {
10910 return;
10911 }
10912
10913 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10914 cx.propagate();
10915 return;
10916 }
10917
10918 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10919
10920 let text_layout_details = &self.text_layout_details(window);
10921
10922 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10923 s.move_with(|map, selection| {
10924 if !selection.is_empty() {
10925 selection.goal = SelectionGoal::None;
10926 }
10927 let (cursor, goal) = movement::down_by_rows(
10928 map,
10929 selection.start,
10930 action.lines,
10931 selection.goal,
10932 false,
10933 text_layout_details,
10934 );
10935 selection.collapse_to(cursor, goal);
10936 });
10937 })
10938 }
10939
10940 pub fn select_down_by_lines(
10941 &mut self,
10942 action: &SelectDownByLines,
10943 window: &mut Window,
10944 cx: &mut Context<Self>,
10945 ) {
10946 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10947 let text_layout_details = &self.text_layout_details(window);
10948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10949 s.move_heads_with(|map, head, goal| {
10950 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10951 })
10952 })
10953 }
10954
10955 pub fn select_up_by_lines(
10956 &mut self,
10957 action: &SelectUpByLines,
10958 window: &mut Window,
10959 cx: &mut Context<Self>,
10960 ) {
10961 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10962 let text_layout_details = &self.text_layout_details(window);
10963 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10964 s.move_heads_with(|map, head, goal| {
10965 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10966 })
10967 })
10968 }
10969
10970 pub fn select_page_up(
10971 &mut self,
10972 _: &SelectPageUp,
10973 window: &mut Window,
10974 cx: &mut Context<Self>,
10975 ) {
10976 let Some(row_count) = self.visible_row_count() else {
10977 return;
10978 };
10979
10980 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10981
10982 let text_layout_details = &self.text_layout_details(window);
10983
10984 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10985 s.move_heads_with(|map, head, goal| {
10986 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10987 })
10988 })
10989 }
10990
10991 pub fn move_page_up(
10992 &mut self,
10993 action: &MovePageUp,
10994 window: &mut Window,
10995 cx: &mut Context<Self>,
10996 ) {
10997 if self.take_rename(true, window, cx).is_some() {
10998 return;
10999 }
11000
11001 if self
11002 .context_menu
11003 .borrow_mut()
11004 .as_mut()
11005 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11006 .unwrap_or(false)
11007 {
11008 return;
11009 }
11010
11011 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11012 cx.propagate();
11013 return;
11014 }
11015
11016 let Some(row_count) = self.visible_row_count() else {
11017 return;
11018 };
11019
11020 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11021
11022 let autoscroll = if action.center_cursor {
11023 Autoscroll::center()
11024 } else {
11025 Autoscroll::fit()
11026 };
11027
11028 let text_layout_details = &self.text_layout_details(window);
11029
11030 self.change_selections(Some(autoscroll), window, cx, |s| {
11031 s.move_with(|map, selection| {
11032 if !selection.is_empty() {
11033 selection.goal = SelectionGoal::None;
11034 }
11035 let (cursor, goal) = movement::up_by_rows(
11036 map,
11037 selection.end,
11038 row_count,
11039 selection.goal,
11040 false,
11041 text_layout_details,
11042 );
11043 selection.collapse_to(cursor, goal);
11044 });
11045 });
11046 }
11047
11048 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11049 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11050 let text_layout_details = &self.text_layout_details(window);
11051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11052 s.move_heads_with(|map, head, goal| {
11053 movement::up(map, head, goal, false, text_layout_details)
11054 })
11055 })
11056 }
11057
11058 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11059 self.take_rename(true, window, cx);
11060
11061 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11062 cx.propagate();
11063 return;
11064 }
11065
11066 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11067
11068 let text_layout_details = &self.text_layout_details(window);
11069 let selection_count = self.selections.count();
11070 let first_selection = self.selections.first_anchor();
11071
11072 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11073 s.move_with(|map, selection| {
11074 if !selection.is_empty() {
11075 selection.goal = SelectionGoal::None;
11076 }
11077 let (cursor, goal) = movement::down(
11078 map,
11079 selection.end,
11080 selection.goal,
11081 false,
11082 text_layout_details,
11083 );
11084 selection.collapse_to(cursor, goal);
11085 });
11086 });
11087
11088 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11089 {
11090 cx.propagate();
11091 }
11092 }
11093
11094 pub fn select_page_down(
11095 &mut self,
11096 _: &SelectPageDown,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 let Some(row_count) = self.visible_row_count() else {
11101 return;
11102 };
11103
11104 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11105
11106 let text_layout_details = &self.text_layout_details(window);
11107
11108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11109 s.move_heads_with(|map, head, goal| {
11110 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11111 })
11112 })
11113 }
11114
11115 pub fn move_page_down(
11116 &mut self,
11117 action: &MovePageDown,
11118 window: &mut Window,
11119 cx: &mut Context<Self>,
11120 ) {
11121 if self.take_rename(true, window, cx).is_some() {
11122 return;
11123 }
11124
11125 if self
11126 .context_menu
11127 .borrow_mut()
11128 .as_mut()
11129 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11130 .unwrap_or(false)
11131 {
11132 return;
11133 }
11134
11135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11136 cx.propagate();
11137 return;
11138 }
11139
11140 let Some(row_count) = self.visible_row_count() else {
11141 return;
11142 };
11143
11144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11145
11146 let autoscroll = if action.center_cursor {
11147 Autoscroll::center()
11148 } else {
11149 Autoscroll::fit()
11150 };
11151
11152 let text_layout_details = &self.text_layout_details(window);
11153 self.change_selections(Some(autoscroll), window, cx, |s| {
11154 s.move_with(|map, selection| {
11155 if !selection.is_empty() {
11156 selection.goal = SelectionGoal::None;
11157 }
11158 let (cursor, goal) = movement::down_by_rows(
11159 map,
11160 selection.end,
11161 row_count,
11162 selection.goal,
11163 false,
11164 text_layout_details,
11165 );
11166 selection.collapse_to(cursor, goal);
11167 });
11168 });
11169 }
11170
11171 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11173 let text_layout_details = &self.text_layout_details(window);
11174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11175 s.move_heads_with(|map, head, goal| {
11176 movement::down(map, head, goal, false, text_layout_details)
11177 })
11178 });
11179 }
11180
11181 pub fn context_menu_first(
11182 &mut self,
11183 _: &ContextMenuFirst,
11184 _window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11188 context_menu.select_first(self.completion_provider.as_deref(), cx);
11189 }
11190 }
11191
11192 pub fn context_menu_prev(
11193 &mut self,
11194 _: &ContextMenuPrevious,
11195 _window: &mut Window,
11196 cx: &mut Context<Self>,
11197 ) {
11198 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11199 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11200 }
11201 }
11202
11203 pub fn context_menu_next(
11204 &mut self,
11205 _: &ContextMenuNext,
11206 _window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11210 context_menu.select_next(self.completion_provider.as_deref(), cx);
11211 }
11212 }
11213
11214 pub fn context_menu_last(
11215 &mut self,
11216 _: &ContextMenuLast,
11217 _window: &mut Window,
11218 cx: &mut Context<Self>,
11219 ) {
11220 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11221 context_menu.select_last(self.completion_provider.as_deref(), cx);
11222 }
11223 }
11224
11225 pub fn move_to_previous_word_start(
11226 &mut self,
11227 _: &MoveToPreviousWordStart,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11233 s.move_cursors_with(|map, head, _| {
11234 (
11235 movement::previous_word_start(map, head),
11236 SelectionGoal::None,
11237 )
11238 });
11239 })
11240 }
11241
11242 pub fn move_to_previous_subword_start(
11243 &mut self,
11244 _: &MoveToPreviousSubwordStart,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11250 s.move_cursors_with(|map, head, _| {
11251 (
11252 movement::previous_subword_start(map, head),
11253 SelectionGoal::None,
11254 )
11255 });
11256 })
11257 }
11258
11259 pub fn select_to_previous_word_start(
11260 &mut self,
11261 _: &SelectToPreviousWordStart,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11267 s.move_heads_with(|map, head, _| {
11268 (
11269 movement::previous_word_start(map, head),
11270 SelectionGoal::None,
11271 )
11272 });
11273 })
11274 }
11275
11276 pub fn select_to_previous_subword_start(
11277 &mut self,
11278 _: &SelectToPreviousSubwordStart,
11279 window: &mut Window,
11280 cx: &mut Context<Self>,
11281 ) {
11282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11284 s.move_heads_with(|map, head, _| {
11285 (
11286 movement::previous_subword_start(map, head),
11287 SelectionGoal::None,
11288 )
11289 });
11290 })
11291 }
11292
11293 pub fn delete_to_previous_word_start(
11294 &mut self,
11295 action: &DeleteToPreviousWordStart,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11300 self.transact(window, cx, |this, window, cx| {
11301 this.select_autoclose_pair(window, cx);
11302 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11303 s.move_with(|map, selection| {
11304 if selection.is_empty() {
11305 let cursor = if action.ignore_newlines {
11306 movement::previous_word_start(map, selection.head())
11307 } else {
11308 movement::previous_word_start_or_newline(map, selection.head())
11309 };
11310 selection.set_head(cursor, SelectionGoal::None);
11311 }
11312 });
11313 });
11314 this.insert("", window, cx);
11315 });
11316 }
11317
11318 pub fn delete_to_previous_subword_start(
11319 &mut self,
11320 _: &DeleteToPreviousSubwordStart,
11321 window: &mut Window,
11322 cx: &mut Context<Self>,
11323 ) {
11324 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11325 self.transact(window, cx, |this, window, cx| {
11326 this.select_autoclose_pair(window, cx);
11327 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11328 s.move_with(|map, selection| {
11329 if selection.is_empty() {
11330 let cursor = movement::previous_subword_start(map, selection.head());
11331 selection.set_head(cursor, SelectionGoal::None);
11332 }
11333 });
11334 });
11335 this.insert("", window, cx);
11336 });
11337 }
11338
11339 pub fn move_to_next_word_end(
11340 &mut self,
11341 _: &MoveToNextWordEnd,
11342 window: &mut Window,
11343 cx: &mut Context<Self>,
11344 ) {
11345 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11347 s.move_cursors_with(|map, head, _| {
11348 (movement::next_word_end(map, head), SelectionGoal::None)
11349 });
11350 })
11351 }
11352
11353 pub fn move_to_next_subword_end(
11354 &mut self,
11355 _: &MoveToNextSubwordEnd,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11360 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11361 s.move_cursors_with(|map, head, _| {
11362 (movement::next_subword_end(map, head), SelectionGoal::None)
11363 });
11364 })
11365 }
11366
11367 pub fn select_to_next_word_end(
11368 &mut self,
11369 _: &SelectToNextWordEnd,
11370 window: &mut Window,
11371 cx: &mut Context<Self>,
11372 ) {
11373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11374 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11375 s.move_heads_with(|map, head, _| {
11376 (movement::next_word_end(map, head), SelectionGoal::None)
11377 });
11378 })
11379 }
11380
11381 pub fn select_to_next_subword_end(
11382 &mut self,
11383 _: &SelectToNextSubwordEnd,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11388 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11389 s.move_heads_with(|map, head, _| {
11390 (movement::next_subword_end(map, head), SelectionGoal::None)
11391 });
11392 })
11393 }
11394
11395 pub fn delete_to_next_word_end(
11396 &mut self,
11397 action: &DeleteToNextWordEnd,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11402 self.transact(window, cx, |this, window, cx| {
11403 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11404 s.move_with(|map, selection| {
11405 if selection.is_empty() {
11406 let cursor = if action.ignore_newlines {
11407 movement::next_word_end(map, selection.head())
11408 } else {
11409 movement::next_word_end_or_newline(map, selection.head())
11410 };
11411 selection.set_head(cursor, SelectionGoal::None);
11412 }
11413 });
11414 });
11415 this.insert("", window, cx);
11416 });
11417 }
11418
11419 pub fn delete_to_next_subword_end(
11420 &mut self,
11421 _: &DeleteToNextSubwordEnd,
11422 window: &mut Window,
11423 cx: &mut Context<Self>,
11424 ) {
11425 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11426 self.transact(window, cx, |this, window, cx| {
11427 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11428 s.move_with(|map, selection| {
11429 if selection.is_empty() {
11430 let cursor = movement::next_subword_end(map, selection.head());
11431 selection.set_head(cursor, SelectionGoal::None);
11432 }
11433 });
11434 });
11435 this.insert("", window, cx);
11436 });
11437 }
11438
11439 pub fn move_to_beginning_of_line(
11440 &mut self,
11441 action: &MoveToBeginningOfLine,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11446 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11447 s.move_cursors_with(|map, head, _| {
11448 (
11449 movement::indented_line_beginning(
11450 map,
11451 head,
11452 action.stop_at_soft_wraps,
11453 action.stop_at_indent,
11454 ),
11455 SelectionGoal::None,
11456 )
11457 });
11458 })
11459 }
11460
11461 pub fn select_to_beginning_of_line(
11462 &mut self,
11463 action: &SelectToBeginningOfLine,
11464 window: &mut Window,
11465 cx: &mut Context<Self>,
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::indented_line_beginning(
11472 map,
11473 head,
11474 action.stop_at_soft_wraps,
11475 action.stop_at_indent,
11476 ),
11477 SelectionGoal::None,
11478 )
11479 });
11480 });
11481 }
11482
11483 pub fn delete_to_beginning_of_line(
11484 &mut self,
11485 action: &DeleteToBeginningOfLine,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11490 self.transact(window, cx, |this, window, cx| {
11491 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11492 s.move_with(|_, selection| {
11493 selection.reversed = true;
11494 });
11495 });
11496
11497 this.select_to_beginning_of_line(
11498 &SelectToBeginningOfLine {
11499 stop_at_soft_wraps: false,
11500 stop_at_indent: action.stop_at_indent,
11501 },
11502 window,
11503 cx,
11504 );
11505 this.backspace(&Backspace, window, cx);
11506 });
11507 }
11508
11509 pub fn move_to_end_of_line(
11510 &mut self,
11511 action: &MoveToEndOfLine,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) {
11515 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11517 s.move_cursors_with(|map, head, _| {
11518 (
11519 movement::line_end(map, head, action.stop_at_soft_wraps),
11520 SelectionGoal::None,
11521 )
11522 });
11523 })
11524 }
11525
11526 pub fn select_to_end_of_line(
11527 &mut self,
11528 action: &SelectToEndOfLine,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11533 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11534 s.move_heads_with(|map, head, _| {
11535 (
11536 movement::line_end(map, head, action.stop_at_soft_wraps),
11537 SelectionGoal::None,
11538 )
11539 });
11540 })
11541 }
11542
11543 pub fn delete_to_end_of_line(
11544 &mut self,
11545 _: &DeleteToEndOfLine,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11550 self.transact(window, cx, |this, window, cx| {
11551 this.select_to_end_of_line(
11552 &SelectToEndOfLine {
11553 stop_at_soft_wraps: false,
11554 },
11555 window,
11556 cx,
11557 );
11558 this.delete(&Delete, window, cx);
11559 });
11560 }
11561
11562 pub fn cut_to_end_of_line(
11563 &mut self,
11564 _: &CutToEndOfLine,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11569 self.transact(window, cx, |this, window, cx| {
11570 this.select_to_end_of_line(
11571 &SelectToEndOfLine {
11572 stop_at_soft_wraps: false,
11573 },
11574 window,
11575 cx,
11576 );
11577 this.cut(&Cut, window, cx);
11578 });
11579 }
11580
11581 pub fn move_to_start_of_paragraph(
11582 &mut self,
11583 _: &MoveToStartOfParagraph,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11588 cx.propagate();
11589 return;
11590 }
11591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11593 s.move_with(|map, selection| {
11594 selection.collapse_to(
11595 movement::start_of_paragraph(map, selection.head(), 1),
11596 SelectionGoal::None,
11597 )
11598 });
11599 })
11600 }
11601
11602 pub fn move_to_end_of_paragraph(
11603 &mut self,
11604 _: &MoveToEndOfParagraph,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11609 cx.propagate();
11610 return;
11611 }
11612 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11613 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11614 s.move_with(|map, selection| {
11615 selection.collapse_to(
11616 movement::end_of_paragraph(map, selection.head(), 1),
11617 SelectionGoal::None,
11618 )
11619 });
11620 })
11621 }
11622
11623 pub fn select_to_start_of_paragraph(
11624 &mut self,
11625 _: &SelectToStartOfParagraph,
11626 window: &mut Window,
11627 cx: &mut Context<Self>,
11628 ) {
11629 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11630 cx.propagate();
11631 return;
11632 }
11633 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11635 s.move_heads_with(|map, head, _| {
11636 (
11637 movement::start_of_paragraph(map, head, 1),
11638 SelectionGoal::None,
11639 )
11640 });
11641 })
11642 }
11643
11644 pub fn select_to_end_of_paragraph(
11645 &mut self,
11646 _: &SelectToEndOfParagraph,
11647 window: &mut Window,
11648 cx: &mut Context<Self>,
11649 ) {
11650 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11651 cx.propagate();
11652 return;
11653 }
11654 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11655 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11656 s.move_heads_with(|map, head, _| {
11657 (
11658 movement::end_of_paragraph(map, head, 1),
11659 SelectionGoal::None,
11660 )
11661 });
11662 })
11663 }
11664
11665 pub fn move_to_start_of_excerpt(
11666 &mut self,
11667 _: &MoveToStartOfExcerpt,
11668 window: &mut Window,
11669 cx: &mut Context<Self>,
11670 ) {
11671 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11672 cx.propagate();
11673 return;
11674 }
11675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11677 s.move_with(|map, selection| {
11678 selection.collapse_to(
11679 movement::start_of_excerpt(
11680 map,
11681 selection.head(),
11682 workspace::searchable::Direction::Prev,
11683 ),
11684 SelectionGoal::None,
11685 )
11686 });
11687 })
11688 }
11689
11690 pub fn move_to_start_of_next_excerpt(
11691 &mut self,
11692 _: &MoveToStartOfNextExcerpt,
11693 window: &mut Window,
11694 cx: &mut Context<Self>,
11695 ) {
11696 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11697 cx.propagate();
11698 return;
11699 }
11700
11701 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11702 s.move_with(|map, selection| {
11703 selection.collapse_to(
11704 movement::start_of_excerpt(
11705 map,
11706 selection.head(),
11707 workspace::searchable::Direction::Next,
11708 ),
11709 SelectionGoal::None,
11710 )
11711 });
11712 })
11713 }
11714
11715 pub fn move_to_end_of_excerpt(
11716 &mut self,
11717 _: &MoveToEndOfExcerpt,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11722 cx.propagate();
11723 return;
11724 }
11725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11726 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11727 s.move_with(|map, selection| {
11728 selection.collapse_to(
11729 movement::end_of_excerpt(
11730 map,
11731 selection.head(),
11732 workspace::searchable::Direction::Next,
11733 ),
11734 SelectionGoal::None,
11735 )
11736 });
11737 })
11738 }
11739
11740 pub fn move_to_end_of_previous_excerpt(
11741 &mut self,
11742 _: &MoveToEndOfPreviousExcerpt,
11743 window: &mut Window,
11744 cx: &mut Context<Self>,
11745 ) {
11746 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11747 cx.propagate();
11748 return;
11749 }
11750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11751 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11752 s.move_with(|map, selection| {
11753 selection.collapse_to(
11754 movement::end_of_excerpt(
11755 map,
11756 selection.head(),
11757 workspace::searchable::Direction::Prev,
11758 ),
11759 SelectionGoal::None,
11760 )
11761 });
11762 })
11763 }
11764
11765 pub fn select_to_start_of_excerpt(
11766 &mut self,
11767 _: &SelectToStartOfExcerpt,
11768 window: &mut Window,
11769 cx: &mut Context<Self>,
11770 ) {
11771 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11772 cx.propagate();
11773 return;
11774 }
11775 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11777 s.move_heads_with(|map, head, _| {
11778 (
11779 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11780 SelectionGoal::None,
11781 )
11782 });
11783 })
11784 }
11785
11786 pub fn select_to_start_of_next_excerpt(
11787 &mut self,
11788 _: &SelectToStartOfNextExcerpt,
11789 window: &mut Window,
11790 cx: &mut Context<Self>,
11791 ) {
11792 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11793 cx.propagate();
11794 return;
11795 }
11796 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11798 s.move_heads_with(|map, head, _| {
11799 (
11800 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11801 SelectionGoal::None,
11802 )
11803 });
11804 })
11805 }
11806
11807 pub fn select_to_end_of_excerpt(
11808 &mut self,
11809 _: &SelectToEndOfExcerpt,
11810 window: &mut Window,
11811 cx: &mut Context<Self>,
11812 ) {
11813 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11814 cx.propagate();
11815 return;
11816 }
11817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11819 s.move_heads_with(|map, head, _| {
11820 (
11821 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11822 SelectionGoal::None,
11823 )
11824 });
11825 })
11826 }
11827
11828 pub fn select_to_end_of_previous_excerpt(
11829 &mut self,
11830 _: &SelectToEndOfPreviousExcerpt,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) {
11834 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11835 cx.propagate();
11836 return;
11837 }
11838 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11839 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11840 s.move_heads_with(|map, head, _| {
11841 (
11842 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11843 SelectionGoal::None,
11844 )
11845 });
11846 })
11847 }
11848
11849 pub fn move_to_beginning(
11850 &mut self,
11851 _: &MoveToBeginning,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11856 cx.propagate();
11857 return;
11858 }
11859 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11860 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11861 s.select_ranges(vec![0..0]);
11862 });
11863 }
11864
11865 pub fn select_to_beginning(
11866 &mut self,
11867 _: &SelectToBeginning,
11868 window: &mut Window,
11869 cx: &mut Context<Self>,
11870 ) {
11871 let mut selection = self.selections.last::<Point>(cx);
11872 selection.set_head(Point::zero(), SelectionGoal::None);
11873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11874 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11875 s.select(vec![selection]);
11876 });
11877 }
11878
11879 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11880 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11881 cx.propagate();
11882 return;
11883 }
11884 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11885 let cursor = self.buffer.read(cx).read(cx).len();
11886 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11887 s.select_ranges(vec![cursor..cursor])
11888 });
11889 }
11890
11891 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11892 self.nav_history = nav_history;
11893 }
11894
11895 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11896 self.nav_history.as_ref()
11897 }
11898
11899 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11900 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11901 }
11902
11903 fn push_to_nav_history(
11904 &mut self,
11905 cursor_anchor: Anchor,
11906 new_position: Option<Point>,
11907 is_deactivate: bool,
11908 cx: &mut Context<Self>,
11909 ) {
11910 if let Some(nav_history) = self.nav_history.as_mut() {
11911 let buffer = self.buffer.read(cx).read(cx);
11912 let cursor_position = cursor_anchor.to_point(&buffer);
11913 let scroll_state = self.scroll_manager.anchor();
11914 let scroll_top_row = scroll_state.top_row(&buffer);
11915 drop(buffer);
11916
11917 if let Some(new_position) = new_position {
11918 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11919 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11920 return;
11921 }
11922 }
11923
11924 nav_history.push(
11925 Some(NavigationData {
11926 cursor_anchor,
11927 cursor_position,
11928 scroll_anchor: scroll_state,
11929 scroll_top_row,
11930 }),
11931 cx,
11932 );
11933 cx.emit(EditorEvent::PushedToNavHistory {
11934 anchor: cursor_anchor,
11935 is_deactivate,
11936 })
11937 }
11938 }
11939
11940 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11941 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11942 let buffer = self.buffer.read(cx).snapshot(cx);
11943 let mut selection = self.selections.first::<usize>(cx);
11944 selection.set_head(buffer.len(), SelectionGoal::None);
11945 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11946 s.select(vec![selection]);
11947 });
11948 }
11949
11950 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 let end = self.buffer.read(cx).read(cx).len();
11953 self.change_selections(None, window, cx, |s| {
11954 s.select_ranges(vec![0..end]);
11955 });
11956 }
11957
11958 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11959 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11960 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11961 let mut selections = self.selections.all::<Point>(cx);
11962 let max_point = display_map.buffer_snapshot.max_point();
11963 for selection in &mut selections {
11964 let rows = selection.spanned_rows(true, &display_map);
11965 selection.start = Point::new(rows.start.0, 0);
11966 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11967 selection.reversed = false;
11968 }
11969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11970 s.select(selections);
11971 });
11972 }
11973
11974 pub fn split_selection_into_lines(
11975 &mut self,
11976 _: &SplitSelectionIntoLines,
11977 window: &mut Window,
11978 cx: &mut Context<Self>,
11979 ) {
11980 let selections = self
11981 .selections
11982 .all::<Point>(cx)
11983 .into_iter()
11984 .map(|selection| selection.start..selection.end)
11985 .collect::<Vec<_>>();
11986 self.unfold_ranges(&selections, true, true, cx);
11987
11988 let mut new_selection_ranges = Vec::new();
11989 {
11990 let buffer = self.buffer.read(cx).read(cx);
11991 for selection in selections {
11992 for row in selection.start.row..selection.end.row {
11993 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11994 new_selection_ranges.push(cursor..cursor);
11995 }
11996
11997 let is_multiline_selection = selection.start.row != selection.end.row;
11998 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11999 // so this action feels more ergonomic when paired with other selection operations
12000 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12001 if !should_skip_last {
12002 new_selection_ranges.push(selection.end..selection.end);
12003 }
12004 }
12005 }
12006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12007 s.select_ranges(new_selection_ranges);
12008 });
12009 }
12010
12011 pub fn add_selection_above(
12012 &mut self,
12013 _: &AddSelectionAbove,
12014 window: &mut Window,
12015 cx: &mut Context<Self>,
12016 ) {
12017 self.add_selection(true, window, cx);
12018 }
12019
12020 pub fn add_selection_below(
12021 &mut self,
12022 _: &AddSelectionBelow,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 self.add_selection(false, window, cx);
12027 }
12028
12029 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12030 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12031
12032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12033 let mut selections = self.selections.all::<Point>(cx);
12034 let text_layout_details = self.text_layout_details(window);
12035 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12036 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12037 let range = oldest_selection.display_range(&display_map).sorted();
12038
12039 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12040 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12041 let positions = start_x.min(end_x)..start_x.max(end_x);
12042
12043 selections.clear();
12044 let mut stack = Vec::new();
12045 for row in range.start.row().0..=range.end.row().0 {
12046 if let Some(selection) = self.selections.build_columnar_selection(
12047 &display_map,
12048 DisplayRow(row),
12049 &positions,
12050 oldest_selection.reversed,
12051 &text_layout_details,
12052 ) {
12053 stack.push(selection.id);
12054 selections.push(selection);
12055 }
12056 }
12057
12058 if above {
12059 stack.reverse();
12060 }
12061
12062 AddSelectionsState { above, stack }
12063 });
12064
12065 let last_added_selection = *state.stack.last().unwrap();
12066 let mut new_selections = Vec::new();
12067 if above == state.above {
12068 let end_row = if above {
12069 DisplayRow(0)
12070 } else {
12071 display_map.max_point().row()
12072 };
12073
12074 'outer: for selection in selections {
12075 if selection.id == last_added_selection {
12076 let range = selection.display_range(&display_map).sorted();
12077 debug_assert_eq!(range.start.row(), range.end.row());
12078 let mut row = range.start.row();
12079 let positions =
12080 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12081 px(start)..px(end)
12082 } else {
12083 let start_x =
12084 display_map.x_for_display_point(range.start, &text_layout_details);
12085 let end_x =
12086 display_map.x_for_display_point(range.end, &text_layout_details);
12087 start_x.min(end_x)..start_x.max(end_x)
12088 };
12089
12090 while row != end_row {
12091 if above {
12092 row.0 -= 1;
12093 } else {
12094 row.0 += 1;
12095 }
12096
12097 if let Some(new_selection) = self.selections.build_columnar_selection(
12098 &display_map,
12099 row,
12100 &positions,
12101 selection.reversed,
12102 &text_layout_details,
12103 ) {
12104 state.stack.push(new_selection.id);
12105 if above {
12106 new_selections.push(new_selection);
12107 new_selections.push(selection);
12108 } else {
12109 new_selections.push(selection);
12110 new_selections.push(new_selection);
12111 }
12112
12113 continue 'outer;
12114 }
12115 }
12116 }
12117
12118 new_selections.push(selection);
12119 }
12120 } else {
12121 new_selections = selections;
12122 new_selections.retain(|s| s.id != last_added_selection);
12123 state.stack.pop();
12124 }
12125
12126 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12127 s.select(new_selections);
12128 });
12129 if state.stack.len() > 1 {
12130 self.add_selections_state = Some(state);
12131 }
12132 }
12133
12134 pub fn select_next_match_internal(
12135 &mut self,
12136 display_map: &DisplaySnapshot,
12137 replace_newest: bool,
12138 autoscroll: Option<Autoscroll>,
12139 window: &mut Window,
12140 cx: &mut Context<Self>,
12141 ) -> Result<()> {
12142 fn select_next_match_ranges(
12143 this: &mut Editor,
12144 range: Range<usize>,
12145 reversed: bool,
12146 replace_newest: bool,
12147 auto_scroll: Option<Autoscroll>,
12148 window: &mut Window,
12149 cx: &mut Context<Editor>,
12150 ) {
12151 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12152 this.change_selections(auto_scroll, window, cx, |s| {
12153 if replace_newest {
12154 s.delete(s.newest_anchor().id);
12155 }
12156 if reversed {
12157 s.insert_range(range.end..range.start);
12158 } else {
12159 s.insert_range(range);
12160 }
12161 });
12162 }
12163
12164 let buffer = &display_map.buffer_snapshot;
12165 let mut selections = self.selections.all::<usize>(cx);
12166 if let Some(mut select_next_state) = self.select_next_state.take() {
12167 let query = &select_next_state.query;
12168 if !select_next_state.done {
12169 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12170 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12171 let mut next_selected_range = None;
12172
12173 let bytes_after_last_selection =
12174 buffer.bytes_in_range(last_selection.end..buffer.len());
12175 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12176 let query_matches = query
12177 .stream_find_iter(bytes_after_last_selection)
12178 .map(|result| (last_selection.end, result))
12179 .chain(
12180 query
12181 .stream_find_iter(bytes_before_first_selection)
12182 .map(|result| (0, result)),
12183 );
12184
12185 for (start_offset, query_match) in query_matches {
12186 let query_match = query_match.unwrap(); // can only fail due to I/O
12187 let offset_range =
12188 start_offset + query_match.start()..start_offset + query_match.end();
12189 let display_range = offset_range.start.to_display_point(display_map)
12190 ..offset_range.end.to_display_point(display_map);
12191
12192 if !select_next_state.wordwise
12193 || (!movement::is_inside_word(display_map, display_range.start)
12194 && !movement::is_inside_word(display_map, display_range.end))
12195 {
12196 // TODO: This is n^2, because we might check all the selections
12197 if !selections
12198 .iter()
12199 .any(|selection| selection.range().overlaps(&offset_range))
12200 {
12201 next_selected_range = Some(offset_range);
12202 break;
12203 }
12204 }
12205 }
12206
12207 if let Some(next_selected_range) = next_selected_range {
12208 select_next_match_ranges(
12209 self,
12210 next_selected_range,
12211 last_selection.reversed,
12212 replace_newest,
12213 autoscroll,
12214 window,
12215 cx,
12216 );
12217 } else {
12218 select_next_state.done = true;
12219 }
12220 }
12221
12222 self.select_next_state = Some(select_next_state);
12223 } else {
12224 let mut only_carets = true;
12225 let mut same_text_selected = true;
12226 let mut selected_text = None;
12227
12228 let mut selections_iter = selections.iter().peekable();
12229 while let Some(selection) = selections_iter.next() {
12230 if selection.start != selection.end {
12231 only_carets = false;
12232 }
12233
12234 if same_text_selected {
12235 if selected_text.is_none() {
12236 selected_text =
12237 Some(buffer.text_for_range(selection.range()).collect::<String>());
12238 }
12239
12240 if let Some(next_selection) = selections_iter.peek() {
12241 if next_selection.range().len() == selection.range().len() {
12242 let next_selected_text = buffer
12243 .text_for_range(next_selection.range())
12244 .collect::<String>();
12245 if Some(next_selected_text) != selected_text {
12246 same_text_selected = false;
12247 selected_text = None;
12248 }
12249 } else {
12250 same_text_selected = false;
12251 selected_text = None;
12252 }
12253 }
12254 }
12255 }
12256
12257 if only_carets {
12258 for selection in &mut selections {
12259 let word_range = movement::surrounding_word(
12260 display_map,
12261 selection.start.to_display_point(display_map),
12262 );
12263 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12264 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12265 selection.goal = SelectionGoal::None;
12266 selection.reversed = false;
12267 select_next_match_ranges(
12268 self,
12269 selection.start..selection.end,
12270 selection.reversed,
12271 replace_newest,
12272 autoscroll,
12273 window,
12274 cx,
12275 );
12276 }
12277
12278 if selections.len() == 1 {
12279 let selection = selections
12280 .last()
12281 .expect("ensured that there's only one selection");
12282 let query = buffer
12283 .text_for_range(selection.start..selection.end)
12284 .collect::<String>();
12285 let is_empty = query.is_empty();
12286 let select_state = SelectNextState {
12287 query: AhoCorasick::new(&[query])?,
12288 wordwise: true,
12289 done: is_empty,
12290 };
12291 self.select_next_state = Some(select_state);
12292 } else {
12293 self.select_next_state = None;
12294 }
12295 } else if let Some(selected_text) = selected_text {
12296 self.select_next_state = Some(SelectNextState {
12297 query: AhoCorasick::new(&[selected_text])?,
12298 wordwise: false,
12299 done: false,
12300 });
12301 self.select_next_match_internal(
12302 display_map,
12303 replace_newest,
12304 autoscroll,
12305 window,
12306 cx,
12307 )?;
12308 }
12309 }
12310 Ok(())
12311 }
12312
12313 pub fn select_all_matches(
12314 &mut self,
12315 _action: &SelectAllMatches,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) -> Result<()> {
12319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12320
12321 self.push_to_selection_history();
12322 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12323
12324 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12325 let Some(select_next_state) = self.select_next_state.as_mut() else {
12326 return Ok(());
12327 };
12328 if select_next_state.done {
12329 return Ok(());
12330 }
12331
12332 let mut new_selections = Vec::new();
12333
12334 let reversed = self.selections.oldest::<usize>(cx).reversed;
12335 let buffer = &display_map.buffer_snapshot;
12336 let query_matches = select_next_state
12337 .query
12338 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12339
12340 for query_match in query_matches.into_iter() {
12341 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12342 let offset_range = if reversed {
12343 query_match.end()..query_match.start()
12344 } else {
12345 query_match.start()..query_match.end()
12346 };
12347 let display_range = offset_range.start.to_display_point(&display_map)
12348 ..offset_range.end.to_display_point(&display_map);
12349
12350 if !select_next_state.wordwise
12351 || (!movement::is_inside_word(&display_map, display_range.start)
12352 && !movement::is_inside_word(&display_map, display_range.end))
12353 {
12354 new_selections.push(offset_range.start..offset_range.end);
12355 }
12356 }
12357
12358 select_next_state.done = true;
12359 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12360 self.change_selections(None, window, cx, |selections| {
12361 selections.select_ranges(new_selections)
12362 });
12363
12364 Ok(())
12365 }
12366
12367 pub fn select_next(
12368 &mut self,
12369 action: &SelectNext,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) -> Result<()> {
12373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12374 self.push_to_selection_history();
12375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12376 self.select_next_match_internal(
12377 &display_map,
12378 action.replace_newest,
12379 Some(Autoscroll::newest()),
12380 window,
12381 cx,
12382 )?;
12383 Ok(())
12384 }
12385
12386 pub fn select_previous(
12387 &mut self,
12388 action: &SelectPrevious,
12389 window: &mut Window,
12390 cx: &mut Context<Self>,
12391 ) -> Result<()> {
12392 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12393 self.push_to_selection_history();
12394 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12395 let buffer = &display_map.buffer_snapshot;
12396 let mut selections = self.selections.all::<usize>(cx);
12397 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12398 let query = &select_prev_state.query;
12399 if !select_prev_state.done {
12400 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12401 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12402 let mut next_selected_range = None;
12403 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12404 let bytes_before_last_selection =
12405 buffer.reversed_bytes_in_range(0..last_selection.start);
12406 let bytes_after_first_selection =
12407 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12408 let query_matches = query
12409 .stream_find_iter(bytes_before_last_selection)
12410 .map(|result| (last_selection.start, result))
12411 .chain(
12412 query
12413 .stream_find_iter(bytes_after_first_selection)
12414 .map(|result| (buffer.len(), result)),
12415 );
12416 for (end_offset, query_match) in query_matches {
12417 let query_match = query_match.unwrap(); // can only fail due to I/O
12418 let offset_range =
12419 end_offset - query_match.end()..end_offset - query_match.start();
12420 let display_range = offset_range.start.to_display_point(&display_map)
12421 ..offset_range.end.to_display_point(&display_map);
12422
12423 if !select_prev_state.wordwise
12424 || (!movement::is_inside_word(&display_map, display_range.start)
12425 && !movement::is_inside_word(&display_map, display_range.end))
12426 {
12427 next_selected_range = Some(offset_range);
12428 break;
12429 }
12430 }
12431
12432 if let Some(next_selected_range) = next_selected_range {
12433 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12434 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12435 if action.replace_newest {
12436 s.delete(s.newest_anchor().id);
12437 }
12438 if last_selection.reversed {
12439 s.insert_range(next_selected_range.end..next_selected_range.start);
12440 } else {
12441 s.insert_range(next_selected_range);
12442 }
12443 });
12444 } else {
12445 select_prev_state.done = true;
12446 }
12447 }
12448
12449 self.select_prev_state = Some(select_prev_state);
12450 } else {
12451 let mut only_carets = true;
12452 let mut same_text_selected = true;
12453 let mut selected_text = None;
12454
12455 let mut selections_iter = selections.iter().peekable();
12456 while let Some(selection) = selections_iter.next() {
12457 if selection.start != selection.end {
12458 only_carets = false;
12459 }
12460
12461 if same_text_selected {
12462 if selected_text.is_none() {
12463 selected_text =
12464 Some(buffer.text_for_range(selection.range()).collect::<String>());
12465 }
12466
12467 if let Some(next_selection) = selections_iter.peek() {
12468 if next_selection.range().len() == selection.range().len() {
12469 let next_selected_text = buffer
12470 .text_for_range(next_selection.range())
12471 .collect::<String>();
12472 if Some(next_selected_text) != selected_text {
12473 same_text_selected = false;
12474 selected_text = None;
12475 }
12476 } else {
12477 same_text_selected = false;
12478 selected_text = None;
12479 }
12480 }
12481 }
12482 }
12483
12484 if only_carets {
12485 for selection in &mut selections {
12486 let word_range = movement::surrounding_word(
12487 &display_map,
12488 selection.start.to_display_point(&display_map),
12489 );
12490 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12491 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12492 selection.goal = SelectionGoal::None;
12493 selection.reversed = false;
12494 }
12495 if selections.len() == 1 {
12496 let selection = selections
12497 .last()
12498 .expect("ensured that there's only one selection");
12499 let query = buffer
12500 .text_for_range(selection.start..selection.end)
12501 .collect::<String>();
12502 let is_empty = query.is_empty();
12503 let select_state = SelectNextState {
12504 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12505 wordwise: true,
12506 done: is_empty,
12507 };
12508 self.select_prev_state = Some(select_state);
12509 } else {
12510 self.select_prev_state = None;
12511 }
12512
12513 self.unfold_ranges(
12514 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12515 false,
12516 true,
12517 cx,
12518 );
12519 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12520 s.select(selections);
12521 });
12522 } else if let Some(selected_text) = selected_text {
12523 self.select_prev_state = Some(SelectNextState {
12524 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12525 wordwise: false,
12526 done: false,
12527 });
12528 self.select_previous(action, window, cx)?;
12529 }
12530 }
12531 Ok(())
12532 }
12533
12534 pub fn find_next_match(
12535 &mut self,
12536 _: &FindNextMatch,
12537 window: &mut Window,
12538 cx: &mut Context<Self>,
12539 ) -> Result<()> {
12540 let selections = self.selections.disjoint_anchors();
12541 match selections.first() {
12542 Some(first) if selections.len() >= 2 => {
12543 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12544 s.select_ranges([first.range()]);
12545 });
12546 }
12547 _ => self.select_next(
12548 &SelectNext {
12549 replace_newest: true,
12550 },
12551 window,
12552 cx,
12553 )?,
12554 }
12555 Ok(())
12556 }
12557
12558 pub fn find_previous_match(
12559 &mut self,
12560 _: &FindPreviousMatch,
12561 window: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) -> Result<()> {
12564 let selections = self.selections.disjoint_anchors();
12565 match selections.last() {
12566 Some(last) if selections.len() >= 2 => {
12567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12568 s.select_ranges([last.range()]);
12569 });
12570 }
12571 _ => self.select_previous(
12572 &SelectPrevious {
12573 replace_newest: true,
12574 },
12575 window,
12576 cx,
12577 )?,
12578 }
12579 Ok(())
12580 }
12581
12582 pub fn toggle_comments(
12583 &mut self,
12584 action: &ToggleComments,
12585 window: &mut Window,
12586 cx: &mut Context<Self>,
12587 ) {
12588 if self.read_only(cx) {
12589 return;
12590 }
12591 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.transact(window, cx, |this, window, cx| {
12594 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12595 let mut edits = Vec::new();
12596 let mut selection_edit_ranges = Vec::new();
12597 let mut last_toggled_row = None;
12598 let snapshot = this.buffer.read(cx).read(cx);
12599 let empty_str: Arc<str> = Arc::default();
12600 let mut suffixes_inserted = Vec::new();
12601 let ignore_indent = action.ignore_indent;
12602
12603 fn comment_prefix_range(
12604 snapshot: &MultiBufferSnapshot,
12605 row: MultiBufferRow,
12606 comment_prefix: &str,
12607 comment_prefix_whitespace: &str,
12608 ignore_indent: bool,
12609 ) -> Range<Point> {
12610 let indent_size = if ignore_indent {
12611 0
12612 } else {
12613 snapshot.indent_size_for_line(row).len
12614 };
12615
12616 let start = Point::new(row.0, indent_size);
12617
12618 let mut line_bytes = snapshot
12619 .bytes_in_range(start..snapshot.max_point())
12620 .flatten()
12621 .copied();
12622
12623 // If this line currently begins with the line comment prefix, then record
12624 // the range containing the prefix.
12625 if line_bytes
12626 .by_ref()
12627 .take(comment_prefix.len())
12628 .eq(comment_prefix.bytes())
12629 {
12630 // Include any whitespace that matches the comment prefix.
12631 let matching_whitespace_len = line_bytes
12632 .zip(comment_prefix_whitespace.bytes())
12633 .take_while(|(a, b)| a == b)
12634 .count() as u32;
12635 let end = Point::new(
12636 start.row,
12637 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12638 );
12639 start..end
12640 } else {
12641 start..start
12642 }
12643 }
12644
12645 fn comment_suffix_range(
12646 snapshot: &MultiBufferSnapshot,
12647 row: MultiBufferRow,
12648 comment_suffix: &str,
12649 comment_suffix_has_leading_space: bool,
12650 ) -> Range<Point> {
12651 let end = Point::new(row.0, snapshot.line_len(row));
12652 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12653
12654 let mut line_end_bytes = snapshot
12655 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12656 .flatten()
12657 .copied();
12658
12659 let leading_space_len = if suffix_start_column > 0
12660 && line_end_bytes.next() == Some(b' ')
12661 && comment_suffix_has_leading_space
12662 {
12663 1
12664 } else {
12665 0
12666 };
12667
12668 // If this line currently begins with the line comment prefix, then record
12669 // the range containing the prefix.
12670 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12671 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12672 start..end
12673 } else {
12674 end..end
12675 }
12676 }
12677
12678 // TODO: Handle selections that cross excerpts
12679 for selection in &mut selections {
12680 let start_column = snapshot
12681 .indent_size_for_line(MultiBufferRow(selection.start.row))
12682 .len;
12683 let language = if let Some(language) =
12684 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12685 {
12686 language
12687 } else {
12688 continue;
12689 };
12690
12691 selection_edit_ranges.clear();
12692
12693 // If multiple selections contain a given row, avoid processing that
12694 // row more than once.
12695 let mut start_row = MultiBufferRow(selection.start.row);
12696 if last_toggled_row == Some(start_row) {
12697 start_row = start_row.next_row();
12698 }
12699 let end_row =
12700 if selection.end.row > selection.start.row && selection.end.column == 0 {
12701 MultiBufferRow(selection.end.row - 1)
12702 } else {
12703 MultiBufferRow(selection.end.row)
12704 };
12705 last_toggled_row = Some(end_row);
12706
12707 if start_row > end_row {
12708 continue;
12709 }
12710
12711 // If the language has line comments, toggle those.
12712 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12713
12714 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12715 if ignore_indent {
12716 full_comment_prefixes = full_comment_prefixes
12717 .into_iter()
12718 .map(|s| Arc::from(s.trim_end()))
12719 .collect();
12720 }
12721
12722 if !full_comment_prefixes.is_empty() {
12723 let first_prefix = full_comment_prefixes
12724 .first()
12725 .expect("prefixes is non-empty");
12726 let prefix_trimmed_lengths = full_comment_prefixes
12727 .iter()
12728 .map(|p| p.trim_end_matches(' ').len())
12729 .collect::<SmallVec<[usize; 4]>>();
12730
12731 let mut all_selection_lines_are_comments = true;
12732
12733 for row in start_row.0..=end_row.0 {
12734 let row = MultiBufferRow(row);
12735 if start_row < end_row && snapshot.is_line_blank(row) {
12736 continue;
12737 }
12738
12739 let prefix_range = full_comment_prefixes
12740 .iter()
12741 .zip(prefix_trimmed_lengths.iter().copied())
12742 .map(|(prefix, trimmed_prefix_len)| {
12743 comment_prefix_range(
12744 snapshot.deref(),
12745 row,
12746 &prefix[..trimmed_prefix_len],
12747 &prefix[trimmed_prefix_len..],
12748 ignore_indent,
12749 )
12750 })
12751 .max_by_key(|range| range.end.column - range.start.column)
12752 .expect("prefixes is non-empty");
12753
12754 if prefix_range.is_empty() {
12755 all_selection_lines_are_comments = false;
12756 }
12757
12758 selection_edit_ranges.push(prefix_range);
12759 }
12760
12761 if all_selection_lines_are_comments {
12762 edits.extend(
12763 selection_edit_ranges
12764 .iter()
12765 .cloned()
12766 .map(|range| (range, empty_str.clone())),
12767 );
12768 } else {
12769 let min_column = selection_edit_ranges
12770 .iter()
12771 .map(|range| range.start.column)
12772 .min()
12773 .unwrap_or(0);
12774 edits.extend(selection_edit_ranges.iter().map(|range| {
12775 let position = Point::new(range.start.row, min_column);
12776 (position..position, first_prefix.clone())
12777 }));
12778 }
12779 } else if let Some((full_comment_prefix, comment_suffix)) =
12780 language.block_comment_delimiters()
12781 {
12782 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12783 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12784 let prefix_range = comment_prefix_range(
12785 snapshot.deref(),
12786 start_row,
12787 comment_prefix,
12788 comment_prefix_whitespace,
12789 ignore_indent,
12790 );
12791 let suffix_range = comment_suffix_range(
12792 snapshot.deref(),
12793 end_row,
12794 comment_suffix.trim_start_matches(' '),
12795 comment_suffix.starts_with(' '),
12796 );
12797
12798 if prefix_range.is_empty() || suffix_range.is_empty() {
12799 edits.push((
12800 prefix_range.start..prefix_range.start,
12801 full_comment_prefix.clone(),
12802 ));
12803 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12804 suffixes_inserted.push((end_row, comment_suffix.len()));
12805 } else {
12806 edits.push((prefix_range, empty_str.clone()));
12807 edits.push((suffix_range, empty_str.clone()));
12808 }
12809 } else {
12810 continue;
12811 }
12812 }
12813
12814 drop(snapshot);
12815 this.buffer.update(cx, |buffer, cx| {
12816 buffer.edit(edits, None, cx);
12817 });
12818
12819 // Adjust selections so that they end before any comment suffixes that
12820 // were inserted.
12821 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12822 let mut selections = this.selections.all::<Point>(cx);
12823 let snapshot = this.buffer.read(cx).read(cx);
12824 for selection in &mut selections {
12825 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12826 match row.cmp(&MultiBufferRow(selection.end.row)) {
12827 Ordering::Less => {
12828 suffixes_inserted.next();
12829 continue;
12830 }
12831 Ordering::Greater => break,
12832 Ordering::Equal => {
12833 if selection.end.column == snapshot.line_len(row) {
12834 if selection.is_empty() {
12835 selection.start.column -= suffix_len as u32;
12836 }
12837 selection.end.column -= suffix_len as u32;
12838 }
12839 break;
12840 }
12841 }
12842 }
12843 }
12844
12845 drop(snapshot);
12846 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12847 s.select(selections)
12848 });
12849
12850 let selections = this.selections.all::<Point>(cx);
12851 let selections_on_single_row = selections.windows(2).all(|selections| {
12852 selections[0].start.row == selections[1].start.row
12853 && selections[0].end.row == selections[1].end.row
12854 && selections[0].start.row == selections[0].end.row
12855 });
12856 let selections_selecting = selections
12857 .iter()
12858 .any(|selection| selection.start != selection.end);
12859 let advance_downwards = action.advance_downwards
12860 && selections_on_single_row
12861 && !selections_selecting
12862 && !matches!(this.mode, EditorMode::SingleLine { .. });
12863
12864 if advance_downwards {
12865 let snapshot = this.buffer.read(cx).snapshot(cx);
12866
12867 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12868 s.move_cursors_with(|display_snapshot, display_point, _| {
12869 let mut point = display_point.to_point(display_snapshot);
12870 point.row += 1;
12871 point = snapshot.clip_point(point, Bias::Left);
12872 let display_point = point.to_display_point(display_snapshot);
12873 let goal = SelectionGoal::HorizontalPosition(
12874 display_snapshot
12875 .x_for_display_point(display_point, text_layout_details)
12876 .into(),
12877 );
12878 (display_point, goal)
12879 })
12880 });
12881 }
12882 });
12883 }
12884
12885 pub fn select_enclosing_symbol(
12886 &mut self,
12887 _: &SelectEnclosingSymbol,
12888 window: &mut Window,
12889 cx: &mut Context<Self>,
12890 ) {
12891 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12892
12893 let buffer = self.buffer.read(cx).snapshot(cx);
12894 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12895
12896 fn update_selection(
12897 selection: &Selection<usize>,
12898 buffer_snap: &MultiBufferSnapshot,
12899 ) -> Option<Selection<usize>> {
12900 let cursor = selection.head();
12901 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12902 for symbol in symbols.iter().rev() {
12903 let start = symbol.range.start.to_offset(buffer_snap);
12904 let end = symbol.range.end.to_offset(buffer_snap);
12905 let new_range = start..end;
12906 if start < selection.start || end > selection.end {
12907 return Some(Selection {
12908 id: selection.id,
12909 start: new_range.start,
12910 end: new_range.end,
12911 goal: SelectionGoal::None,
12912 reversed: selection.reversed,
12913 });
12914 }
12915 }
12916 None
12917 }
12918
12919 let mut selected_larger_symbol = false;
12920 let new_selections = old_selections
12921 .iter()
12922 .map(|selection| match update_selection(selection, &buffer) {
12923 Some(new_selection) => {
12924 if new_selection.range() != selection.range() {
12925 selected_larger_symbol = true;
12926 }
12927 new_selection
12928 }
12929 None => selection.clone(),
12930 })
12931 .collect::<Vec<_>>();
12932
12933 if selected_larger_symbol {
12934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12935 s.select(new_selections);
12936 });
12937 }
12938 }
12939
12940 pub fn select_larger_syntax_node(
12941 &mut self,
12942 _: &SelectLargerSyntaxNode,
12943 window: &mut Window,
12944 cx: &mut Context<Self>,
12945 ) {
12946 let Some(visible_row_count) = self.visible_row_count() else {
12947 return;
12948 };
12949 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12950 if old_selections.is_empty() {
12951 return;
12952 }
12953
12954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12955
12956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12957 let buffer = self.buffer.read(cx).snapshot(cx);
12958
12959 let mut selected_larger_node = false;
12960 let mut new_selections = old_selections
12961 .iter()
12962 .map(|selection| {
12963 let old_range = selection.start..selection.end;
12964
12965 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12966 // manually select word at selection
12967 if ["string_content", "inline"].contains(&node.kind()) {
12968 let word_range = {
12969 let display_point = buffer
12970 .offset_to_point(old_range.start)
12971 .to_display_point(&display_map);
12972 let Range { start, end } =
12973 movement::surrounding_word(&display_map, display_point);
12974 start.to_point(&display_map).to_offset(&buffer)
12975 ..end.to_point(&display_map).to_offset(&buffer)
12976 };
12977 // ignore if word is already selected
12978 if !word_range.is_empty() && old_range != word_range {
12979 let last_word_range = {
12980 let display_point = buffer
12981 .offset_to_point(old_range.end)
12982 .to_display_point(&display_map);
12983 let Range { start, end } =
12984 movement::surrounding_word(&display_map, display_point);
12985 start.to_point(&display_map).to_offset(&buffer)
12986 ..end.to_point(&display_map).to_offset(&buffer)
12987 };
12988 // only select word if start and end point belongs to same word
12989 if word_range == last_word_range {
12990 selected_larger_node = true;
12991 return Selection {
12992 id: selection.id,
12993 start: word_range.start,
12994 end: word_range.end,
12995 goal: SelectionGoal::None,
12996 reversed: selection.reversed,
12997 };
12998 }
12999 }
13000 }
13001 }
13002
13003 let mut new_range = old_range.clone();
13004 let mut new_node = None;
13005 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
13006 {
13007 new_node = Some(node);
13008 new_range = match containing_range {
13009 MultiOrSingleBufferOffsetRange::Single(_) => break,
13010 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13011 };
13012 if !display_map.intersects_fold(new_range.start)
13013 && !display_map.intersects_fold(new_range.end)
13014 {
13015 break;
13016 }
13017 }
13018
13019 if let Some(node) = new_node {
13020 // Log the ancestor, to support using this action as a way to explore TreeSitter
13021 // nodes. Parent and grandparent are also logged because this operation will not
13022 // visit nodes that have the same range as their parent.
13023 log::info!("Node: {node:?}");
13024 let parent = node.parent();
13025 log::info!("Parent: {parent:?}");
13026 let grandparent = parent.and_then(|x| x.parent());
13027 log::info!("Grandparent: {grandparent:?}");
13028 }
13029
13030 selected_larger_node |= new_range != old_range;
13031 Selection {
13032 id: selection.id,
13033 start: new_range.start,
13034 end: new_range.end,
13035 goal: SelectionGoal::None,
13036 reversed: selection.reversed,
13037 }
13038 })
13039 .collect::<Vec<_>>();
13040
13041 if !selected_larger_node {
13042 return; // don't put this call in the history
13043 }
13044
13045 // scroll based on transformation done to the last selection created by the user
13046 let (last_old, last_new) = old_selections
13047 .last()
13048 .zip(new_selections.last().cloned())
13049 .expect("old_selections isn't empty");
13050
13051 // revert selection
13052 let is_selection_reversed = {
13053 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13054 new_selections.last_mut().expect("checked above").reversed =
13055 should_newest_selection_be_reversed;
13056 should_newest_selection_be_reversed
13057 };
13058
13059 if selected_larger_node {
13060 self.select_syntax_node_history.disable_clearing = true;
13061 self.change_selections(None, window, cx, |s| {
13062 s.select(new_selections.clone());
13063 });
13064 self.select_syntax_node_history.disable_clearing = false;
13065 }
13066
13067 let start_row = last_new.start.to_display_point(&display_map).row().0;
13068 let end_row = last_new.end.to_display_point(&display_map).row().0;
13069 let selection_height = end_row - start_row + 1;
13070 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13071
13072 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13073 let scroll_behavior = if fits_on_the_screen {
13074 self.request_autoscroll(Autoscroll::fit(), cx);
13075 SelectSyntaxNodeScrollBehavior::FitSelection
13076 } else if is_selection_reversed {
13077 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13078 SelectSyntaxNodeScrollBehavior::CursorTop
13079 } else {
13080 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13081 SelectSyntaxNodeScrollBehavior::CursorBottom
13082 };
13083
13084 self.select_syntax_node_history.push((
13085 old_selections,
13086 scroll_behavior,
13087 is_selection_reversed,
13088 ));
13089 }
13090
13091 pub fn select_smaller_syntax_node(
13092 &mut self,
13093 _: &SelectSmallerSyntaxNode,
13094 window: &mut Window,
13095 cx: &mut Context<Self>,
13096 ) {
13097 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13098
13099 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13100 self.select_syntax_node_history.pop()
13101 {
13102 if let Some(selection) = selections.last_mut() {
13103 selection.reversed = is_selection_reversed;
13104 }
13105
13106 self.select_syntax_node_history.disable_clearing = true;
13107 self.change_selections(None, window, cx, |s| {
13108 s.select(selections.to_vec());
13109 });
13110 self.select_syntax_node_history.disable_clearing = false;
13111
13112 match scroll_behavior {
13113 SelectSyntaxNodeScrollBehavior::CursorTop => {
13114 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13115 }
13116 SelectSyntaxNodeScrollBehavior::FitSelection => {
13117 self.request_autoscroll(Autoscroll::fit(), cx);
13118 }
13119 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13120 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13121 }
13122 }
13123 }
13124 }
13125
13126 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13127 if !EditorSettings::get_global(cx).gutter.runnables {
13128 self.clear_tasks();
13129 return Task::ready(());
13130 }
13131 let project = self.project.as_ref().map(Entity::downgrade);
13132 let task_sources = self.lsp_task_sources(cx);
13133 cx.spawn_in(window, async move |editor, cx| {
13134 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13135 let Some(project) = project.and_then(|p| p.upgrade()) else {
13136 return;
13137 };
13138 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13139 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13140 }) else {
13141 return;
13142 };
13143
13144 let hide_runnables = project
13145 .update(cx, |project, cx| {
13146 // Do not display any test indicators in non-dev server remote projects.
13147 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13148 })
13149 .unwrap_or(true);
13150 if hide_runnables {
13151 return;
13152 }
13153 let new_rows =
13154 cx.background_spawn({
13155 let snapshot = display_snapshot.clone();
13156 async move {
13157 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13158 }
13159 })
13160 .await;
13161 let Ok(lsp_tasks) =
13162 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13163 else {
13164 return;
13165 };
13166 let lsp_tasks = lsp_tasks.await;
13167
13168 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13169 lsp_tasks
13170 .into_iter()
13171 .flat_map(|(kind, tasks)| {
13172 tasks.into_iter().filter_map(move |(location, task)| {
13173 Some((kind.clone(), location?, task))
13174 })
13175 })
13176 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13177 let buffer = location.target.buffer;
13178 let buffer_snapshot = buffer.read(cx).snapshot();
13179 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13180 |(excerpt_id, snapshot, _)| {
13181 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13182 display_snapshot
13183 .buffer_snapshot
13184 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13185 } else {
13186 None
13187 }
13188 },
13189 );
13190 if let Some(offset) = offset {
13191 let task_buffer_range =
13192 location.target.range.to_point(&buffer_snapshot);
13193 let context_buffer_range =
13194 task_buffer_range.to_offset(&buffer_snapshot);
13195 let context_range = BufferOffset(context_buffer_range.start)
13196 ..BufferOffset(context_buffer_range.end);
13197
13198 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13199 .or_insert_with(|| RunnableTasks {
13200 templates: Vec::new(),
13201 offset,
13202 column: task_buffer_range.start.column,
13203 extra_variables: HashMap::default(),
13204 context_range,
13205 })
13206 .templates
13207 .push((kind, task.original_task().clone()));
13208 }
13209
13210 acc
13211 })
13212 }) else {
13213 return;
13214 };
13215
13216 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13217 editor
13218 .update(cx, |editor, _| {
13219 editor.clear_tasks();
13220 for (key, mut value) in rows {
13221 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13222 value.templates.extend(lsp_tasks.templates);
13223 }
13224
13225 editor.insert_tasks(key, value);
13226 }
13227 for (key, value) in lsp_tasks_by_rows {
13228 editor.insert_tasks(key, value);
13229 }
13230 })
13231 .ok();
13232 })
13233 }
13234 fn fetch_runnable_ranges(
13235 snapshot: &DisplaySnapshot,
13236 range: Range<Anchor>,
13237 ) -> Vec<language::RunnableRange> {
13238 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13239 }
13240
13241 fn runnable_rows(
13242 project: Entity<Project>,
13243 snapshot: DisplaySnapshot,
13244 runnable_ranges: Vec<RunnableRange>,
13245 mut cx: AsyncWindowContext,
13246 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13247 runnable_ranges
13248 .into_iter()
13249 .filter_map(|mut runnable| {
13250 let tasks = cx
13251 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13252 .ok()?;
13253 if tasks.is_empty() {
13254 return None;
13255 }
13256
13257 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13258
13259 let row = snapshot
13260 .buffer_snapshot
13261 .buffer_line_for_row(MultiBufferRow(point.row))?
13262 .1
13263 .start
13264 .row;
13265
13266 let context_range =
13267 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13268 Some((
13269 (runnable.buffer_id, row),
13270 RunnableTasks {
13271 templates: tasks,
13272 offset: snapshot
13273 .buffer_snapshot
13274 .anchor_before(runnable.run_range.start),
13275 context_range,
13276 column: point.column,
13277 extra_variables: runnable.extra_captures,
13278 },
13279 ))
13280 })
13281 .collect()
13282 }
13283
13284 fn templates_with_tags(
13285 project: &Entity<Project>,
13286 runnable: &mut Runnable,
13287 cx: &mut App,
13288 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13289 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13290 let (worktree_id, file) = project
13291 .buffer_for_id(runnable.buffer, cx)
13292 .and_then(|buffer| buffer.read(cx).file())
13293 .map(|file| (file.worktree_id(cx), file.clone()))
13294 .unzip();
13295
13296 (
13297 project.task_store().read(cx).task_inventory().cloned(),
13298 worktree_id,
13299 file,
13300 )
13301 });
13302
13303 let mut templates_with_tags = mem::take(&mut runnable.tags)
13304 .into_iter()
13305 .flat_map(|RunnableTag(tag)| {
13306 inventory
13307 .as_ref()
13308 .into_iter()
13309 .flat_map(|inventory| {
13310 inventory.read(cx).list_tasks(
13311 file.clone(),
13312 Some(runnable.language.clone()),
13313 worktree_id,
13314 cx,
13315 )
13316 })
13317 .filter(move |(_, template)| {
13318 template.tags.iter().any(|source_tag| source_tag == &tag)
13319 })
13320 })
13321 .sorted_by_key(|(kind, _)| kind.to_owned())
13322 .collect::<Vec<_>>();
13323 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13324 // Strongest source wins; if we have worktree tag binding, prefer that to
13325 // global and language bindings;
13326 // if we have a global binding, prefer that to language binding.
13327 let first_mismatch = templates_with_tags
13328 .iter()
13329 .position(|(tag_source, _)| tag_source != leading_tag_source);
13330 if let Some(index) = first_mismatch {
13331 templates_with_tags.truncate(index);
13332 }
13333 }
13334
13335 templates_with_tags
13336 }
13337
13338 pub fn move_to_enclosing_bracket(
13339 &mut self,
13340 _: &MoveToEnclosingBracket,
13341 window: &mut Window,
13342 cx: &mut Context<Self>,
13343 ) {
13344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13346 s.move_offsets_with(|snapshot, selection| {
13347 let Some(enclosing_bracket_ranges) =
13348 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13349 else {
13350 return;
13351 };
13352
13353 let mut best_length = usize::MAX;
13354 let mut best_inside = false;
13355 let mut best_in_bracket_range = false;
13356 let mut best_destination = None;
13357 for (open, close) in enclosing_bracket_ranges {
13358 let close = close.to_inclusive();
13359 let length = close.end() - open.start;
13360 let inside = selection.start >= open.end && selection.end <= *close.start();
13361 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13362 || close.contains(&selection.head());
13363
13364 // If best is next to a bracket and current isn't, skip
13365 if !in_bracket_range && best_in_bracket_range {
13366 continue;
13367 }
13368
13369 // Prefer smaller lengths unless best is inside and current isn't
13370 if length > best_length && (best_inside || !inside) {
13371 continue;
13372 }
13373
13374 best_length = length;
13375 best_inside = inside;
13376 best_in_bracket_range = in_bracket_range;
13377 best_destination = Some(
13378 if close.contains(&selection.start) && close.contains(&selection.end) {
13379 if inside { open.end } else { open.start }
13380 } else if inside {
13381 *close.start()
13382 } else {
13383 *close.end()
13384 },
13385 );
13386 }
13387
13388 if let Some(destination) = best_destination {
13389 selection.collapse_to(destination, SelectionGoal::None);
13390 }
13391 })
13392 });
13393 }
13394
13395 pub fn undo_selection(
13396 &mut self,
13397 _: &UndoSelection,
13398 window: &mut Window,
13399 cx: &mut Context<Self>,
13400 ) {
13401 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13402 self.end_selection(window, cx);
13403 self.selection_history.mode = SelectionHistoryMode::Undoing;
13404 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13405 self.change_selections(None, window, cx, |s| {
13406 s.select_anchors(entry.selections.to_vec())
13407 });
13408 self.select_next_state = entry.select_next_state;
13409 self.select_prev_state = entry.select_prev_state;
13410 self.add_selections_state = entry.add_selections_state;
13411 self.request_autoscroll(Autoscroll::newest(), cx);
13412 }
13413 self.selection_history.mode = SelectionHistoryMode::Normal;
13414 }
13415
13416 pub fn redo_selection(
13417 &mut self,
13418 _: &RedoSelection,
13419 window: &mut Window,
13420 cx: &mut Context<Self>,
13421 ) {
13422 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13423 self.end_selection(window, cx);
13424 self.selection_history.mode = SelectionHistoryMode::Redoing;
13425 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13426 self.change_selections(None, window, cx, |s| {
13427 s.select_anchors(entry.selections.to_vec())
13428 });
13429 self.select_next_state = entry.select_next_state;
13430 self.select_prev_state = entry.select_prev_state;
13431 self.add_selections_state = entry.add_selections_state;
13432 self.request_autoscroll(Autoscroll::newest(), cx);
13433 }
13434 self.selection_history.mode = SelectionHistoryMode::Normal;
13435 }
13436
13437 pub fn expand_excerpts(
13438 &mut self,
13439 action: &ExpandExcerpts,
13440 _: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13444 }
13445
13446 pub fn expand_excerpts_down(
13447 &mut self,
13448 action: &ExpandExcerptsDown,
13449 _: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13453 }
13454
13455 pub fn expand_excerpts_up(
13456 &mut self,
13457 action: &ExpandExcerptsUp,
13458 _: &mut Window,
13459 cx: &mut Context<Self>,
13460 ) {
13461 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13462 }
13463
13464 pub fn expand_excerpts_for_direction(
13465 &mut self,
13466 lines: u32,
13467 direction: ExpandExcerptDirection,
13468
13469 cx: &mut Context<Self>,
13470 ) {
13471 let selections = self.selections.disjoint_anchors();
13472
13473 let lines = if lines == 0 {
13474 EditorSettings::get_global(cx).expand_excerpt_lines
13475 } else {
13476 lines
13477 };
13478
13479 self.buffer.update(cx, |buffer, cx| {
13480 let snapshot = buffer.snapshot(cx);
13481 let mut excerpt_ids = selections
13482 .iter()
13483 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13484 .collect::<Vec<_>>();
13485 excerpt_ids.sort();
13486 excerpt_ids.dedup();
13487 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13488 })
13489 }
13490
13491 pub fn expand_excerpt(
13492 &mut self,
13493 excerpt: ExcerptId,
13494 direction: ExpandExcerptDirection,
13495 window: &mut Window,
13496 cx: &mut Context<Self>,
13497 ) {
13498 let current_scroll_position = self.scroll_position(cx);
13499 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13500 let mut should_scroll_up = false;
13501
13502 if direction == ExpandExcerptDirection::Down {
13503 let multi_buffer = self.buffer.read(cx);
13504 let snapshot = multi_buffer.snapshot(cx);
13505 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13506 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13507 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13508 let buffer_snapshot = buffer.read(cx).snapshot();
13509 let excerpt_end_row =
13510 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13511 let last_row = buffer_snapshot.max_point().row;
13512 let lines_below = last_row.saturating_sub(excerpt_end_row);
13513 should_scroll_up = lines_below >= lines_to_expand;
13514 }
13515 }
13516 }
13517 }
13518
13519 self.buffer.update(cx, |buffer, cx| {
13520 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13521 });
13522
13523 if should_scroll_up {
13524 let new_scroll_position =
13525 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13526 self.set_scroll_position(new_scroll_position, window, cx);
13527 }
13528 }
13529
13530 pub fn go_to_singleton_buffer_point(
13531 &mut self,
13532 point: Point,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) {
13536 self.go_to_singleton_buffer_range(point..point, window, cx);
13537 }
13538
13539 pub fn go_to_singleton_buffer_range(
13540 &mut self,
13541 range: Range<Point>,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 let multibuffer = self.buffer().read(cx);
13546 let Some(buffer) = multibuffer.as_singleton() else {
13547 return;
13548 };
13549 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13550 return;
13551 };
13552 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13553 return;
13554 };
13555 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13556 s.select_anchor_ranges([start..end])
13557 });
13558 }
13559
13560 pub fn go_to_diagnostic(
13561 &mut self,
13562 _: &GoToDiagnostic,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13567 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13568 }
13569
13570 pub fn go_to_prev_diagnostic(
13571 &mut self,
13572 _: &GoToPreviousDiagnostic,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13577 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13578 }
13579
13580 pub fn go_to_diagnostic_impl(
13581 &mut self,
13582 direction: Direction,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 let buffer = self.buffer.read(cx).snapshot(cx);
13587 let selection = self.selections.newest::<usize>(cx);
13588
13589 let mut active_group_id = None;
13590 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13591 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13592 active_group_id = Some(active_group.group_id);
13593 }
13594 }
13595
13596 fn filtered(
13597 snapshot: EditorSnapshot,
13598 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13599 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13600 diagnostics
13601 .filter(|entry| entry.range.start != entry.range.end)
13602 .filter(|entry| !entry.diagnostic.is_unnecessary)
13603 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13604 }
13605
13606 let snapshot = self.snapshot(window, cx);
13607 let before = filtered(
13608 snapshot.clone(),
13609 buffer
13610 .diagnostics_in_range(0..selection.start)
13611 .filter(|entry| entry.range.start <= selection.start),
13612 );
13613 let after = filtered(
13614 snapshot,
13615 buffer
13616 .diagnostics_in_range(selection.start..buffer.len())
13617 .filter(|entry| entry.range.start >= selection.start),
13618 );
13619
13620 let mut found: Option<DiagnosticEntry<usize>> = None;
13621 if direction == Direction::Prev {
13622 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13623 {
13624 for diagnostic in prev_diagnostics.into_iter().rev() {
13625 if diagnostic.range.start != selection.start
13626 || active_group_id
13627 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13628 {
13629 found = Some(diagnostic);
13630 break 'outer;
13631 }
13632 }
13633 }
13634 } else {
13635 for diagnostic in after.chain(before) {
13636 if diagnostic.range.start != selection.start
13637 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13638 {
13639 found = Some(diagnostic);
13640 break;
13641 }
13642 }
13643 }
13644 let Some(next_diagnostic) = found else {
13645 return;
13646 };
13647
13648 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13649 return;
13650 };
13651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13652 s.select_ranges(vec![
13653 next_diagnostic.range.start..next_diagnostic.range.start,
13654 ])
13655 });
13656 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13657 self.refresh_inline_completion(false, true, window, cx);
13658 }
13659
13660 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13661 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13662 let snapshot = self.snapshot(window, cx);
13663 let selection = self.selections.newest::<Point>(cx);
13664 self.go_to_hunk_before_or_after_position(
13665 &snapshot,
13666 selection.head(),
13667 Direction::Next,
13668 window,
13669 cx,
13670 );
13671 }
13672
13673 pub fn go_to_hunk_before_or_after_position(
13674 &mut self,
13675 snapshot: &EditorSnapshot,
13676 position: Point,
13677 direction: Direction,
13678 window: &mut Window,
13679 cx: &mut Context<Editor>,
13680 ) {
13681 let row = if direction == Direction::Next {
13682 self.hunk_after_position(snapshot, position)
13683 .map(|hunk| hunk.row_range.start)
13684 } else {
13685 self.hunk_before_position(snapshot, position)
13686 };
13687
13688 if let Some(row) = row {
13689 let destination = Point::new(row.0, 0);
13690 let autoscroll = Autoscroll::center();
13691
13692 self.unfold_ranges(&[destination..destination], false, false, cx);
13693 self.change_selections(Some(autoscroll), window, cx, |s| {
13694 s.select_ranges([destination..destination]);
13695 });
13696 }
13697 }
13698
13699 fn hunk_after_position(
13700 &mut self,
13701 snapshot: &EditorSnapshot,
13702 position: Point,
13703 ) -> Option<MultiBufferDiffHunk> {
13704 snapshot
13705 .buffer_snapshot
13706 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13707 .find(|hunk| hunk.row_range.start.0 > position.row)
13708 .or_else(|| {
13709 snapshot
13710 .buffer_snapshot
13711 .diff_hunks_in_range(Point::zero()..position)
13712 .find(|hunk| hunk.row_range.end.0 < position.row)
13713 })
13714 }
13715
13716 fn go_to_prev_hunk(
13717 &mut self,
13718 _: &GoToPreviousHunk,
13719 window: &mut Window,
13720 cx: &mut Context<Self>,
13721 ) {
13722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13723 let snapshot = self.snapshot(window, cx);
13724 let selection = self.selections.newest::<Point>(cx);
13725 self.go_to_hunk_before_or_after_position(
13726 &snapshot,
13727 selection.head(),
13728 Direction::Prev,
13729 window,
13730 cx,
13731 );
13732 }
13733
13734 fn hunk_before_position(
13735 &mut self,
13736 snapshot: &EditorSnapshot,
13737 position: Point,
13738 ) -> Option<MultiBufferRow> {
13739 snapshot
13740 .buffer_snapshot
13741 .diff_hunk_before(position)
13742 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13743 }
13744
13745 fn go_to_next_change(
13746 &mut self,
13747 _: &GoToNextChange,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 if let Some(selections) = self
13752 .change_list
13753 .next_change(1, Direction::Next)
13754 .map(|s| s.to_vec())
13755 {
13756 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13757 let map = s.display_map();
13758 s.select_display_ranges(selections.iter().map(|a| {
13759 let point = a.to_display_point(&map);
13760 point..point
13761 }))
13762 })
13763 }
13764 }
13765
13766 fn go_to_previous_change(
13767 &mut self,
13768 _: &GoToPreviousChange,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 if let Some(selections) = self
13773 .change_list
13774 .next_change(1, Direction::Prev)
13775 .map(|s| s.to_vec())
13776 {
13777 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13778 let map = s.display_map();
13779 s.select_display_ranges(selections.iter().map(|a| {
13780 let point = a.to_display_point(&map);
13781 point..point
13782 }))
13783 })
13784 }
13785 }
13786
13787 fn go_to_line<T: 'static>(
13788 &mut self,
13789 position: Anchor,
13790 highlight_color: Option<Hsla>,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 let snapshot = self.snapshot(window, cx).display_snapshot;
13795 let position = position.to_point(&snapshot.buffer_snapshot);
13796 let start = snapshot
13797 .buffer_snapshot
13798 .clip_point(Point::new(position.row, 0), Bias::Left);
13799 let end = start + Point::new(1, 0);
13800 let start = snapshot.buffer_snapshot.anchor_before(start);
13801 let end = snapshot.buffer_snapshot.anchor_before(end);
13802
13803 self.highlight_rows::<T>(
13804 start..end,
13805 highlight_color
13806 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13807 Default::default(),
13808 cx,
13809 );
13810 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13811 }
13812
13813 pub fn go_to_definition(
13814 &mut self,
13815 _: &GoToDefinition,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) -> Task<Result<Navigated>> {
13819 let definition =
13820 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13821 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13822 cx.spawn_in(window, async move |editor, cx| {
13823 if definition.await? == Navigated::Yes {
13824 return Ok(Navigated::Yes);
13825 }
13826 match fallback_strategy {
13827 GoToDefinitionFallback::None => Ok(Navigated::No),
13828 GoToDefinitionFallback::FindAllReferences => {
13829 match editor.update_in(cx, |editor, window, cx| {
13830 editor.find_all_references(&FindAllReferences, window, cx)
13831 })? {
13832 Some(references) => references.await,
13833 None => Ok(Navigated::No),
13834 }
13835 }
13836 }
13837 })
13838 }
13839
13840 pub fn go_to_declaration(
13841 &mut self,
13842 _: &GoToDeclaration,
13843 window: &mut Window,
13844 cx: &mut Context<Self>,
13845 ) -> Task<Result<Navigated>> {
13846 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13847 }
13848
13849 pub fn go_to_declaration_split(
13850 &mut self,
13851 _: &GoToDeclaration,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) -> Task<Result<Navigated>> {
13855 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13856 }
13857
13858 pub fn go_to_implementation(
13859 &mut self,
13860 _: &GoToImplementation,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) -> Task<Result<Navigated>> {
13864 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13865 }
13866
13867 pub fn go_to_implementation_split(
13868 &mut self,
13869 _: &GoToImplementationSplit,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) -> Task<Result<Navigated>> {
13873 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13874 }
13875
13876 pub fn go_to_type_definition(
13877 &mut self,
13878 _: &GoToTypeDefinition,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) -> Task<Result<Navigated>> {
13882 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13883 }
13884
13885 pub fn go_to_definition_split(
13886 &mut self,
13887 _: &GoToDefinitionSplit,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) -> Task<Result<Navigated>> {
13891 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13892 }
13893
13894 pub fn go_to_type_definition_split(
13895 &mut self,
13896 _: &GoToTypeDefinitionSplit,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) -> Task<Result<Navigated>> {
13900 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13901 }
13902
13903 fn go_to_definition_of_kind(
13904 &mut self,
13905 kind: GotoDefinitionKind,
13906 split: bool,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) -> Task<Result<Navigated>> {
13910 let Some(provider) = self.semantics_provider.clone() else {
13911 return Task::ready(Ok(Navigated::No));
13912 };
13913 let head = self.selections.newest::<usize>(cx).head();
13914 let buffer = self.buffer.read(cx);
13915 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13916 text_anchor
13917 } else {
13918 return Task::ready(Ok(Navigated::No));
13919 };
13920
13921 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13922 return Task::ready(Ok(Navigated::No));
13923 };
13924
13925 cx.spawn_in(window, async move |editor, cx| {
13926 let definitions = definitions.await?;
13927 let navigated = editor
13928 .update_in(cx, |editor, window, cx| {
13929 editor.navigate_to_hover_links(
13930 Some(kind),
13931 definitions
13932 .into_iter()
13933 .filter(|location| {
13934 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13935 })
13936 .map(HoverLink::Text)
13937 .collect::<Vec<_>>(),
13938 split,
13939 window,
13940 cx,
13941 )
13942 })?
13943 .await?;
13944 anyhow::Ok(navigated)
13945 })
13946 }
13947
13948 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13949 let selection = self.selections.newest_anchor();
13950 let head = selection.head();
13951 let tail = selection.tail();
13952
13953 let Some((buffer, start_position)) =
13954 self.buffer.read(cx).text_anchor_for_position(head, cx)
13955 else {
13956 return;
13957 };
13958
13959 let end_position = if head != tail {
13960 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13961 return;
13962 };
13963 Some(pos)
13964 } else {
13965 None
13966 };
13967
13968 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13969 let url = if let Some(end_pos) = end_position {
13970 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13971 } else {
13972 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13973 };
13974
13975 if let Some(url) = url {
13976 editor.update(cx, |_, cx| {
13977 cx.open_url(&url);
13978 })
13979 } else {
13980 Ok(())
13981 }
13982 });
13983
13984 url_finder.detach();
13985 }
13986
13987 pub fn open_selected_filename(
13988 &mut self,
13989 _: &OpenSelectedFilename,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 let Some(workspace) = self.workspace() else {
13994 return;
13995 };
13996
13997 let position = self.selections.newest_anchor().head();
13998
13999 let Some((buffer, buffer_position)) =
14000 self.buffer.read(cx).text_anchor_for_position(position, cx)
14001 else {
14002 return;
14003 };
14004
14005 let project = self.project.clone();
14006
14007 cx.spawn_in(window, async move |_, cx| {
14008 let result = find_file(&buffer, project, buffer_position, cx).await;
14009
14010 if let Some((_, path)) = result {
14011 workspace
14012 .update_in(cx, |workspace, window, cx| {
14013 workspace.open_resolved_path(path, window, cx)
14014 })?
14015 .await?;
14016 }
14017 anyhow::Ok(())
14018 })
14019 .detach();
14020 }
14021
14022 pub(crate) fn navigate_to_hover_links(
14023 &mut self,
14024 kind: Option<GotoDefinitionKind>,
14025 mut definitions: Vec<HoverLink>,
14026 split: bool,
14027 window: &mut Window,
14028 cx: &mut Context<Editor>,
14029 ) -> Task<Result<Navigated>> {
14030 // If there is one definition, just open it directly
14031 if definitions.len() == 1 {
14032 let definition = definitions.pop().unwrap();
14033
14034 enum TargetTaskResult {
14035 Location(Option<Location>),
14036 AlreadyNavigated,
14037 }
14038
14039 let target_task = match definition {
14040 HoverLink::Text(link) => {
14041 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14042 }
14043 HoverLink::InlayHint(lsp_location, server_id) => {
14044 let computation =
14045 self.compute_target_location(lsp_location, server_id, window, cx);
14046 cx.background_spawn(async move {
14047 let location = computation.await?;
14048 Ok(TargetTaskResult::Location(location))
14049 })
14050 }
14051 HoverLink::Url(url) => {
14052 cx.open_url(&url);
14053 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14054 }
14055 HoverLink::File(path) => {
14056 if let Some(workspace) = self.workspace() {
14057 cx.spawn_in(window, async move |_, cx| {
14058 workspace
14059 .update_in(cx, |workspace, window, cx| {
14060 workspace.open_resolved_path(path, window, cx)
14061 })?
14062 .await
14063 .map(|_| TargetTaskResult::AlreadyNavigated)
14064 })
14065 } else {
14066 Task::ready(Ok(TargetTaskResult::Location(None)))
14067 }
14068 }
14069 };
14070 cx.spawn_in(window, async move |editor, cx| {
14071 let target = match target_task.await.context("target resolution task")? {
14072 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14073 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14074 TargetTaskResult::Location(Some(target)) => target,
14075 };
14076
14077 editor.update_in(cx, |editor, window, cx| {
14078 let Some(workspace) = editor.workspace() else {
14079 return Navigated::No;
14080 };
14081 let pane = workspace.read(cx).active_pane().clone();
14082
14083 let range = target.range.to_point(target.buffer.read(cx));
14084 let range = editor.range_for_match(&range);
14085 let range = collapse_multiline_range(range);
14086
14087 if !split
14088 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14089 {
14090 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14091 } else {
14092 window.defer(cx, move |window, cx| {
14093 let target_editor: Entity<Self> =
14094 workspace.update(cx, |workspace, cx| {
14095 let pane = if split {
14096 workspace.adjacent_pane(window, cx)
14097 } else {
14098 workspace.active_pane().clone()
14099 };
14100
14101 workspace.open_project_item(
14102 pane,
14103 target.buffer.clone(),
14104 true,
14105 true,
14106 window,
14107 cx,
14108 )
14109 });
14110 target_editor.update(cx, |target_editor, cx| {
14111 // When selecting a definition in a different buffer, disable the nav history
14112 // to avoid creating a history entry at the previous cursor location.
14113 pane.update(cx, |pane, _| pane.disable_history());
14114 target_editor.go_to_singleton_buffer_range(range, window, cx);
14115 pane.update(cx, |pane, _| pane.enable_history());
14116 });
14117 });
14118 }
14119 Navigated::Yes
14120 })
14121 })
14122 } else if !definitions.is_empty() {
14123 cx.spawn_in(window, async move |editor, cx| {
14124 let (title, location_tasks, workspace) = editor
14125 .update_in(cx, |editor, window, cx| {
14126 let tab_kind = match kind {
14127 Some(GotoDefinitionKind::Implementation) => "Implementations",
14128 _ => "Definitions",
14129 };
14130 let title = definitions
14131 .iter()
14132 .find_map(|definition| match definition {
14133 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14134 let buffer = origin.buffer.read(cx);
14135 format!(
14136 "{} for {}",
14137 tab_kind,
14138 buffer
14139 .text_for_range(origin.range.clone())
14140 .collect::<String>()
14141 )
14142 }),
14143 HoverLink::InlayHint(_, _) => None,
14144 HoverLink::Url(_) => None,
14145 HoverLink::File(_) => None,
14146 })
14147 .unwrap_or(tab_kind.to_string());
14148 let location_tasks = definitions
14149 .into_iter()
14150 .map(|definition| match definition {
14151 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14152 HoverLink::InlayHint(lsp_location, server_id) => editor
14153 .compute_target_location(lsp_location, server_id, window, cx),
14154 HoverLink::Url(_) => Task::ready(Ok(None)),
14155 HoverLink::File(_) => Task::ready(Ok(None)),
14156 })
14157 .collect::<Vec<_>>();
14158 (title, location_tasks, editor.workspace().clone())
14159 })
14160 .context("location tasks preparation")?;
14161
14162 let locations = future::join_all(location_tasks)
14163 .await
14164 .into_iter()
14165 .filter_map(|location| location.transpose())
14166 .collect::<Result<_>>()
14167 .context("location tasks")?;
14168
14169 let Some(workspace) = workspace else {
14170 return Ok(Navigated::No);
14171 };
14172 let opened = workspace
14173 .update_in(cx, |workspace, window, cx| {
14174 Self::open_locations_in_multibuffer(
14175 workspace,
14176 locations,
14177 title,
14178 split,
14179 MultibufferSelectionMode::First,
14180 window,
14181 cx,
14182 )
14183 })
14184 .ok();
14185
14186 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14187 })
14188 } else {
14189 Task::ready(Ok(Navigated::No))
14190 }
14191 }
14192
14193 fn compute_target_location(
14194 &self,
14195 lsp_location: lsp::Location,
14196 server_id: LanguageServerId,
14197 window: &mut Window,
14198 cx: &mut Context<Self>,
14199 ) -> Task<anyhow::Result<Option<Location>>> {
14200 let Some(project) = self.project.clone() else {
14201 return Task::ready(Ok(None));
14202 };
14203
14204 cx.spawn_in(window, async move |editor, cx| {
14205 let location_task = editor.update(cx, |_, cx| {
14206 project.update(cx, |project, cx| {
14207 let language_server_name = project
14208 .language_server_statuses(cx)
14209 .find(|(id, _)| server_id == *id)
14210 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14211 language_server_name.map(|language_server_name| {
14212 project.open_local_buffer_via_lsp(
14213 lsp_location.uri.clone(),
14214 server_id,
14215 language_server_name,
14216 cx,
14217 )
14218 })
14219 })
14220 })?;
14221 let location = match location_task {
14222 Some(task) => Some({
14223 let target_buffer_handle = task.await.context("open local buffer")?;
14224 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14225 let target_start = target_buffer
14226 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14227 let target_end = target_buffer
14228 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14229 target_buffer.anchor_after(target_start)
14230 ..target_buffer.anchor_before(target_end)
14231 })?;
14232 Location {
14233 buffer: target_buffer_handle,
14234 range,
14235 }
14236 }),
14237 None => None,
14238 };
14239 Ok(location)
14240 })
14241 }
14242
14243 pub fn find_all_references(
14244 &mut self,
14245 _: &FindAllReferences,
14246 window: &mut Window,
14247 cx: &mut Context<Self>,
14248 ) -> Option<Task<Result<Navigated>>> {
14249 let selection = self.selections.newest::<usize>(cx);
14250 let multi_buffer = self.buffer.read(cx);
14251 let head = selection.head();
14252
14253 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14254 let head_anchor = multi_buffer_snapshot.anchor_at(
14255 head,
14256 if head < selection.tail() {
14257 Bias::Right
14258 } else {
14259 Bias::Left
14260 },
14261 );
14262
14263 match self
14264 .find_all_references_task_sources
14265 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14266 {
14267 Ok(_) => {
14268 log::info!(
14269 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14270 );
14271 return None;
14272 }
14273 Err(i) => {
14274 self.find_all_references_task_sources.insert(i, head_anchor);
14275 }
14276 }
14277
14278 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14279 let workspace = self.workspace()?;
14280 let project = workspace.read(cx).project().clone();
14281 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14282 Some(cx.spawn_in(window, async move |editor, cx| {
14283 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14284 if let Ok(i) = editor
14285 .find_all_references_task_sources
14286 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14287 {
14288 editor.find_all_references_task_sources.remove(i);
14289 }
14290 });
14291
14292 let locations = references.await?;
14293 if locations.is_empty() {
14294 return anyhow::Ok(Navigated::No);
14295 }
14296
14297 workspace.update_in(cx, |workspace, window, cx| {
14298 let title = locations
14299 .first()
14300 .as_ref()
14301 .map(|location| {
14302 let buffer = location.buffer.read(cx);
14303 format!(
14304 "References to `{}`",
14305 buffer
14306 .text_for_range(location.range.clone())
14307 .collect::<String>()
14308 )
14309 })
14310 .unwrap();
14311 Self::open_locations_in_multibuffer(
14312 workspace,
14313 locations,
14314 title,
14315 false,
14316 MultibufferSelectionMode::First,
14317 window,
14318 cx,
14319 );
14320 Navigated::Yes
14321 })
14322 }))
14323 }
14324
14325 /// Opens a multibuffer with the given project locations in it
14326 pub fn open_locations_in_multibuffer(
14327 workspace: &mut Workspace,
14328 mut locations: Vec<Location>,
14329 title: String,
14330 split: bool,
14331 multibuffer_selection_mode: MultibufferSelectionMode,
14332 window: &mut Window,
14333 cx: &mut Context<Workspace>,
14334 ) {
14335 // If there are multiple definitions, open them in a multibuffer
14336 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14337 let mut locations = locations.into_iter().peekable();
14338 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14339 let capability = workspace.project().read(cx).capability();
14340
14341 let excerpt_buffer = cx.new(|cx| {
14342 let mut multibuffer = MultiBuffer::new(capability);
14343 while let Some(location) = locations.next() {
14344 let buffer = location.buffer.read(cx);
14345 let mut ranges_for_buffer = Vec::new();
14346 let range = location.range.to_point(buffer);
14347 ranges_for_buffer.push(range.clone());
14348
14349 while let Some(next_location) = locations.peek() {
14350 if next_location.buffer == location.buffer {
14351 ranges_for_buffer.push(next_location.range.to_point(buffer));
14352 locations.next();
14353 } else {
14354 break;
14355 }
14356 }
14357
14358 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14359 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14360 PathKey::for_buffer(&location.buffer, cx),
14361 location.buffer.clone(),
14362 ranges_for_buffer,
14363 DEFAULT_MULTIBUFFER_CONTEXT,
14364 cx,
14365 );
14366 ranges.extend(new_ranges)
14367 }
14368
14369 multibuffer.with_title(title)
14370 });
14371
14372 let editor = cx.new(|cx| {
14373 Editor::for_multibuffer(
14374 excerpt_buffer,
14375 Some(workspace.project().clone()),
14376 window,
14377 cx,
14378 )
14379 });
14380 editor.update(cx, |editor, cx| {
14381 match multibuffer_selection_mode {
14382 MultibufferSelectionMode::First => {
14383 if let Some(first_range) = ranges.first() {
14384 editor.change_selections(None, window, cx, |selections| {
14385 selections.clear_disjoint();
14386 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14387 });
14388 }
14389 editor.highlight_background::<Self>(
14390 &ranges,
14391 |theme| theme.editor_highlighted_line_background,
14392 cx,
14393 );
14394 }
14395 MultibufferSelectionMode::All => {
14396 editor.change_selections(None, window, cx, |selections| {
14397 selections.clear_disjoint();
14398 selections.select_anchor_ranges(ranges);
14399 });
14400 }
14401 }
14402 editor.register_buffers_with_language_servers(cx);
14403 });
14404
14405 let item = Box::new(editor);
14406 let item_id = item.item_id();
14407
14408 if split {
14409 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14410 } else {
14411 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14412 let (preview_item_id, preview_item_idx) =
14413 workspace.active_pane().update(cx, |pane, _| {
14414 (pane.preview_item_id(), pane.preview_item_idx())
14415 });
14416
14417 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14418
14419 if let Some(preview_item_id) = preview_item_id {
14420 workspace.active_pane().update(cx, |pane, cx| {
14421 pane.remove_item(preview_item_id, false, false, window, cx);
14422 });
14423 }
14424 } else {
14425 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14426 }
14427 }
14428 workspace.active_pane().update(cx, |pane, cx| {
14429 pane.set_preview_item_id(Some(item_id), cx);
14430 });
14431 }
14432
14433 pub fn rename(
14434 &mut self,
14435 _: &Rename,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) -> Option<Task<Result<()>>> {
14439 use language::ToOffset as _;
14440
14441 let provider = self.semantics_provider.clone()?;
14442 let selection = self.selections.newest_anchor().clone();
14443 let (cursor_buffer, cursor_buffer_position) = self
14444 .buffer
14445 .read(cx)
14446 .text_anchor_for_position(selection.head(), cx)?;
14447 let (tail_buffer, cursor_buffer_position_end) = self
14448 .buffer
14449 .read(cx)
14450 .text_anchor_for_position(selection.tail(), cx)?;
14451 if tail_buffer != cursor_buffer {
14452 return None;
14453 }
14454
14455 let snapshot = cursor_buffer.read(cx).snapshot();
14456 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14457 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14458 let prepare_rename = provider
14459 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14460 .unwrap_or_else(|| Task::ready(Ok(None)));
14461 drop(snapshot);
14462
14463 Some(cx.spawn_in(window, async move |this, cx| {
14464 let rename_range = if let Some(range) = prepare_rename.await? {
14465 Some(range)
14466 } else {
14467 this.update(cx, |this, cx| {
14468 let buffer = this.buffer.read(cx).snapshot(cx);
14469 let mut buffer_highlights = this
14470 .document_highlights_for_position(selection.head(), &buffer)
14471 .filter(|highlight| {
14472 highlight.start.excerpt_id == selection.head().excerpt_id
14473 && highlight.end.excerpt_id == selection.head().excerpt_id
14474 });
14475 buffer_highlights
14476 .next()
14477 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14478 })?
14479 };
14480 if let Some(rename_range) = rename_range {
14481 this.update_in(cx, |this, window, cx| {
14482 let snapshot = cursor_buffer.read(cx).snapshot();
14483 let rename_buffer_range = rename_range.to_offset(&snapshot);
14484 let cursor_offset_in_rename_range =
14485 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14486 let cursor_offset_in_rename_range_end =
14487 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14488
14489 this.take_rename(false, window, cx);
14490 let buffer = this.buffer.read(cx).read(cx);
14491 let cursor_offset = selection.head().to_offset(&buffer);
14492 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14493 let rename_end = rename_start + rename_buffer_range.len();
14494 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14495 let mut old_highlight_id = None;
14496 let old_name: Arc<str> = buffer
14497 .chunks(rename_start..rename_end, true)
14498 .map(|chunk| {
14499 if old_highlight_id.is_none() {
14500 old_highlight_id = chunk.syntax_highlight_id;
14501 }
14502 chunk.text
14503 })
14504 .collect::<String>()
14505 .into();
14506
14507 drop(buffer);
14508
14509 // Position the selection in the rename editor so that it matches the current selection.
14510 this.show_local_selections = false;
14511 let rename_editor = cx.new(|cx| {
14512 let mut editor = Editor::single_line(window, cx);
14513 editor.buffer.update(cx, |buffer, cx| {
14514 buffer.edit([(0..0, old_name.clone())], None, cx)
14515 });
14516 let rename_selection_range = match cursor_offset_in_rename_range
14517 .cmp(&cursor_offset_in_rename_range_end)
14518 {
14519 Ordering::Equal => {
14520 editor.select_all(&SelectAll, window, cx);
14521 return editor;
14522 }
14523 Ordering::Less => {
14524 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14525 }
14526 Ordering::Greater => {
14527 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14528 }
14529 };
14530 if rename_selection_range.end > old_name.len() {
14531 editor.select_all(&SelectAll, window, cx);
14532 } else {
14533 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14534 s.select_ranges([rename_selection_range]);
14535 });
14536 }
14537 editor
14538 });
14539 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14540 if e == &EditorEvent::Focused {
14541 cx.emit(EditorEvent::FocusedIn)
14542 }
14543 })
14544 .detach();
14545
14546 let write_highlights =
14547 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14548 let read_highlights =
14549 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14550 let ranges = write_highlights
14551 .iter()
14552 .flat_map(|(_, ranges)| ranges.iter())
14553 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14554 .cloned()
14555 .collect();
14556
14557 this.highlight_text::<Rename>(
14558 ranges,
14559 HighlightStyle {
14560 fade_out: Some(0.6),
14561 ..Default::default()
14562 },
14563 cx,
14564 );
14565 let rename_focus_handle = rename_editor.focus_handle(cx);
14566 window.focus(&rename_focus_handle);
14567 let block_id = this.insert_blocks(
14568 [BlockProperties {
14569 style: BlockStyle::Flex,
14570 placement: BlockPlacement::Below(range.start),
14571 height: Some(1),
14572 render: Arc::new({
14573 let rename_editor = rename_editor.clone();
14574 move |cx: &mut BlockContext| {
14575 let mut text_style = cx.editor_style.text.clone();
14576 if let Some(highlight_style) = old_highlight_id
14577 .and_then(|h| h.style(&cx.editor_style.syntax))
14578 {
14579 text_style = text_style.highlight(highlight_style);
14580 }
14581 div()
14582 .block_mouse_down()
14583 .pl(cx.anchor_x)
14584 .child(EditorElement::new(
14585 &rename_editor,
14586 EditorStyle {
14587 background: cx.theme().system().transparent,
14588 local_player: cx.editor_style.local_player,
14589 text: text_style,
14590 scrollbar_width: cx.editor_style.scrollbar_width,
14591 syntax: cx.editor_style.syntax.clone(),
14592 status: cx.editor_style.status.clone(),
14593 inlay_hints_style: HighlightStyle {
14594 font_weight: Some(FontWeight::BOLD),
14595 ..make_inlay_hints_style(cx.app)
14596 },
14597 inline_completion_styles: make_suggestion_styles(
14598 cx.app,
14599 ),
14600 ..EditorStyle::default()
14601 },
14602 ))
14603 .into_any_element()
14604 }
14605 }),
14606 priority: 0,
14607 }],
14608 Some(Autoscroll::fit()),
14609 cx,
14610 )[0];
14611 this.pending_rename = Some(RenameState {
14612 range,
14613 old_name,
14614 editor: rename_editor,
14615 block_id,
14616 });
14617 })?;
14618 }
14619
14620 Ok(())
14621 }))
14622 }
14623
14624 pub fn confirm_rename(
14625 &mut self,
14626 _: &ConfirmRename,
14627 window: &mut Window,
14628 cx: &mut Context<Self>,
14629 ) -> Option<Task<Result<()>>> {
14630 let rename = self.take_rename(false, window, cx)?;
14631 let workspace = self.workspace()?.downgrade();
14632 let (buffer, start) = self
14633 .buffer
14634 .read(cx)
14635 .text_anchor_for_position(rename.range.start, cx)?;
14636 let (end_buffer, _) = self
14637 .buffer
14638 .read(cx)
14639 .text_anchor_for_position(rename.range.end, cx)?;
14640 if buffer != end_buffer {
14641 return None;
14642 }
14643
14644 let old_name = rename.old_name;
14645 let new_name = rename.editor.read(cx).text(cx);
14646
14647 let rename = self.semantics_provider.as_ref()?.perform_rename(
14648 &buffer,
14649 start,
14650 new_name.clone(),
14651 cx,
14652 )?;
14653
14654 Some(cx.spawn_in(window, async move |editor, cx| {
14655 let project_transaction = rename.await?;
14656 Self::open_project_transaction(
14657 &editor,
14658 workspace,
14659 project_transaction,
14660 format!("Rename: {} → {}", old_name, new_name),
14661 cx,
14662 )
14663 .await?;
14664
14665 editor.update(cx, |editor, cx| {
14666 editor.refresh_document_highlights(cx);
14667 })?;
14668 Ok(())
14669 }))
14670 }
14671
14672 fn take_rename(
14673 &mut self,
14674 moving_cursor: bool,
14675 window: &mut Window,
14676 cx: &mut Context<Self>,
14677 ) -> Option<RenameState> {
14678 let rename = self.pending_rename.take()?;
14679 if rename.editor.focus_handle(cx).is_focused(window) {
14680 window.focus(&self.focus_handle);
14681 }
14682
14683 self.remove_blocks(
14684 [rename.block_id].into_iter().collect(),
14685 Some(Autoscroll::fit()),
14686 cx,
14687 );
14688 self.clear_highlights::<Rename>(cx);
14689 self.show_local_selections = true;
14690
14691 if moving_cursor {
14692 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14693 editor.selections.newest::<usize>(cx).head()
14694 });
14695
14696 // Update the selection to match the position of the selection inside
14697 // the rename editor.
14698 let snapshot = self.buffer.read(cx).read(cx);
14699 let rename_range = rename.range.to_offset(&snapshot);
14700 let cursor_in_editor = snapshot
14701 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14702 .min(rename_range.end);
14703 drop(snapshot);
14704
14705 self.change_selections(None, window, cx, |s| {
14706 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14707 });
14708 } else {
14709 self.refresh_document_highlights(cx);
14710 }
14711
14712 Some(rename)
14713 }
14714
14715 pub fn pending_rename(&self) -> Option<&RenameState> {
14716 self.pending_rename.as_ref()
14717 }
14718
14719 fn format(
14720 &mut self,
14721 _: &Format,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) -> Option<Task<Result<()>>> {
14725 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14726
14727 let project = match &self.project {
14728 Some(project) => project.clone(),
14729 None => return None,
14730 };
14731
14732 Some(self.perform_format(
14733 project,
14734 FormatTrigger::Manual,
14735 FormatTarget::Buffers,
14736 window,
14737 cx,
14738 ))
14739 }
14740
14741 fn format_selections(
14742 &mut self,
14743 _: &FormatSelections,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) -> Option<Task<Result<()>>> {
14747 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14748
14749 let project = match &self.project {
14750 Some(project) => project.clone(),
14751 None => return None,
14752 };
14753
14754 let ranges = self
14755 .selections
14756 .all_adjusted(cx)
14757 .into_iter()
14758 .map(|selection| selection.range())
14759 .collect_vec();
14760
14761 Some(self.perform_format(
14762 project,
14763 FormatTrigger::Manual,
14764 FormatTarget::Ranges(ranges),
14765 window,
14766 cx,
14767 ))
14768 }
14769
14770 fn perform_format(
14771 &mut self,
14772 project: Entity<Project>,
14773 trigger: FormatTrigger,
14774 target: FormatTarget,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) -> Task<Result<()>> {
14778 let buffer = self.buffer.clone();
14779 let (buffers, target) = match target {
14780 FormatTarget::Buffers => {
14781 let mut buffers = buffer.read(cx).all_buffers();
14782 if trigger == FormatTrigger::Save {
14783 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14784 }
14785 (buffers, LspFormatTarget::Buffers)
14786 }
14787 FormatTarget::Ranges(selection_ranges) => {
14788 let multi_buffer = buffer.read(cx);
14789 let snapshot = multi_buffer.read(cx);
14790 let mut buffers = HashSet::default();
14791 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14792 BTreeMap::new();
14793 for selection_range in selection_ranges {
14794 for (buffer, buffer_range, _) in
14795 snapshot.range_to_buffer_ranges(selection_range)
14796 {
14797 let buffer_id = buffer.remote_id();
14798 let start = buffer.anchor_before(buffer_range.start);
14799 let end = buffer.anchor_after(buffer_range.end);
14800 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14801 buffer_id_to_ranges
14802 .entry(buffer_id)
14803 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14804 .or_insert_with(|| vec![start..end]);
14805 }
14806 }
14807 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14808 }
14809 };
14810
14811 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14812 let selections_prev = transaction_id_prev
14813 .and_then(|transaction_id_prev| {
14814 // default to selections as they were after the last edit, if we have them,
14815 // instead of how they are now.
14816 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14817 // will take you back to where you made the last edit, instead of staying where you scrolled
14818 self.selection_history
14819 .transaction(transaction_id_prev)
14820 .map(|t| t.0.clone())
14821 })
14822 .unwrap_or_else(|| {
14823 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14824 self.selections.disjoint_anchors()
14825 });
14826
14827 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14828 let format = project.update(cx, |project, cx| {
14829 project.format(buffers, target, true, trigger, cx)
14830 });
14831
14832 cx.spawn_in(window, async move |editor, cx| {
14833 let transaction = futures::select_biased! {
14834 transaction = format.log_err().fuse() => transaction,
14835 () = timeout => {
14836 log::warn!("timed out waiting for formatting");
14837 None
14838 }
14839 };
14840
14841 buffer
14842 .update(cx, |buffer, cx| {
14843 if let Some(transaction) = transaction {
14844 if !buffer.is_singleton() {
14845 buffer.push_transaction(&transaction.0, cx);
14846 }
14847 }
14848 cx.notify();
14849 })
14850 .ok();
14851
14852 if let Some(transaction_id_now) =
14853 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14854 {
14855 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14856 if has_new_transaction {
14857 _ = editor.update(cx, |editor, _| {
14858 editor
14859 .selection_history
14860 .insert_transaction(transaction_id_now, selections_prev);
14861 });
14862 }
14863 }
14864
14865 Ok(())
14866 })
14867 }
14868
14869 fn organize_imports(
14870 &mut self,
14871 _: &OrganizeImports,
14872 window: &mut Window,
14873 cx: &mut Context<Self>,
14874 ) -> Option<Task<Result<()>>> {
14875 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14876 let project = match &self.project {
14877 Some(project) => project.clone(),
14878 None => return None,
14879 };
14880 Some(self.perform_code_action_kind(
14881 project,
14882 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14883 window,
14884 cx,
14885 ))
14886 }
14887
14888 fn perform_code_action_kind(
14889 &mut self,
14890 project: Entity<Project>,
14891 kind: CodeActionKind,
14892 window: &mut Window,
14893 cx: &mut Context<Self>,
14894 ) -> Task<Result<()>> {
14895 let buffer = self.buffer.clone();
14896 let buffers = buffer.read(cx).all_buffers();
14897 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14898 let apply_action = project.update(cx, |project, cx| {
14899 project.apply_code_action_kind(buffers, kind, true, cx)
14900 });
14901 cx.spawn_in(window, async move |_, cx| {
14902 let transaction = futures::select_biased! {
14903 () = timeout => {
14904 log::warn!("timed out waiting for executing code action");
14905 None
14906 }
14907 transaction = apply_action.log_err().fuse() => transaction,
14908 };
14909 buffer
14910 .update(cx, |buffer, cx| {
14911 // check if we need this
14912 if let Some(transaction) = transaction {
14913 if !buffer.is_singleton() {
14914 buffer.push_transaction(&transaction.0, cx);
14915 }
14916 }
14917 cx.notify();
14918 })
14919 .ok();
14920 Ok(())
14921 })
14922 }
14923
14924 fn restart_language_server(
14925 &mut self,
14926 _: &RestartLanguageServer,
14927 _: &mut Window,
14928 cx: &mut Context<Self>,
14929 ) {
14930 if let Some(project) = self.project.clone() {
14931 self.buffer.update(cx, |multi_buffer, cx| {
14932 project.update(cx, |project, cx| {
14933 project.restart_language_servers_for_buffers(
14934 multi_buffer.all_buffers().into_iter().collect(),
14935 cx,
14936 );
14937 });
14938 })
14939 }
14940 }
14941
14942 fn stop_language_server(
14943 &mut self,
14944 _: &StopLanguageServer,
14945 _: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 if let Some(project) = self.project.clone() {
14949 self.buffer.update(cx, |multi_buffer, cx| {
14950 project.update(cx, |project, cx| {
14951 project.stop_language_servers_for_buffers(
14952 multi_buffer.all_buffers().into_iter().collect(),
14953 cx,
14954 );
14955 cx.emit(project::Event::RefreshInlayHints);
14956 });
14957 });
14958 }
14959 }
14960
14961 fn cancel_language_server_work(
14962 workspace: &mut Workspace,
14963 _: &actions::CancelLanguageServerWork,
14964 _: &mut Window,
14965 cx: &mut Context<Workspace>,
14966 ) {
14967 let project = workspace.project();
14968 let buffers = workspace
14969 .active_item(cx)
14970 .and_then(|item| item.act_as::<Editor>(cx))
14971 .map_or(HashSet::default(), |editor| {
14972 editor.read(cx).buffer.read(cx).all_buffers()
14973 });
14974 project.update(cx, |project, cx| {
14975 project.cancel_language_server_work_for_buffers(buffers, cx);
14976 });
14977 }
14978
14979 fn show_character_palette(
14980 &mut self,
14981 _: &ShowCharacterPalette,
14982 window: &mut Window,
14983 _: &mut Context<Self>,
14984 ) {
14985 window.show_character_palette();
14986 }
14987
14988 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14989 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14990 let buffer = self.buffer.read(cx).snapshot(cx);
14991 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14992 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14993 let is_valid = buffer
14994 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14995 .any(|entry| {
14996 entry.diagnostic.is_primary
14997 && !entry.range.is_empty()
14998 && entry.range.start == primary_range_start
14999 && entry.diagnostic.message == active_diagnostics.active_message
15000 });
15001
15002 if !is_valid {
15003 self.dismiss_diagnostics(cx);
15004 }
15005 }
15006 }
15007
15008 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15009 match &self.active_diagnostics {
15010 ActiveDiagnostic::Group(group) => Some(group),
15011 _ => None,
15012 }
15013 }
15014
15015 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15016 self.dismiss_diagnostics(cx);
15017 self.active_diagnostics = ActiveDiagnostic::All;
15018 }
15019
15020 fn activate_diagnostics(
15021 &mut self,
15022 buffer_id: BufferId,
15023 diagnostic: DiagnosticEntry<usize>,
15024 window: &mut Window,
15025 cx: &mut Context<Self>,
15026 ) {
15027 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15028 return;
15029 }
15030 self.dismiss_diagnostics(cx);
15031 let snapshot = self.snapshot(window, cx);
15032 let buffer = self.buffer.read(cx).snapshot(cx);
15033 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15034 return;
15035 };
15036
15037 let diagnostic_group = buffer
15038 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15039 .collect::<Vec<_>>();
15040
15041 let blocks =
15042 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15043
15044 let blocks = self.display_map.update(cx, |display_map, cx| {
15045 display_map.insert_blocks(blocks, cx).into_iter().collect()
15046 });
15047 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15048 active_range: buffer.anchor_before(diagnostic.range.start)
15049 ..buffer.anchor_after(diagnostic.range.end),
15050 active_message: diagnostic.diagnostic.message.clone(),
15051 group_id: diagnostic.diagnostic.group_id,
15052 blocks,
15053 });
15054 cx.notify();
15055 }
15056
15057 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15058 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15059 return;
15060 };
15061
15062 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15063 if let ActiveDiagnostic::Group(group) = prev {
15064 self.display_map.update(cx, |display_map, cx| {
15065 display_map.remove_blocks(group.blocks, cx);
15066 });
15067 cx.notify();
15068 }
15069 }
15070
15071 /// Disable inline diagnostics rendering for this editor.
15072 pub fn disable_inline_diagnostics(&mut self) {
15073 self.inline_diagnostics_enabled = false;
15074 self.inline_diagnostics_update = Task::ready(());
15075 self.inline_diagnostics.clear();
15076 }
15077
15078 pub fn inline_diagnostics_enabled(&self) -> bool {
15079 self.inline_diagnostics_enabled
15080 }
15081
15082 pub fn show_inline_diagnostics(&self) -> bool {
15083 self.show_inline_diagnostics
15084 }
15085
15086 pub fn toggle_inline_diagnostics(
15087 &mut self,
15088 _: &ToggleInlineDiagnostics,
15089 window: &mut Window,
15090 cx: &mut Context<Editor>,
15091 ) {
15092 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15093 self.refresh_inline_diagnostics(false, window, cx);
15094 }
15095
15096 fn refresh_inline_diagnostics(
15097 &mut self,
15098 debounce: bool,
15099 window: &mut Window,
15100 cx: &mut Context<Self>,
15101 ) {
15102 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15103 self.inline_diagnostics_update = Task::ready(());
15104 self.inline_diagnostics.clear();
15105 return;
15106 }
15107
15108 let debounce_ms = ProjectSettings::get_global(cx)
15109 .diagnostics
15110 .inline
15111 .update_debounce_ms;
15112 let debounce = if debounce && debounce_ms > 0 {
15113 Some(Duration::from_millis(debounce_ms))
15114 } else {
15115 None
15116 };
15117 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15118 let editor = editor.upgrade().unwrap();
15119
15120 if let Some(debounce) = debounce {
15121 cx.background_executor().timer(debounce).await;
15122 }
15123 let Some(snapshot) = editor
15124 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15125 .ok()
15126 else {
15127 return;
15128 };
15129
15130 let new_inline_diagnostics = cx
15131 .background_spawn(async move {
15132 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15133 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15134 let message = diagnostic_entry
15135 .diagnostic
15136 .message
15137 .split_once('\n')
15138 .map(|(line, _)| line)
15139 .map(SharedString::new)
15140 .unwrap_or_else(|| {
15141 SharedString::from(diagnostic_entry.diagnostic.message)
15142 });
15143 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15144 let (Ok(i) | Err(i)) = inline_diagnostics
15145 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15146 inline_diagnostics.insert(
15147 i,
15148 (
15149 start_anchor,
15150 InlineDiagnostic {
15151 message,
15152 group_id: diagnostic_entry.diagnostic.group_id,
15153 start: diagnostic_entry.range.start.to_point(&snapshot),
15154 is_primary: diagnostic_entry.diagnostic.is_primary,
15155 severity: diagnostic_entry.diagnostic.severity,
15156 },
15157 ),
15158 );
15159 }
15160 inline_diagnostics
15161 })
15162 .await;
15163
15164 editor
15165 .update(cx, |editor, cx| {
15166 editor.inline_diagnostics = new_inline_diagnostics;
15167 cx.notify();
15168 })
15169 .ok();
15170 });
15171 }
15172
15173 pub fn set_selections_from_remote(
15174 &mut self,
15175 selections: Vec<Selection<Anchor>>,
15176 pending_selection: Option<Selection<Anchor>>,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 let old_cursor_position = self.selections.newest_anchor().head();
15181 self.selections.change_with(cx, |s| {
15182 s.select_anchors(selections);
15183 if let Some(pending_selection) = pending_selection {
15184 s.set_pending(pending_selection, SelectMode::Character);
15185 } else {
15186 s.clear_pending();
15187 }
15188 });
15189 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15190 }
15191
15192 fn push_to_selection_history(&mut self) {
15193 self.selection_history.push(SelectionHistoryEntry {
15194 selections: self.selections.disjoint_anchors(),
15195 select_next_state: self.select_next_state.clone(),
15196 select_prev_state: self.select_prev_state.clone(),
15197 add_selections_state: self.add_selections_state.clone(),
15198 });
15199 }
15200
15201 pub fn transact(
15202 &mut self,
15203 window: &mut Window,
15204 cx: &mut Context<Self>,
15205 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15206 ) -> Option<TransactionId> {
15207 self.start_transaction_at(Instant::now(), window, cx);
15208 update(self, window, cx);
15209 self.end_transaction_at(Instant::now(), cx)
15210 }
15211
15212 pub fn start_transaction_at(
15213 &mut self,
15214 now: Instant,
15215 window: &mut Window,
15216 cx: &mut Context<Self>,
15217 ) {
15218 self.end_selection(window, cx);
15219 if let Some(tx_id) = self
15220 .buffer
15221 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15222 {
15223 self.selection_history
15224 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15225 cx.emit(EditorEvent::TransactionBegun {
15226 transaction_id: tx_id,
15227 })
15228 }
15229 }
15230
15231 pub fn end_transaction_at(
15232 &mut self,
15233 now: Instant,
15234 cx: &mut Context<Self>,
15235 ) -> Option<TransactionId> {
15236 if let Some(transaction_id) = self
15237 .buffer
15238 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15239 {
15240 if let Some((_, end_selections)) =
15241 self.selection_history.transaction_mut(transaction_id)
15242 {
15243 *end_selections = Some(self.selections.disjoint_anchors());
15244 } else {
15245 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15246 }
15247
15248 cx.emit(EditorEvent::Edited { transaction_id });
15249 Some(transaction_id)
15250 } else {
15251 None
15252 }
15253 }
15254
15255 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15256 if self.selection_mark_mode {
15257 self.change_selections(None, window, cx, |s| {
15258 s.move_with(|_, sel| {
15259 sel.collapse_to(sel.head(), SelectionGoal::None);
15260 });
15261 })
15262 }
15263 self.selection_mark_mode = true;
15264 cx.notify();
15265 }
15266
15267 pub fn swap_selection_ends(
15268 &mut self,
15269 _: &actions::SwapSelectionEnds,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 self.change_selections(None, window, cx, |s| {
15274 s.move_with(|_, sel| {
15275 if sel.start != sel.end {
15276 sel.reversed = !sel.reversed
15277 }
15278 });
15279 });
15280 self.request_autoscroll(Autoscroll::newest(), cx);
15281 cx.notify();
15282 }
15283
15284 pub fn toggle_fold(
15285 &mut self,
15286 _: &actions::ToggleFold,
15287 window: &mut Window,
15288 cx: &mut Context<Self>,
15289 ) {
15290 if self.is_singleton(cx) {
15291 let selection = self.selections.newest::<Point>(cx);
15292
15293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15294 let range = if selection.is_empty() {
15295 let point = selection.head().to_display_point(&display_map);
15296 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15297 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15298 .to_point(&display_map);
15299 start..end
15300 } else {
15301 selection.range()
15302 };
15303 if display_map.folds_in_range(range).next().is_some() {
15304 self.unfold_lines(&Default::default(), window, cx)
15305 } else {
15306 self.fold(&Default::default(), window, cx)
15307 }
15308 } else {
15309 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15310 let buffer_ids: HashSet<_> = self
15311 .selections
15312 .disjoint_anchor_ranges()
15313 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15314 .collect();
15315
15316 let should_unfold = buffer_ids
15317 .iter()
15318 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15319
15320 for buffer_id in buffer_ids {
15321 if should_unfold {
15322 self.unfold_buffer(buffer_id, cx);
15323 } else {
15324 self.fold_buffer(buffer_id, cx);
15325 }
15326 }
15327 }
15328 }
15329
15330 pub fn toggle_fold_recursive(
15331 &mut self,
15332 _: &actions::ToggleFoldRecursive,
15333 window: &mut Window,
15334 cx: &mut Context<Self>,
15335 ) {
15336 let selection = self.selections.newest::<Point>(cx);
15337
15338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15339 let range = if selection.is_empty() {
15340 let point = selection.head().to_display_point(&display_map);
15341 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15342 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15343 .to_point(&display_map);
15344 start..end
15345 } else {
15346 selection.range()
15347 };
15348 if display_map.folds_in_range(range).next().is_some() {
15349 self.unfold_recursive(&Default::default(), window, cx)
15350 } else {
15351 self.fold_recursive(&Default::default(), window, cx)
15352 }
15353 }
15354
15355 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15356 if self.is_singleton(cx) {
15357 let mut to_fold = Vec::new();
15358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15359 let selections = self.selections.all_adjusted(cx);
15360
15361 for selection in selections {
15362 let range = selection.range().sorted();
15363 let buffer_start_row = range.start.row;
15364
15365 if range.start.row != range.end.row {
15366 let mut found = false;
15367 let mut row = range.start.row;
15368 while row <= range.end.row {
15369 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15370 {
15371 found = true;
15372 row = crease.range().end.row + 1;
15373 to_fold.push(crease);
15374 } else {
15375 row += 1
15376 }
15377 }
15378 if found {
15379 continue;
15380 }
15381 }
15382
15383 for row in (0..=range.start.row).rev() {
15384 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15385 if crease.range().end.row >= buffer_start_row {
15386 to_fold.push(crease);
15387 if row <= range.start.row {
15388 break;
15389 }
15390 }
15391 }
15392 }
15393 }
15394
15395 self.fold_creases(to_fold, true, window, cx);
15396 } else {
15397 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15398 let buffer_ids = self
15399 .selections
15400 .disjoint_anchor_ranges()
15401 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15402 .collect::<HashSet<_>>();
15403 for buffer_id in buffer_ids {
15404 self.fold_buffer(buffer_id, cx);
15405 }
15406 }
15407 }
15408
15409 fn fold_at_level(
15410 &mut self,
15411 fold_at: &FoldAtLevel,
15412 window: &mut Window,
15413 cx: &mut Context<Self>,
15414 ) {
15415 if !self.buffer.read(cx).is_singleton() {
15416 return;
15417 }
15418
15419 let fold_at_level = fold_at.0;
15420 let snapshot = self.buffer.read(cx).snapshot(cx);
15421 let mut to_fold = Vec::new();
15422 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15423
15424 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15425 while start_row < end_row {
15426 match self
15427 .snapshot(window, cx)
15428 .crease_for_buffer_row(MultiBufferRow(start_row))
15429 {
15430 Some(crease) => {
15431 let nested_start_row = crease.range().start.row + 1;
15432 let nested_end_row = crease.range().end.row;
15433
15434 if current_level < fold_at_level {
15435 stack.push((nested_start_row, nested_end_row, current_level + 1));
15436 } else if current_level == fold_at_level {
15437 to_fold.push(crease);
15438 }
15439
15440 start_row = nested_end_row + 1;
15441 }
15442 None => start_row += 1,
15443 }
15444 }
15445 }
15446
15447 self.fold_creases(to_fold, true, window, cx);
15448 }
15449
15450 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15451 if self.buffer.read(cx).is_singleton() {
15452 let mut fold_ranges = Vec::new();
15453 let snapshot = self.buffer.read(cx).snapshot(cx);
15454
15455 for row in 0..snapshot.max_row().0 {
15456 if let Some(foldable_range) = self
15457 .snapshot(window, cx)
15458 .crease_for_buffer_row(MultiBufferRow(row))
15459 {
15460 fold_ranges.push(foldable_range);
15461 }
15462 }
15463
15464 self.fold_creases(fold_ranges, true, window, cx);
15465 } else {
15466 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15467 editor
15468 .update_in(cx, |editor, _, cx| {
15469 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15470 editor.fold_buffer(buffer_id, cx);
15471 }
15472 })
15473 .ok();
15474 });
15475 }
15476 }
15477
15478 pub fn fold_function_bodies(
15479 &mut self,
15480 _: &actions::FoldFunctionBodies,
15481 window: &mut Window,
15482 cx: &mut Context<Self>,
15483 ) {
15484 let snapshot = self.buffer.read(cx).snapshot(cx);
15485
15486 let ranges = snapshot
15487 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15488 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15489 .collect::<Vec<_>>();
15490
15491 let creases = ranges
15492 .into_iter()
15493 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15494 .collect();
15495
15496 self.fold_creases(creases, true, window, cx);
15497 }
15498
15499 pub fn fold_recursive(
15500 &mut self,
15501 _: &actions::FoldRecursive,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let mut to_fold = Vec::new();
15506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15507 let selections = self.selections.all_adjusted(cx);
15508
15509 for selection in selections {
15510 let range = selection.range().sorted();
15511 let buffer_start_row = range.start.row;
15512
15513 if range.start.row != range.end.row {
15514 let mut found = false;
15515 for row in range.start.row..=range.end.row {
15516 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15517 found = true;
15518 to_fold.push(crease);
15519 }
15520 }
15521 if found {
15522 continue;
15523 }
15524 }
15525
15526 for row in (0..=range.start.row).rev() {
15527 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15528 if crease.range().end.row >= buffer_start_row {
15529 to_fold.push(crease);
15530 } else {
15531 break;
15532 }
15533 }
15534 }
15535 }
15536
15537 self.fold_creases(to_fold, true, window, cx);
15538 }
15539
15540 pub fn fold_at(
15541 &mut self,
15542 buffer_row: MultiBufferRow,
15543 window: &mut Window,
15544 cx: &mut Context<Self>,
15545 ) {
15546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15547
15548 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15549 let autoscroll = self
15550 .selections
15551 .all::<Point>(cx)
15552 .iter()
15553 .any(|selection| crease.range().overlaps(&selection.range()));
15554
15555 self.fold_creases(vec![crease], autoscroll, window, cx);
15556 }
15557 }
15558
15559 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15560 if self.is_singleton(cx) {
15561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15562 let buffer = &display_map.buffer_snapshot;
15563 let selections = self.selections.all::<Point>(cx);
15564 let ranges = selections
15565 .iter()
15566 .map(|s| {
15567 let range = s.display_range(&display_map).sorted();
15568 let mut start = range.start.to_point(&display_map);
15569 let mut end = range.end.to_point(&display_map);
15570 start.column = 0;
15571 end.column = buffer.line_len(MultiBufferRow(end.row));
15572 start..end
15573 })
15574 .collect::<Vec<_>>();
15575
15576 self.unfold_ranges(&ranges, true, true, cx);
15577 } else {
15578 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15579 let buffer_ids = self
15580 .selections
15581 .disjoint_anchor_ranges()
15582 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15583 .collect::<HashSet<_>>();
15584 for buffer_id in buffer_ids {
15585 self.unfold_buffer(buffer_id, cx);
15586 }
15587 }
15588 }
15589
15590 pub fn unfold_recursive(
15591 &mut self,
15592 _: &UnfoldRecursive,
15593 _window: &mut Window,
15594 cx: &mut Context<Self>,
15595 ) {
15596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15597 let selections = self.selections.all::<Point>(cx);
15598 let ranges = selections
15599 .iter()
15600 .map(|s| {
15601 let mut range = s.display_range(&display_map).sorted();
15602 *range.start.column_mut() = 0;
15603 *range.end.column_mut() = display_map.line_len(range.end.row());
15604 let start = range.start.to_point(&display_map);
15605 let end = range.end.to_point(&display_map);
15606 start..end
15607 })
15608 .collect::<Vec<_>>();
15609
15610 self.unfold_ranges(&ranges, true, true, cx);
15611 }
15612
15613 pub fn unfold_at(
15614 &mut self,
15615 buffer_row: MultiBufferRow,
15616 _window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15620
15621 let intersection_range = Point::new(buffer_row.0, 0)
15622 ..Point::new(
15623 buffer_row.0,
15624 display_map.buffer_snapshot.line_len(buffer_row),
15625 );
15626
15627 let autoscroll = self
15628 .selections
15629 .all::<Point>(cx)
15630 .iter()
15631 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15632
15633 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15634 }
15635
15636 pub fn unfold_all(
15637 &mut self,
15638 _: &actions::UnfoldAll,
15639 _window: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) {
15642 if self.buffer.read(cx).is_singleton() {
15643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15644 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15645 } else {
15646 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15647 editor
15648 .update(cx, |editor, cx| {
15649 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15650 editor.unfold_buffer(buffer_id, cx);
15651 }
15652 })
15653 .ok();
15654 });
15655 }
15656 }
15657
15658 pub fn fold_selected_ranges(
15659 &mut self,
15660 _: &FoldSelectedRanges,
15661 window: &mut Window,
15662 cx: &mut Context<Self>,
15663 ) {
15664 let selections = self.selections.all_adjusted(cx);
15665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15666 let ranges = selections
15667 .into_iter()
15668 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15669 .collect::<Vec<_>>();
15670 self.fold_creases(ranges, true, window, cx);
15671 }
15672
15673 pub fn fold_ranges<T: ToOffset + Clone>(
15674 &mut self,
15675 ranges: Vec<Range<T>>,
15676 auto_scroll: bool,
15677 window: &mut Window,
15678 cx: &mut Context<Self>,
15679 ) {
15680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15681 let ranges = ranges
15682 .into_iter()
15683 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15684 .collect::<Vec<_>>();
15685 self.fold_creases(ranges, auto_scroll, window, cx);
15686 }
15687
15688 pub fn fold_creases<T: ToOffset + Clone>(
15689 &mut self,
15690 creases: Vec<Crease<T>>,
15691 auto_scroll: bool,
15692 _window: &mut Window,
15693 cx: &mut Context<Self>,
15694 ) {
15695 if creases.is_empty() {
15696 return;
15697 }
15698
15699 let mut buffers_affected = HashSet::default();
15700 let multi_buffer = self.buffer().read(cx);
15701 for crease in &creases {
15702 if let Some((_, buffer, _)) =
15703 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15704 {
15705 buffers_affected.insert(buffer.read(cx).remote_id());
15706 };
15707 }
15708
15709 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15710
15711 if auto_scroll {
15712 self.request_autoscroll(Autoscroll::fit(), cx);
15713 }
15714
15715 cx.notify();
15716
15717 self.scrollbar_marker_state.dirty = true;
15718 self.folds_did_change(cx);
15719 }
15720
15721 /// Removes any folds whose ranges intersect any of the given ranges.
15722 pub fn unfold_ranges<T: ToOffset + Clone>(
15723 &mut self,
15724 ranges: &[Range<T>],
15725 inclusive: bool,
15726 auto_scroll: bool,
15727 cx: &mut Context<Self>,
15728 ) {
15729 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15730 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15731 });
15732 self.folds_did_change(cx);
15733 }
15734
15735 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15736 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15737 return;
15738 }
15739 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15740 self.display_map.update(cx, |display_map, cx| {
15741 display_map.fold_buffers([buffer_id], cx)
15742 });
15743 cx.emit(EditorEvent::BufferFoldToggled {
15744 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15745 folded: true,
15746 });
15747 cx.notify();
15748 }
15749
15750 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15751 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15752 return;
15753 }
15754 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15755 self.display_map.update(cx, |display_map, cx| {
15756 display_map.unfold_buffers([buffer_id], cx);
15757 });
15758 cx.emit(EditorEvent::BufferFoldToggled {
15759 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15760 folded: false,
15761 });
15762 cx.notify();
15763 }
15764
15765 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15766 self.display_map.read(cx).is_buffer_folded(buffer)
15767 }
15768
15769 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15770 self.display_map.read(cx).folded_buffers()
15771 }
15772
15773 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15774 self.display_map.update(cx, |display_map, cx| {
15775 display_map.disable_header_for_buffer(buffer_id, cx);
15776 });
15777 cx.notify();
15778 }
15779
15780 /// Removes any folds with the given ranges.
15781 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15782 &mut self,
15783 ranges: &[Range<T>],
15784 type_id: TypeId,
15785 auto_scroll: bool,
15786 cx: &mut Context<Self>,
15787 ) {
15788 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15789 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15790 });
15791 self.folds_did_change(cx);
15792 }
15793
15794 fn remove_folds_with<T: ToOffset + Clone>(
15795 &mut self,
15796 ranges: &[Range<T>],
15797 auto_scroll: bool,
15798 cx: &mut Context<Self>,
15799 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15800 ) {
15801 if ranges.is_empty() {
15802 return;
15803 }
15804
15805 let mut buffers_affected = HashSet::default();
15806 let multi_buffer = self.buffer().read(cx);
15807 for range in ranges {
15808 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15809 buffers_affected.insert(buffer.read(cx).remote_id());
15810 };
15811 }
15812
15813 self.display_map.update(cx, update);
15814
15815 if auto_scroll {
15816 self.request_autoscroll(Autoscroll::fit(), cx);
15817 }
15818
15819 cx.notify();
15820 self.scrollbar_marker_state.dirty = true;
15821 self.active_indent_guides_state.dirty = true;
15822 }
15823
15824 pub fn update_fold_widths(
15825 &mut self,
15826 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15827 cx: &mut Context<Self>,
15828 ) -> bool {
15829 self.display_map
15830 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15831 }
15832
15833 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15834 self.display_map.read(cx).fold_placeholder.clone()
15835 }
15836
15837 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15838 self.buffer.update(cx, |buffer, cx| {
15839 buffer.set_all_diff_hunks_expanded(cx);
15840 });
15841 }
15842
15843 pub fn expand_all_diff_hunks(
15844 &mut self,
15845 _: &ExpandAllDiffHunks,
15846 _window: &mut Window,
15847 cx: &mut Context<Self>,
15848 ) {
15849 self.buffer.update(cx, |buffer, cx| {
15850 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15851 });
15852 }
15853
15854 pub fn toggle_selected_diff_hunks(
15855 &mut self,
15856 _: &ToggleSelectedDiffHunks,
15857 _window: &mut Window,
15858 cx: &mut Context<Self>,
15859 ) {
15860 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15861 self.toggle_diff_hunks_in_ranges(ranges, cx);
15862 }
15863
15864 pub fn diff_hunks_in_ranges<'a>(
15865 &'a self,
15866 ranges: &'a [Range<Anchor>],
15867 buffer: &'a MultiBufferSnapshot,
15868 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15869 ranges.iter().flat_map(move |range| {
15870 let end_excerpt_id = range.end.excerpt_id;
15871 let range = range.to_point(buffer);
15872 let mut peek_end = range.end;
15873 if range.end.row < buffer.max_row().0 {
15874 peek_end = Point::new(range.end.row + 1, 0);
15875 }
15876 buffer
15877 .diff_hunks_in_range(range.start..peek_end)
15878 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15879 })
15880 }
15881
15882 pub fn has_stageable_diff_hunks_in_ranges(
15883 &self,
15884 ranges: &[Range<Anchor>],
15885 snapshot: &MultiBufferSnapshot,
15886 ) -> bool {
15887 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15888 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15889 }
15890
15891 pub fn toggle_staged_selected_diff_hunks(
15892 &mut self,
15893 _: &::git::ToggleStaged,
15894 _: &mut Window,
15895 cx: &mut Context<Self>,
15896 ) {
15897 let snapshot = self.buffer.read(cx).snapshot(cx);
15898 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15899 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15900 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15901 }
15902
15903 pub fn set_render_diff_hunk_controls(
15904 &mut self,
15905 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15906 cx: &mut Context<Self>,
15907 ) {
15908 self.render_diff_hunk_controls = render_diff_hunk_controls;
15909 cx.notify();
15910 }
15911
15912 pub fn stage_and_next(
15913 &mut self,
15914 _: &::git::StageAndNext,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 self.do_stage_or_unstage_and_next(true, window, cx);
15919 }
15920
15921 pub fn unstage_and_next(
15922 &mut self,
15923 _: &::git::UnstageAndNext,
15924 window: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) {
15927 self.do_stage_or_unstage_and_next(false, window, cx);
15928 }
15929
15930 pub fn stage_or_unstage_diff_hunks(
15931 &mut self,
15932 stage: bool,
15933 ranges: Vec<Range<Anchor>>,
15934 cx: &mut Context<Self>,
15935 ) {
15936 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15937 cx.spawn(async move |this, cx| {
15938 task.await?;
15939 this.update(cx, |this, cx| {
15940 let snapshot = this.buffer.read(cx).snapshot(cx);
15941 let chunk_by = this
15942 .diff_hunks_in_ranges(&ranges, &snapshot)
15943 .chunk_by(|hunk| hunk.buffer_id);
15944 for (buffer_id, hunks) in &chunk_by {
15945 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15946 }
15947 })
15948 })
15949 .detach_and_log_err(cx);
15950 }
15951
15952 fn save_buffers_for_ranges_if_needed(
15953 &mut self,
15954 ranges: &[Range<Anchor>],
15955 cx: &mut Context<Editor>,
15956 ) -> Task<Result<()>> {
15957 let multibuffer = self.buffer.read(cx);
15958 let snapshot = multibuffer.read(cx);
15959 let buffer_ids: HashSet<_> = ranges
15960 .iter()
15961 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15962 .collect();
15963 drop(snapshot);
15964
15965 let mut buffers = HashSet::default();
15966 for buffer_id in buffer_ids {
15967 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15968 let buffer = buffer_entity.read(cx);
15969 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15970 {
15971 buffers.insert(buffer_entity);
15972 }
15973 }
15974 }
15975
15976 if let Some(project) = &self.project {
15977 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15978 } else {
15979 Task::ready(Ok(()))
15980 }
15981 }
15982
15983 fn do_stage_or_unstage_and_next(
15984 &mut self,
15985 stage: bool,
15986 window: &mut Window,
15987 cx: &mut Context<Self>,
15988 ) {
15989 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15990
15991 if ranges.iter().any(|range| range.start != range.end) {
15992 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15993 return;
15994 }
15995
15996 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15997 let snapshot = self.snapshot(window, cx);
15998 let position = self.selections.newest::<Point>(cx).head();
15999 let mut row = snapshot
16000 .buffer_snapshot
16001 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16002 .find(|hunk| hunk.row_range.start.0 > position.row)
16003 .map(|hunk| hunk.row_range.start);
16004
16005 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16006 // Outside of the project diff editor, wrap around to the beginning.
16007 if !all_diff_hunks_expanded {
16008 row = row.or_else(|| {
16009 snapshot
16010 .buffer_snapshot
16011 .diff_hunks_in_range(Point::zero()..position)
16012 .find(|hunk| hunk.row_range.end.0 < position.row)
16013 .map(|hunk| hunk.row_range.start)
16014 });
16015 }
16016
16017 if let Some(row) = row {
16018 let destination = Point::new(row.0, 0);
16019 let autoscroll = Autoscroll::center();
16020
16021 self.unfold_ranges(&[destination..destination], false, false, cx);
16022 self.change_selections(Some(autoscroll), window, cx, |s| {
16023 s.select_ranges([destination..destination]);
16024 });
16025 }
16026 }
16027
16028 fn do_stage_or_unstage(
16029 &self,
16030 stage: bool,
16031 buffer_id: BufferId,
16032 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16033 cx: &mut App,
16034 ) -> Option<()> {
16035 let project = self.project.as_ref()?;
16036 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16037 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16038 let buffer_snapshot = buffer.read(cx).snapshot();
16039 let file_exists = buffer_snapshot
16040 .file()
16041 .is_some_and(|file| file.disk_state().exists());
16042 diff.update(cx, |diff, cx| {
16043 diff.stage_or_unstage_hunks(
16044 stage,
16045 &hunks
16046 .map(|hunk| buffer_diff::DiffHunk {
16047 buffer_range: hunk.buffer_range,
16048 diff_base_byte_range: hunk.diff_base_byte_range,
16049 secondary_status: hunk.secondary_status,
16050 range: Point::zero()..Point::zero(), // unused
16051 })
16052 .collect::<Vec<_>>(),
16053 &buffer_snapshot,
16054 file_exists,
16055 cx,
16056 )
16057 });
16058 None
16059 }
16060
16061 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16062 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16063 self.buffer
16064 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16065 }
16066
16067 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16068 self.buffer.update(cx, |buffer, cx| {
16069 let ranges = vec![Anchor::min()..Anchor::max()];
16070 if !buffer.all_diff_hunks_expanded()
16071 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16072 {
16073 buffer.collapse_diff_hunks(ranges, cx);
16074 true
16075 } else {
16076 false
16077 }
16078 })
16079 }
16080
16081 fn toggle_diff_hunks_in_ranges(
16082 &mut self,
16083 ranges: Vec<Range<Anchor>>,
16084 cx: &mut Context<Editor>,
16085 ) {
16086 self.buffer.update(cx, |buffer, cx| {
16087 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16088 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16089 })
16090 }
16091
16092 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16093 self.buffer.update(cx, |buffer, cx| {
16094 let snapshot = buffer.snapshot(cx);
16095 let excerpt_id = range.end.excerpt_id;
16096 let point_range = range.to_point(&snapshot);
16097 let expand = !buffer.single_hunk_is_expanded(range, cx);
16098 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16099 })
16100 }
16101
16102 pub(crate) fn apply_all_diff_hunks(
16103 &mut self,
16104 _: &ApplyAllDiffHunks,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) {
16108 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16109
16110 let buffers = self.buffer.read(cx).all_buffers();
16111 for branch_buffer in buffers {
16112 branch_buffer.update(cx, |branch_buffer, cx| {
16113 branch_buffer.merge_into_base(Vec::new(), cx);
16114 });
16115 }
16116
16117 if let Some(project) = self.project.clone() {
16118 self.save(true, project, window, cx).detach_and_log_err(cx);
16119 }
16120 }
16121
16122 pub(crate) fn apply_selected_diff_hunks(
16123 &mut self,
16124 _: &ApplyDiffHunk,
16125 window: &mut Window,
16126 cx: &mut Context<Self>,
16127 ) {
16128 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16129 let snapshot = self.snapshot(window, cx);
16130 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16131 let mut ranges_by_buffer = HashMap::default();
16132 self.transact(window, cx, |editor, _window, cx| {
16133 for hunk in hunks {
16134 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16135 ranges_by_buffer
16136 .entry(buffer.clone())
16137 .or_insert_with(Vec::new)
16138 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16139 }
16140 }
16141
16142 for (buffer, ranges) in ranges_by_buffer {
16143 buffer.update(cx, |buffer, cx| {
16144 buffer.merge_into_base(ranges, cx);
16145 });
16146 }
16147 });
16148
16149 if let Some(project) = self.project.clone() {
16150 self.save(true, project, window, cx).detach_and_log_err(cx);
16151 }
16152 }
16153
16154 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16155 if hovered != self.gutter_hovered {
16156 self.gutter_hovered = hovered;
16157 cx.notify();
16158 }
16159 }
16160
16161 pub fn insert_blocks(
16162 &mut self,
16163 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16164 autoscroll: Option<Autoscroll>,
16165 cx: &mut Context<Self>,
16166 ) -> Vec<CustomBlockId> {
16167 let blocks = self
16168 .display_map
16169 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16170 if let Some(autoscroll) = autoscroll {
16171 self.request_autoscroll(autoscroll, cx);
16172 }
16173 cx.notify();
16174 blocks
16175 }
16176
16177 pub fn resize_blocks(
16178 &mut self,
16179 heights: HashMap<CustomBlockId, u32>,
16180 autoscroll: Option<Autoscroll>,
16181 cx: &mut Context<Self>,
16182 ) {
16183 self.display_map
16184 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16185 if let Some(autoscroll) = autoscroll {
16186 self.request_autoscroll(autoscroll, cx);
16187 }
16188 cx.notify();
16189 }
16190
16191 pub fn replace_blocks(
16192 &mut self,
16193 renderers: HashMap<CustomBlockId, RenderBlock>,
16194 autoscroll: Option<Autoscroll>,
16195 cx: &mut Context<Self>,
16196 ) {
16197 self.display_map
16198 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16199 if let Some(autoscroll) = autoscroll {
16200 self.request_autoscroll(autoscroll, cx);
16201 }
16202 cx.notify();
16203 }
16204
16205 pub fn remove_blocks(
16206 &mut self,
16207 block_ids: HashSet<CustomBlockId>,
16208 autoscroll: Option<Autoscroll>,
16209 cx: &mut Context<Self>,
16210 ) {
16211 self.display_map.update(cx, |display_map, cx| {
16212 display_map.remove_blocks(block_ids, cx)
16213 });
16214 if let Some(autoscroll) = autoscroll {
16215 self.request_autoscroll(autoscroll, cx);
16216 }
16217 cx.notify();
16218 }
16219
16220 pub fn row_for_block(
16221 &self,
16222 block_id: CustomBlockId,
16223 cx: &mut Context<Self>,
16224 ) -> Option<DisplayRow> {
16225 self.display_map
16226 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16227 }
16228
16229 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16230 self.focused_block = Some(focused_block);
16231 }
16232
16233 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16234 self.focused_block.take()
16235 }
16236
16237 pub fn insert_creases(
16238 &mut self,
16239 creases: impl IntoIterator<Item = Crease<Anchor>>,
16240 cx: &mut Context<Self>,
16241 ) -> Vec<CreaseId> {
16242 self.display_map
16243 .update(cx, |map, cx| map.insert_creases(creases, cx))
16244 }
16245
16246 pub fn remove_creases(
16247 &mut self,
16248 ids: impl IntoIterator<Item = CreaseId>,
16249 cx: &mut Context<Self>,
16250 ) -> Vec<(CreaseId, Range<Anchor>)> {
16251 self.display_map
16252 .update(cx, |map, cx| map.remove_creases(ids, cx))
16253 }
16254
16255 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16256 self.display_map
16257 .update(cx, |map, cx| map.snapshot(cx))
16258 .longest_row()
16259 }
16260
16261 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16262 self.display_map
16263 .update(cx, |map, cx| map.snapshot(cx))
16264 .max_point()
16265 }
16266
16267 pub fn text(&self, cx: &App) -> String {
16268 self.buffer.read(cx).read(cx).text()
16269 }
16270
16271 pub fn is_empty(&self, cx: &App) -> bool {
16272 self.buffer.read(cx).read(cx).is_empty()
16273 }
16274
16275 pub fn text_option(&self, cx: &App) -> Option<String> {
16276 let text = self.text(cx);
16277 let text = text.trim();
16278
16279 if text.is_empty() {
16280 return None;
16281 }
16282
16283 Some(text.to_string())
16284 }
16285
16286 pub fn set_text(
16287 &mut self,
16288 text: impl Into<Arc<str>>,
16289 window: &mut Window,
16290 cx: &mut Context<Self>,
16291 ) {
16292 self.transact(window, cx, |this, _, cx| {
16293 this.buffer
16294 .read(cx)
16295 .as_singleton()
16296 .expect("you can only call set_text on editors for singleton buffers")
16297 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16298 });
16299 }
16300
16301 pub fn display_text(&self, cx: &mut App) -> String {
16302 self.display_map
16303 .update(cx, |map, cx| map.snapshot(cx))
16304 .text()
16305 }
16306
16307 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16308 let mut wrap_guides = smallvec::smallvec![];
16309
16310 if self.show_wrap_guides == Some(false) {
16311 return wrap_guides;
16312 }
16313
16314 let settings = self.buffer.read(cx).language_settings(cx);
16315 if settings.show_wrap_guides {
16316 match self.soft_wrap_mode(cx) {
16317 SoftWrap::Column(soft_wrap) => {
16318 wrap_guides.push((soft_wrap as usize, true));
16319 }
16320 SoftWrap::Bounded(soft_wrap) => {
16321 wrap_guides.push((soft_wrap as usize, true));
16322 }
16323 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16324 }
16325 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16326 }
16327
16328 wrap_guides
16329 }
16330
16331 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16332 let settings = self.buffer.read(cx).language_settings(cx);
16333 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16334 match mode {
16335 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16336 SoftWrap::None
16337 }
16338 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16339 language_settings::SoftWrap::PreferredLineLength => {
16340 SoftWrap::Column(settings.preferred_line_length)
16341 }
16342 language_settings::SoftWrap::Bounded => {
16343 SoftWrap::Bounded(settings.preferred_line_length)
16344 }
16345 }
16346 }
16347
16348 pub fn set_soft_wrap_mode(
16349 &mut self,
16350 mode: language_settings::SoftWrap,
16351
16352 cx: &mut Context<Self>,
16353 ) {
16354 self.soft_wrap_mode_override = Some(mode);
16355 cx.notify();
16356 }
16357
16358 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16359 self.hard_wrap = hard_wrap;
16360 cx.notify();
16361 }
16362
16363 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16364 self.text_style_refinement = Some(style);
16365 }
16366
16367 /// called by the Element so we know what style we were most recently rendered with.
16368 pub(crate) fn set_style(
16369 &mut self,
16370 style: EditorStyle,
16371 window: &mut Window,
16372 cx: &mut Context<Self>,
16373 ) {
16374 let rem_size = window.rem_size();
16375 self.display_map.update(cx, |map, cx| {
16376 map.set_font(
16377 style.text.font(),
16378 style.text.font_size.to_pixels(rem_size),
16379 cx,
16380 )
16381 });
16382 self.style = Some(style);
16383 }
16384
16385 pub fn style(&self) -> Option<&EditorStyle> {
16386 self.style.as_ref()
16387 }
16388
16389 // Called by the element. This method is not designed to be called outside of the editor
16390 // element's layout code because it does not notify when rewrapping is computed synchronously.
16391 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16392 self.display_map
16393 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16394 }
16395
16396 pub fn set_soft_wrap(&mut self) {
16397 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16398 }
16399
16400 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16401 if self.soft_wrap_mode_override.is_some() {
16402 self.soft_wrap_mode_override.take();
16403 } else {
16404 let soft_wrap = match self.soft_wrap_mode(cx) {
16405 SoftWrap::GitDiff => return,
16406 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16407 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16408 language_settings::SoftWrap::None
16409 }
16410 };
16411 self.soft_wrap_mode_override = Some(soft_wrap);
16412 }
16413 cx.notify();
16414 }
16415
16416 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16417 let Some(workspace) = self.workspace() else {
16418 return;
16419 };
16420 let fs = workspace.read(cx).app_state().fs.clone();
16421 let current_show = TabBarSettings::get_global(cx).show;
16422 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16423 setting.show = Some(!current_show);
16424 });
16425 }
16426
16427 pub fn toggle_indent_guides(
16428 &mut self,
16429 _: &ToggleIndentGuides,
16430 _: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) {
16433 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16434 self.buffer
16435 .read(cx)
16436 .language_settings(cx)
16437 .indent_guides
16438 .enabled
16439 });
16440 self.show_indent_guides = Some(!currently_enabled);
16441 cx.notify();
16442 }
16443
16444 fn should_show_indent_guides(&self) -> Option<bool> {
16445 self.show_indent_guides
16446 }
16447
16448 pub fn toggle_line_numbers(
16449 &mut self,
16450 _: &ToggleLineNumbers,
16451 _: &mut Window,
16452 cx: &mut Context<Self>,
16453 ) {
16454 let mut editor_settings = EditorSettings::get_global(cx).clone();
16455 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16456 EditorSettings::override_global(editor_settings, cx);
16457 }
16458
16459 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16460 if let Some(show_line_numbers) = self.show_line_numbers {
16461 return show_line_numbers;
16462 }
16463 EditorSettings::get_global(cx).gutter.line_numbers
16464 }
16465
16466 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16467 self.use_relative_line_numbers
16468 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16469 }
16470
16471 pub fn toggle_relative_line_numbers(
16472 &mut self,
16473 _: &ToggleRelativeLineNumbers,
16474 _: &mut Window,
16475 cx: &mut Context<Self>,
16476 ) {
16477 let is_relative = self.should_use_relative_line_numbers(cx);
16478 self.set_relative_line_number(Some(!is_relative), cx)
16479 }
16480
16481 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16482 self.use_relative_line_numbers = is_relative;
16483 cx.notify();
16484 }
16485
16486 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16487 self.show_gutter = show_gutter;
16488 cx.notify();
16489 }
16490
16491 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16492 self.show_scrollbars = show_scrollbars;
16493 cx.notify();
16494 }
16495
16496 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16497 self.show_line_numbers = Some(show_line_numbers);
16498 cx.notify();
16499 }
16500
16501 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16502 self.disable_expand_excerpt_buttons = true;
16503 cx.notify();
16504 }
16505
16506 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16507 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16508 cx.notify();
16509 }
16510
16511 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16512 self.show_code_actions = Some(show_code_actions);
16513 cx.notify();
16514 }
16515
16516 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16517 self.show_runnables = Some(show_runnables);
16518 cx.notify();
16519 }
16520
16521 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16522 self.show_breakpoints = Some(show_breakpoints);
16523 cx.notify();
16524 }
16525
16526 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16527 if self.display_map.read(cx).masked != masked {
16528 self.display_map.update(cx, |map, _| map.masked = masked);
16529 }
16530 cx.notify()
16531 }
16532
16533 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16534 self.show_wrap_guides = Some(show_wrap_guides);
16535 cx.notify();
16536 }
16537
16538 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16539 self.show_indent_guides = Some(show_indent_guides);
16540 cx.notify();
16541 }
16542
16543 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16544 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16545 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16546 if let Some(dir) = file.abs_path(cx).parent() {
16547 return Some(dir.to_owned());
16548 }
16549 }
16550
16551 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16552 return Some(project_path.path.to_path_buf());
16553 }
16554 }
16555
16556 None
16557 }
16558
16559 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16560 self.active_excerpt(cx)?
16561 .1
16562 .read(cx)
16563 .file()
16564 .and_then(|f| f.as_local())
16565 }
16566
16567 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16568 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16569 let buffer = buffer.read(cx);
16570 if let Some(project_path) = buffer.project_path(cx) {
16571 let project = self.project.as_ref()?.read(cx);
16572 project.absolute_path(&project_path, cx)
16573 } else {
16574 buffer
16575 .file()
16576 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16577 }
16578 })
16579 }
16580
16581 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16582 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16583 let project_path = buffer.read(cx).project_path(cx)?;
16584 let project = self.project.as_ref()?.read(cx);
16585 let entry = project.entry_for_path(&project_path, cx)?;
16586 let path = entry.path.to_path_buf();
16587 Some(path)
16588 })
16589 }
16590
16591 pub fn reveal_in_finder(
16592 &mut self,
16593 _: &RevealInFileManager,
16594 _window: &mut Window,
16595 cx: &mut Context<Self>,
16596 ) {
16597 if let Some(target) = self.target_file(cx) {
16598 cx.reveal_path(&target.abs_path(cx));
16599 }
16600 }
16601
16602 pub fn copy_path(
16603 &mut self,
16604 _: &zed_actions::workspace::CopyPath,
16605 _window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) {
16608 if let Some(path) = self.target_file_abs_path(cx) {
16609 if let Some(path) = path.to_str() {
16610 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16611 }
16612 }
16613 }
16614
16615 pub fn copy_relative_path(
16616 &mut self,
16617 _: &zed_actions::workspace::CopyRelativePath,
16618 _window: &mut Window,
16619 cx: &mut Context<Self>,
16620 ) {
16621 if let Some(path) = self.target_file_path(cx) {
16622 if let Some(path) = path.to_str() {
16623 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16624 }
16625 }
16626 }
16627
16628 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16629 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16630 buffer.read(cx).project_path(cx)
16631 } else {
16632 None
16633 }
16634 }
16635
16636 // Returns true if the editor handled a go-to-line request
16637 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16638 maybe!({
16639 let breakpoint_store = self.breakpoint_store.as_ref()?;
16640
16641 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16642 else {
16643 self.clear_row_highlights::<ActiveDebugLine>();
16644 return None;
16645 };
16646
16647 let position = active_stack_frame.position;
16648 let buffer_id = position.buffer_id?;
16649 let snapshot = self
16650 .project
16651 .as_ref()?
16652 .read(cx)
16653 .buffer_for_id(buffer_id, cx)?
16654 .read(cx)
16655 .snapshot();
16656
16657 let mut handled = false;
16658 for (id, ExcerptRange { context, .. }) in
16659 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16660 {
16661 if context.start.cmp(&position, &snapshot).is_ge()
16662 || context.end.cmp(&position, &snapshot).is_lt()
16663 {
16664 continue;
16665 }
16666 let snapshot = self.buffer.read(cx).snapshot(cx);
16667 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16668
16669 handled = true;
16670 self.clear_row_highlights::<ActiveDebugLine>();
16671 self.go_to_line::<ActiveDebugLine>(
16672 multibuffer_anchor,
16673 Some(cx.theme().colors().editor_debugger_active_line_background),
16674 window,
16675 cx,
16676 );
16677
16678 cx.notify();
16679 }
16680
16681 handled.then_some(())
16682 })
16683 .is_some()
16684 }
16685
16686 pub fn copy_file_name_without_extension(
16687 &mut self,
16688 _: &CopyFileNameWithoutExtension,
16689 _: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) {
16692 if let Some(file) = self.target_file(cx) {
16693 if let Some(file_stem) = file.path().file_stem() {
16694 if let Some(name) = file_stem.to_str() {
16695 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16696 }
16697 }
16698 }
16699 }
16700
16701 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16702 if let Some(file) = self.target_file(cx) {
16703 if let Some(file_name) = file.path().file_name() {
16704 if let Some(name) = file_name.to_str() {
16705 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16706 }
16707 }
16708 }
16709 }
16710
16711 pub fn toggle_git_blame(
16712 &mut self,
16713 _: &::git::Blame,
16714 window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) {
16717 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16718
16719 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16720 self.start_git_blame(true, window, cx);
16721 }
16722
16723 cx.notify();
16724 }
16725
16726 pub fn toggle_git_blame_inline(
16727 &mut self,
16728 _: &ToggleGitBlameInline,
16729 window: &mut Window,
16730 cx: &mut Context<Self>,
16731 ) {
16732 self.toggle_git_blame_inline_internal(true, window, cx);
16733 cx.notify();
16734 }
16735
16736 pub fn open_git_blame_commit(
16737 &mut self,
16738 _: &OpenGitBlameCommit,
16739 window: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) {
16742 self.open_git_blame_commit_internal(window, cx);
16743 }
16744
16745 fn open_git_blame_commit_internal(
16746 &mut self,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) -> Option<()> {
16750 let blame = self.blame.as_ref()?;
16751 let snapshot = self.snapshot(window, cx);
16752 let cursor = self.selections.newest::<Point>(cx).head();
16753 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16754 let blame_entry = blame
16755 .update(cx, |blame, cx| {
16756 blame
16757 .blame_for_rows(
16758 &[RowInfo {
16759 buffer_id: Some(buffer.remote_id()),
16760 buffer_row: Some(point.row),
16761 ..Default::default()
16762 }],
16763 cx,
16764 )
16765 .next()
16766 })
16767 .flatten()?;
16768 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16769 let repo = blame.read(cx).repository(cx)?;
16770 let workspace = self.workspace()?.downgrade();
16771 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16772 None
16773 }
16774
16775 pub fn git_blame_inline_enabled(&self) -> bool {
16776 self.git_blame_inline_enabled
16777 }
16778
16779 pub fn toggle_selection_menu(
16780 &mut self,
16781 _: &ToggleSelectionMenu,
16782 _: &mut Window,
16783 cx: &mut Context<Self>,
16784 ) {
16785 self.show_selection_menu = self
16786 .show_selection_menu
16787 .map(|show_selections_menu| !show_selections_menu)
16788 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16789
16790 cx.notify();
16791 }
16792
16793 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16794 self.show_selection_menu
16795 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16796 }
16797
16798 fn start_git_blame(
16799 &mut self,
16800 user_triggered: bool,
16801 window: &mut Window,
16802 cx: &mut Context<Self>,
16803 ) {
16804 if let Some(project) = self.project.as_ref() {
16805 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16806 return;
16807 };
16808
16809 if buffer.read(cx).file().is_none() {
16810 return;
16811 }
16812
16813 let focused = self.focus_handle(cx).contains_focused(window, cx);
16814
16815 let project = project.clone();
16816 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16817 self.blame_subscription =
16818 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16819 self.blame = Some(blame);
16820 }
16821 }
16822
16823 fn toggle_git_blame_inline_internal(
16824 &mut self,
16825 user_triggered: bool,
16826 window: &mut Window,
16827 cx: &mut Context<Self>,
16828 ) {
16829 if self.git_blame_inline_enabled {
16830 self.git_blame_inline_enabled = false;
16831 self.show_git_blame_inline = false;
16832 self.show_git_blame_inline_delay_task.take();
16833 } else {
16834 self.git_blame_inline_enabled = true;
16835 self.start_git_blame_inline(user_triggered, window, cx);
16836 }
16837
16838 cx.notify();
16839 }
16840
16841 fn start_git_blame_inline(
16842 &mut self,
16843 user_triggered: bool,
16844 window: &mut Window,
16845 cx: &mut Context<Self>,
16846 ) {
16847 self.start_git_blame(user_triggered, window, cx);
16848
16849 if ProjectSettings::get_global(cx)
16850 .git
16851 .inline_blame_delay()
16852 .is_some()
16853 {
16854 self.start_inline_blame_timer(window, cx);
16855 } else {
16856 self.show_git_blame_inline = true
16857 }
16858 }
16859
16860 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16861 self.blame.as_ref()
16862 }
16863
16864 pub fn show_git_blame_gutter(&self) -> bool {
16865 self.show_git_blame_gutter
16866 }
16867
16868 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16869 self.show_git_blame_gutter && self.has_blame_entries(cx)
16870 }
16871
16872 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16873 self.show_git_blame_inline
16874 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16875 && !self.newest_selection_head_on_empty_line(cx)
16876 && self.has_blame_entries(cx)
16877 }
16878
16879 fn has_blame_entries(&self, cx: &App) -> bool {
16880 self.blame()
16881 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16882 }
16883
16884 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16885 let cursor_anchor = self.selections.newest_anchor().head();
16886
16887 let snapshot = self.buffer.read(cx).snapshot(cx);
16888 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16889
16890 snapshot.line_len(buffer_row) == 0
16891 }
16892
16893 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16894 let buffer_and_selection = maybe!({
16895 let selection = self.selections.newest::<Point>(cx);
16896 let selection_range = selection.range();
16897
16898 let multi_buffer = self.buffer().read(cx);
16899 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16900 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16901
16902 let (buffer, range, _) = if selection.reversed {
16903 buffer_ranges.first()
16904 } else {
16905 buffer_ranges.last()
16906 }?;
16907
16908 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16909 ..text::ToPoint::to_point(&range.end, &buffer).row;
16910 Some((
16911 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16912 selection,
16913 ))
16914 });
16915
16916 let Some((buffer, selection)) = buffer_and_selection else {
16917 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16918 };
16919
16920 let Some(project) = self.project.as_ref() else {
16921 return Task::ready(Err(anyhow!("editor does not have project")));
16922 };
16923
16924 project.update(cx, |project, cx| {
16925 project.get_permalink_to_line(&buffer, selection, cx)
16926 })
16927 }
16928
16929 pub fn copy_permalink_to_line(
16930 &mut self,
16931 _: &CopyPermalinkToLine,
16932 window: &mut Window,
16933 cx: &mut Context<Self>,
16934 ) {
16935 let permalink_task = self.get_permalink_to_line(cx);
16936 let workspace = self.workspace();
16937
16938 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16939 Ok(permalink) => {
16940 cx.update(|_, cx| {
16941 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16942 })
16943 .ok();
16944 }
16945 Err(err) => {
16946 let message = format!("Failed to copy permalink: {err}");
16947
16948 Err::<(), anyhow::Error>(err).log_err();
16949
16950 if let Some(workspace) = workspace {
16951 workspace
16952 .update_in(cx, |workspace, _, cx| {
16953 struct CopyPermalinkToLine;
16954
16955 workspace.show_toast(
16956 Toast::new(
16957 NotificationId::unique::<CopyPermalinkToLine>(),
16958 message,
16959 ),
16960 cx,
16961 )
16962 })
16963 .ok();
16964 }
16965 }
16966 })
16967 .detach();
16968 }
16969
16970 pub fn copy_file_location(
16971 &mut self,
16972 _: &CopyFileLocation,
16973 _: &mut Window,
16974 cx: &mut Context<Self>,
16975 ) {
16976 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16977 if let Some(file) = self.target_file(cx) {
16978 if let Some(path) = file.path().to_str() {
16979 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16980 }
16981 }
16982 }
16983
16984 pub fn open_permalink_to_line(
16985 &mut self,
16986 _: &OpenPermalinkToLine,
16987 window: &mut Window,
16988 cx: &mut Context<Self>,
16989 ) {
16990 let permalink_task = self.get_permalink_to_line(cx);
16991 let workspace = self.workspace();
16992
16993 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16994 Ok(permalink) => {
16995 cx.update(|_, cx| {
16996 cx.open_url(permalink.as_ref());
16997 })
16998 .ok();
16999 }
17000 Err(err) => {
17001 let message = format!("Failed to open permalink: {err}");
17002
17003 Err::<(), anyhow::Error>(err).log_err();
17004
17005 if let Some(workspace) = workspace {
17006 workspace
17007 .update(cx, |workspace, cx| {
17008 struct OpenPermalinkToLine;
17009
17010 workspace.show_toast(
17011 Toast::new(
17012 NotificationId::unique::<OpenPermalinkToLine>(),
17013 message,
17014 ),
17015 cx,
17016 )
17017 })
17018 .ok();
17019 }
17020 }
17021 })
17022 .detach();
17023 }
17024
17025 pub fn insert_uuid_v4(
17026 &mut self,
17027 _: &InsertUuidV4,
17028 window: &mut Window,
17029 cx: &mut Context<Self>,
17030 ) {
17031 self.insert_uuid(UuidVersion::V4, window, cx);
17032 }
17033
17034 pub fn insert_uuid_v7(
17035 &mut self,
17036 _: &InsertUuidV7,
17037 window: &mut Window,
17038 cx: &mut Context<Self>,
17039 ) {
17040 self.insert_uuid(UuidVersion::V7, window, cx);
17041 }
17042
17043 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17044 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17045 self.transact(window, cx, |this, window, cx| {
17046 let edits = this
17047 .selections
17048 .all::<Point>(cx)
17049 .into_iter()
17050 .map(|selection| {
17051 let uuid = match version {
17052 UuidVersion::V4 => uuid::Uuid::new_v4(),
17053 UuidVersion::V7 => uuid::Uuid::now_v7(),
17054 };
17055
17056 (selection.range(), uuid.to_string())
17057 });
17058 this.edit(edits, cx);
17059 this.refresh_inline_completion(true, false, window, cx);
17060 });
17061 }
17062
17063 pub fn open_selections_in_multibuffer(
17064 &mut self,
17065 _: &OpenSelectionsInMultibuffer,
17066 window: &mut Window,
17067 cx: &mut Context<Self>,
17068 ) {
17069 let multibuffer = self.buffer.read(cx);
17070
17071 let Some(buffer) = multibuffer.as_singleton() else {
17072 return;
17073 };
17074
17075 let Some(workspace) = self.workspace() else {
17076 return;
17077 };
17078
17079 let locations = self
17080 .selections
17081 .disjoint_anchors()
17082 .iter()
17083 .map(|range| Location {
17084 buffer: buffer.clone(),
17085 range: range.start.text_anchor..range.end.text_anchor,
17086 })
17087 .collect::<Vec<_>>();
17088
17089 let title = multibuffer.title(cx).to_string();
17090
17091 cx.spawn_in(window, async move |_, cx| {
17092 workspace.update_in(cx, |workspace, window, cx| {
17093 Self::open_locations_in_multibuffer(
17094 workspace,
17095 locations,
17096 format!("Selections for '{title}'"),
17097 false,
17098 MultibufferSelectionMode::All,
17099 window,
17100 cx,
17101 );
17102 })
17103 })
17104 .detach();
17105 }
17106
17107 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17108 /// last highlight added will be used.
17109 ///
17110 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17111 pub fn highlight_rows<T: 'static>(
17112 &mut self,
17113 range: Range<Anchor>,
17114 color: Hsla,
17115 options: RowHighlightOptions,
17116 cx: &mut Context<Self>,
17117 ) {
17118 let snapshot = self.buffer().read(cx).snapshot(cx);
17119 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17120 let ix = row_highlights.binary_search_by(|highlight| {
17121 Ordering::Equal
17122 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17123 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17124 });
17125
17126 if let Err(mut ix) = ix {
17127 let index = post_inc(&mut self.highlight_order);
17128
17129 // If this range intersects with the preceding highlight, then merge it with
17130 // the preceding highlight. Otherwise insert a new highlight.
17131 let mut merged = false;
17132 if ix > 0 {
17133 let prev_highlight = &mut row_highlights[ix - 1];
17134 if prev_highlight
17135 .range
17136 .end
17137 .cmp(&range.start, &snapshot)
17138 .is_ge()
17139 {
17140 ix -= 1;
17141 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17142 prev_highlight.range.end = range.end;
17143 }
17144 merged = true;
17145 prev_highlight.index = index;
17146 prev_highlight.color = color;
17147 prev_highlight.options = options;
17148 }
17149 }
17150
17151 if !merged {
17152 row_highlights.insert(
17153 ix,
17154 RowHighlight {
17155 range: range.clone(),
17156 index,
17157 color,
17158 options,
17159 type_id: TypeId::of::<T>(),
17160 },
17161 );
17162 }
17163
17164 // If any of the following highlights intersect with this one, merge them.
17165 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17166 let highlight = &row_highlights[ix];
17167 if next_highlight
17168 .range
17169 .start
17170 .cmp(&highlight.range.end, &snapshot)
17171 .is_le()
17172 {
17173 if next_highlight
17174 .range
17175 .end
17176 .cmp(&highlight.range.end, &snapshot)
17177 .is_gt()
17178 {
17179 row_highlights[ix].range.end = next_highlight.range.end;
17180 }
17181 row_highlights.remove(ix + 1);
17182 } else {
17183 break;
17184 }
17185 }
17186 }
17187 }
17188
17189 /// Remove any highlighted row ranges of the given type that intersect the
17190 /// given ranges.
17191 pub fn remove_highlighted_rows<T: 'static>(
17192 &mut self,
17193 ranges_to_remove: Vec<Range<Anchor>>,
17194 cx: &mut Context<Self>,
17195 ) {
17196 let snapshot = self.buffer().read(cx).snapshot(cx);
17197 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17198 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17199 row_highlights.retain(|highlight| {
17200 while let Some(range_to_remove) = ranges_to_remove.peek() {
17201 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17202 Ordering::Less | Ordering::Equal => {
17203 ranges_to_remove.next();
17204 }
17205 Ordering::Greater => {
17206 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17207 Ordering::Less | Ordering::Equal => {
17208 return false;
17209 }
17210 Ordering::Greater => break,
17211 }
17212 }
17213 }
17214 }
17215
17216 true
17217 })
17218 }
17219
17220 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17221 pub fn clear_row_highlights<T: 'static>(&mut self) {
17222 self.highlighted_rows.remove(&TypeId::of::<T>());
17223 }
17224
17225 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17226 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17227 self.highlighted_rows
17228 .get(&TypeId::of::<T>())
17229 .map_or(&[] as &[_], |vec| vec.as_slice())
17230 .iter()
17231 .map(|highlight| (highlight.range.clone(), highlight.color))
17232 }
17233
17234 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17235 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17236 /// Allows to ignore certain kinds of highlights.
17237 pub fn highlighted_display_rows(
17238 &self,
17239 window: &mut Window,
17240 cx: &mut App,
17241 ) -> BTreeMap<DisplayRow, LineHighlight> {
17242 let snapshot = self.snapshot(window, cx);
17243 let mut used_highlight_orders = HashMap::default();
17244 self.highlighted_rows
17245 .iter()
17246 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17247 .fold(
17248 BTreeMap::<DisplayRow, LineHighlight>::new(),
17249 |mut unique_rows, highlight| {
17250 let start = highlight.range.start.to_display_point(&snapshot);
17251 let end = highlight.range.end.to_display_point(&snapshot);
17252 let start_row = start.row().0;
17253 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17254 && end.column() == 0
17255 {
17256 end.row().0.saturating_sub(1)
17257 } else {
17258 end.row().0
17259 };
17260 for row in start_row..=end_row {
17261 let used_index =
17262 used_highlight_orders.entry(row).or_insert(highlight.index);
17263 if highlight.index >= *used_index {
17264 *used_index = highlight.index;
17265 unique_rows.insert(
17266 DisplayRow(row),
17267 LineHighlight {
17268 include_gutter: highlight.options.include_gutter,
17269 border: None,
17270 background: highlight.color.into(),
17271 type_id: Some(highlight.type_id),
17272 },
17273 );
17274 }
17275 }
17276 unique_rows
17277 },
17278 )
17279 }
17280
17281 pub fn highlighted_display_row_for_autoscroll(
17282 &self,
17283 snapshot: &DisplaySnapshot,
17284 ) -> Option<DisplayRow> {
17285 self.highlighted_rows
17286 .values()
17287 .flat_map(|highlighted_rows| highlighted_rows.iter())
17288 .filter_map(|highlight| {
17289 if highlight.options.autoscroll {
17290 Some(highlight.range.start.to_display_point(snapshot).row())
17291 } else {
17292 None
17293 }
17294 })
17295 .min()
17296 }
17297
17298 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17299 self.highlight_background::<SearchWithinRange>(
17300 ranges,
17301 |colors| colors.editor_document_highlight_read_background,
17302 cx,
17303 )
17304 }
17305
17306 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17307 self.breadcrumb_header = Some(new_header);
17308 }
17309
17310 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17311 self.clear_background_highlights::<SearchWithinRange>(cx);
17312 }
17313
17314 pub fn highlight_background<T: 'static>(
17315 &mut self,
17316 ranges: &[Range<Anchor>],
17317 color_fetcher: fn(&ThemeColors) -> Hsla,
17318 cx: &mut Context<Self>,
17319 ) {
17320 self.background_highlights
17321 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17322 self.scrollbar_marker_state.dirty = true;
17323 cx.notify();
17324 }
17325
17326 pub fn clear_background_highlights<T: 'static>(
17327 &mut self,
17328 cx: &mut Context<Self>,
17329 ) -> Option<BackgroundHighlight> {
17330 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17331 if !text_highlights.1.is_empty() {
17332 self.scrollbar_marker_state.dirty = true;
17333 cx.notify();
17334 }
17335 Some(text_highlights)
17336 }
17337
17338 pub fn highlight_gutter<T: 'static>(
17339 &mut self,
17340 ranges: &[Range<Anchor>],
17341 color_fetcher: fn(&App) -> Hsla,
17342 cx: &mut Context<Self>,
17343 ) {
17344 self.gutter_highlights
17345 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17346 cx.notify();
17347 }
17348
17349 pub fn clear_gutter_highlights<T: 'static>(
17350 &mut self,
17351 cx: &mut Context<Self>,
17352 ) -> Option<GutterHighlight> {
17353 cx.notify();
17354 self.gutter_highlights.remove(&TypeId::of::<T>())
17355 }
17356
17357 #[cfg(feature = "test-support")]
17358 pub fn all_text_background_highlights(
17359 &self,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17363 let snapshot = self.snapshot(window, cx);
17364 let buffer = &snapshot.buffer_snapshot;
17365 let start = buffer.anchor_before(0);
17366 let end = buffer.anchor_after(buffer.len());
17367 let theme = cx.theme().colors();
17368 self.background_highlights_in_range(start..end, &snapshot, theme)
17369 }
17370
17371 #[cfg(feature = "test-support")]
17372 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17373 let snapshot = self.buffer().read(cx).snapshot(cx);
17374
17375 let highlights = self
17376 .background_highlights
17377 .get(&TypeId::of::<items::BufferSearchHighlights>());
17378
17379 if let Some((_color, ranges)) = highlights {
17380 ranges
17381 .iter()
17382 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17383 .collect_vec()
17384 } else {
17385 vec![]
17386 }
17387 }
17388
17389 fn document_highlights_for_position<'a>(
17390 &'a self,
17391 position: Anchor,
17392 buffer: &'a MultiBufferSnapshot,
17393 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17394 let read_highlights = self
17395 .background_highlights
17396 .get(&TypeId::of::<DocumentHighlightRead>())
17397 .map(|h| &h.1);
17398 let write_highlights = self
17399 .background_highlights
17400 .get(&TypeId::of::<DocumentHighlightWrite>())
17401 .map(|h| &h.1);
17402 let left_position = position.bias_left(buffer);
17403 let right_position = position.bias_right(buffer);
17404 read_highlights
17405 .into_iter()
17406 .chain(write_highlights)
17407 .flat_map(move |ranges| {
17408 let start_ix = match ranges.binary_search_by(|probe| {
17409 let cmp = probe.end.cmp(&left_position, buffer);
17410 if cmp.is_ge() {
17411 Ordering::Greater
17412 } else {
17413 Ordering::Less
17414 }
17415 }) {
17416 Ok(i) | Err(i) => i,
17417 };
17418
17419 ranges[start_ix..]
17420 .iter()
17421 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17422 })
17423 }
17424
17425 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17426 self.background_highlights
17427 .get(&TypeId::of::<T>())
17428 .map_or(false, |(_, highlights)| !highlights.is_empty())
17429 }
17430
17431 pub fn background_highlights_in_range(
17432 &self,
17433 search_range: Range<Anchor>,
17434 display_snapshot: &DisplaySnapshot,
17435 theme: &ThemeColors,
17436 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17437 let mut results = Vec::new();
17438 for (color_fetcher, ranges) in self.background_highlights.values() {
17439 let color = color_fetcher(theme);
17440 let start_ix = match ranges.binary_search_by(|probe| {
17441 let cmp = probe
17442 .end
17443 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17444 if cmp.is_gt() {
17445 Ordering::Greater
17446 } else {
17447 Ordering::Less
17448 }
17449 }) {
17450 Ok(i) | Err(i) => i,
17451 };
17452 for range in &ranges[start_ix..] {
17453 if range
17454 .start
17455 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17456 .is_ge()
17457 {
17458 break;
17459 }
17460
17461 let start = range.start.to_display_point(display_snapshot);
17462 let end = range.end.to_display_point(display_snapshot);
17463 results.push((start..end, color))
17464 }
17465 }
17466 results
17467 }
17468
17469 pub fn background_highlight_row_ranges<T: 'static>(
17470 &self,
17471 search_range: Range<Anchor>,
17472 display_snapshot: &DisplaySnapshot,
17473 count: usize,
17474 ) -> Vec<RangeInclusive<DisplayPoint>> {
17475 let mut results = Vec::new();
17476 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17477 return vec![];
17478 };
17479
17480 let start_ix = match ranges.binary_search_by(|probe| {
17481 let cmp = probe
17482 .end
17483 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17484 if cmp.is_gt() {
17485 Ordering::Greater
17486 } else {
17487 Ordering::Less
17488 }
17489 }) {
17490 Ok(i) | Err(i) => i,
17491 };
17492 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17493 if let (Some(start_display), Some(end_display)) = (start, end) {
17494 results.push(
17495 start_display.to_display_point(display_snapshot)
17496 ..=end_display.to_display_point(display_snapshot),
17497 );
17498 }
17499 };
17500 let mut start_row: Option<Point> = None;
17501 let mut end_row: Option<Point> = None;
17502 if ranges.len() > count {
17503 return Vec::new();
17504 }
17505 for range in &ranges[start_ix..] {
17506 if range
17507 .start
17508 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17509 .is_ge()
17510 {
17511 break;
17512 }
17513 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17514 if let Some(current_row) = &end_row {
17515 if end.row == current_row.row {
17516 continue;
17517 }
17518 }
17519 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17520 if start_row.is_none() {
17521 assert_eq!(end_row, None);
17522 start_row = Some(start);
17523 end_row = Some(end);
17524 continue;
17525 }
17526 if let Some(current_end) = end_row.as_mut() {
17527 if start.row > current_end.row + 1 {
17528 push_region(start_row, end_row);
17529 start_row = Some(start);
17530 end_row = Some(end);
17531 } else {
17532 // Merge two hunks.
17533 *current_end = end;
17534 }
17535 } else {
17536 unreachable!();
17537 }
17538 }
17539 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17540 push_region(start_row, end_row);
17541 results
17542 }
17543
17544 pub fn gutter_highlights_in_range(
17545 &self,
17546 search_range: Range<Anchor>,
17547 display_snapshot: &DisplaySnapshot,
17548 cx: &App,
17549 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17550 let mut results = Vec::new();
17551 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17552 let color = color_fetcher(cx);
17553 let start_ix = match ranges.binary_search_by(|probe| {
17554 let cmp = probe
17555 .end
17556 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17557 if cmp.is_gt() {
17558 Ordering::Greater
17559 } else {
17560 Ordering::Less
17561 }
17562 }) {
17563 Ok(i) | Err(i) => i,
17564 };
17565 for range in &ranges[start_ix..] {
17566 if range
17567 .start
17568 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17569 .is_ge()
17570 {
17571 break;
17572 }
17573
17574 let start = range.start.to_display_point(display_snapshot);
17575 let end = range.end.to_display_point(display_snapshot);
17576 results.push((start..end, color))
17577 }
17578 }
17579 results
17580 }
17581
17582 /// Get the text ranges corresponding to the redaction query
17583 pub fn redacted_ranges(
17584 &self,
17585 search_range: Range<Anchor>,
17586 display_snapshot: &DisplaySnapshot,
17587 cx: &App,
17588 ) -> Vec<Range<DisplayPoint>> {
17589 display_snapshot
17590 .buffer_snapshot
17591 .redacted_ranges(search_range, |file| {
17592 if let Some(file) = file {
17593 file.is_private()
17594 && EditorSettings::get(
17595 Some(SettingsLocation {
17596 worktree_id: file.worktree_id(cx),
17597 path: file.path().as_ref(),
17598 }),
17599 cx,
17600 )
17601 .redact_private_values
17602 } else {
17603 false
17604 }
17605 })
17606 .map(|range| {
17607 range.start.to_display_point(display_snapshot)
17608 ..range.end.to_display_point(display_snapshot)
17609 })
17610 .collect()
17611 }
17612
17613 pub fn highlight_text<T: 'static>(
17614 &mut self,
17615 ranges: Vec<Range<Anchor>>,
17616 style: HighlightStyle,
17617 cx: &mut Context<Self>,
17618 ) {
17619 self.display_map.update(cx, |map, _| {
17620 map.highlight_text(TypeId::of::<T>(), ranges, style)
17621 });
17622 cx.notify();
17623 }
17624
17625 pub(crate) fn highlight_inlays<T: 'static>(
17626 &mut self,
17627 highlights: Vec<InlayHighlight>,
17628 style: HighlightStyle,
17629 cx: &mut Context<Self>,
17630 ) {
17631 self.display_map.update(cx, |map, _| {
17632 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17633 });
17634 cx.notify();
17635 }
17636
17637 pub fn text_highlights<'a, T: 'static>(
17638 &'a self,
17639 cx: &'a App,
17640 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17641 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17642 }
17643
17644 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17645 let cleared = self
17646 .display_map
17647 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17648 if cleared {
17649 cx.notify();
17650 }
17651 }
17652
17653 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17654 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17655 && self.focus_handle.is_focused(window)
17656 }
17657
17658 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17659 self.show_cursor_when_unfocused = is_enabled;
17660 cx.notify();
17661 }
17662
17663 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17664 cx.notify();
17665 }
17666
17667 fn on_debug_session_event(
17668 &mut self,
17669 _session: Entity<Session>,
17670 event: &SessionEvent,
17671 cx: &mut Context<Self>,
17672 ) {
17673 match event {
17674 SessionEvent::InvalidateInlineValue => {
17675 self.refresh_inline_values(cx);
17676 }
17677 _ => {}
17678 }
17679 }
17680
17681 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17682 let Some(project) = self.project.clone() else {
17683 return;
17684 };
17685 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17686 return;
17687 };
17688 if !self.inline_value_cache.enabled {
17689 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17690 self.splice_inlays(&inlays, Vec::new(), cx);
17691 return;
17692 }
17693
17694 let current_execution_position = self
17695 .highlighted_rows
17696 .get(&TypeId::of::<ActiveDebugLine>())
17697 .and_then(|lines| lines.last().map(|line| line.range.start));
17698
17699 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17700 let snapshot = editor
17701 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17702 .ok()?;
17703
17704 let inline_values = editor
17705 .update(cx, |_, cx| {
17706 let Some(current_execution_position) = current_execution_position else {
17707 return Some(Task::ready(Ok(Vec::new())));
17708 };
17709
17710 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17711 // anchor is in the same buffer
17712 let range =
17713 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17714 project.inline_values(buffer, range, cx)
17715 })
17716 .ok()
17717 .flatten()?
17718 .await
17719 .context("refreshing debugger inlays")
17720 .log_err()?;
17721
17722 let (excerpt_id, buffer_id) = snapshot
17723 .excerpts()
17724 .next()
17725 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17726 editor
17727 .update(cx, |editor, cx| {
17728 let new_inlays = inline_values
17729 .into_iter()
17730 .map(|debugger_value| {
17731 Inlay::debugger_hint(
17732 post_inc(&mut editor.next_inlay_id),
17733 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17734 debugger_value.text(),
17735 )
17736 })
17737 .collect::<Vec<_>>();
17738 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17739 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17740
17741 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17742 })
17743 .ok()?;
17744 Some(())
17745 });
17746 }
17747
17748 fn on_buffer_event(
17749 &mut self,
17750 multibuffer: &Entity<MultiBuffer>,
17751 event: &multi_buffer::Event,
17752 window: &mut Window,
17753 cx: &mut Context<Self>,
17754 ) {
17755 match event {
17756 multi_buffer::Event::Edited {
17757 singleton_buffer_edited,
17758 edited_buffer: buffer_edited,
17759 } => {
17760 self.scrollbar_marker_state.dirty = true;
17761 self.active_indent_guides_state.dirty = true;
17762 self.refresh_active_diagnostics(cx);
17763 self.refresh_code_actions(window, cx);
17764 self.refresh_selected_text_highlights(true, window, cx);
17765 refresh_matching_bracket_highlights(self, window, cx);
17766 if self.has_active_inline_completion() {
17767 self.update_visible_inline_completion(window, cx);
17768 }
17769 if let Some(buffer) = buffer_edited {
17770 let buffer_id = buffer.read(cx).remote_id();
17771 if !self.registered_buffers.contains_key(&buffer_id) {
17772 if let Some(project) = self.project.as_ref() {
17773 project.update(cx, |project, cx| {
17774 self.registered_buffers.insert(
17775 buffer_id,
17776 project.register_buffer_with_language_servers(&buffer, cx),
17777 );
17778 })
17779 }
17780 }
17781 }
17782 cx.emit(EditorEvent::BufferEdited);
17783 cx.emit(SearchEvent::MatchesInvalidated);
17784 if *singleton_buffer_edited {
17785 if let Some(project) = &self.project {
17786 #[allow(clippy::mutable_key_type)]
17787 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17788 multibuffer
17789 .all_buffers()
17790 .into_iter()
17791 .filter_map(|buffer| {
17792 buffer.update(cx, |buffer, cx| {
17793 let language = buffer.language()?;
17794 let should_discard = project.update(cx, |project, cx| {
17795 project.is_local()
17796 && !project.has_language_servers_for(buffer, cx)
17797 });
17798 should_discard.not().then_some(language.clone())
17799 })
17800 })
17801 .collect::<HashSet<_>>()
17802 });
17803 if !languages_affected.is_empty() {
17804 self.refresh_inlay_hints(
17805 InlayHintRefreshReason::BufferEdited(languages_affected),
17806 cx,
17807 );
17808 }
17809 }
17810 }
17811
17812 let Some(project) = &self.project else { return };
17813 let (telemetry, is_via_ssh) = {
17814 let project = project.read(cx);
17815 let telemetry = project.client().telemetry().clone();
17816 let is_via_ssh = project.is_via_ssh();
17817 (telemetry, is_via_ssh)
17818 };
17819 refresh_linked_ranges(self, window, cx);
17820 telemetry.log_edit_event("editor", is_via_ssh);
17821 }
17822 multi_buffer::Event::ExcerptsAdded {
17823 buffer,
17824 predecessor,
17825 excerpts,
17826 } => {
17827 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17828 let buffer_id = buffer.read(cx).remote_id();
17829 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17830 if let Some(project) = &self.project {
17831 update_uncommitted_diff_for_buffer(
17832 cx.entity(),
17833 project,
17834 [buffer.clone()],
17835 self.buffer.clone(),
17836 cx,
17837 )
17838 .detach();
17839 }
17840 }
17841 cx.emit(EditorEvent::ExcerptsAdded {
17842 buffer: buffer.clone(),
17843 predecessor: *predecessor,
17844 excerpts: excerpts.clone(),
17845 });
17846 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17847 }
17848 multi_buffer::Event::ExcerptsRemoved {
17849 ids,
17850 removed_buffer_ids,
17851 } => {
17852 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17853 let buffer = self.buffer.read(cx);
17854 self.registered_buffers
17855 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17856 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17857 cx.emit(EditorEvent::ExcerptsRemoved {
17858 ids: ids.clone(),
17859 removed_buffer_ids: removed_buffer_ids.clone(),
17860 })
17861 }
17862 multi_buffer::Event::ExcerptsEdited {
17863 excerpt_ids,
17864 buffer_ids,
17865 } => {
17866 self.display_map.update(cx, |map, cx| {
17867 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17868 });
17869 cx.emit(EditorEvent::ExcerptsEdited {
17870 ids: excerpt_ids.clone(),
17871 })
17872 }
17873 multi_buffer::Event::ExcerptsExpanded { ids } => {
17874 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17875 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17876 }
17877 multi_buffer::Event::Reparsed(buffer_id) => {
17878 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17879 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17880
17881 cx.emit(EditorEvent::Reparsed(*buffer_id));
17882 }
17883 multi_buffer::Event::DiffHunksToggled => {
17884 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17885 }
17886 multi_buffer::Event::LanguageChanged(buffer_id) => {
17887 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17888 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17889 cx.emit(EditorEvent::Reparsed(*buffer_id));
17890 cx.notify();
17891 }
17892 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17893 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17894 multi_buffer::Event::FileHandleChanged
17895 | multi_buffer::Event::Reloaded
17896 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17897 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17898 multi_buffer::Event::DiagnosticsUpdated => {
17899 self.refresh_active_diagnostics(cx);
17900 self.refresh_inline_diagnostics(true, window, cx);
17901 self.scrollbar_marker_state.dirty = true;
17902 cx.notify();
17903 }
17904 _ => {}
17905 };
17906 }
17907
17908 pub fn start_temporary_diff_override(&mut self) {
17909 self.load_diff_task.take();
17910 self.temporary_diff_override = true;
17911 }
17912
17913 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
17914 self.temporary_diff_override = false;
17915 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
17916 self.buffer.update(cx, |buffer, cx| {
17917 buffer.set_all_diff_hunks_collapsed(cx);
17918 });
17919
17920 if let Some(project) = self.project.clone() {
17921 self.load_diff_task = Some(
17922 update_uncommitted_diff_for_buffer(
17923 cx.entity(),
17924 &project,
17925 self.buffer.read(cx).all_buffers(),
17926 self.buffer.clone(),
17927 cx,
17928 )
17929 .shared(),
17930 );
17931 }
17932 }
17933
17934 fn on_display_map_changed(
17935 &mut self,
17936 _: Entity<DisplayMap>,
17937 _: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) {
17940 cx.notify();
17941 }
17942
17943 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17944 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17945 self.update_edit_prediction_settings(cx);
17946 self.refresh_inline_completion(true, false, window, cx);
17947 self.refresh_inlay_hints(
17948 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17949 self.selections.newest_anchor().head(),
17950 &self.buffer.read(cx).snapshot(cx),
17951 cx,
17952 )),
17953 cx,
17954 );
17955
17956 let old_cursor_shape = self.cursor_shape;
17957
17958 {
17959 let editor_settings = EditorSettings::get_global(cx);
17960 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17961 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17962 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17963 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17964 }
17965
17966 if old_cursor_shape != self.cursor_shape {
17967 cx.emit(EditorEvent::CursorShapeChanged);
17968 }
17969
17970 let project_settings = ProjectSettings::get_global(cx);
17971 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17972
17973 if self.mode.is_full() {
17974 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17975 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17976 if self.show_inline_diagnostics != show_inline_diagnostics {
17977 self.show_inline_diagnostics = show_inline_diagnostics;
17978 self.refresh_inline_diagnostics(false, window, cx);
17979 }
17980
17981 if self.git_blame_inline_enabled != inline_blame_enabled {
17982 self.toggle_git_blame_inline_internal(false, window, cx);
17983 }
17984 }
17985
17986 cx.notify();
17987 }
17988
17989 pub fn set_searchable(&mut self, searchable: bool) {
17990 self.searchable = searchable;
17991 }
17992
17993 pub fn searchable(&self) -> bool {
17994 self.searchable
17995 }
17996
17997 fn open_proposed_changes_editor(
17998 &mut self,
17999 _: &OpenProposedChangesEditor,
18000 window: &mut Window,
18001 cx: &mut Context<Self>,
18002 ) {
18003 let Some(workspace) = self.workspace() else {
18004 cx.propagate();
18005 return;
18006 };
18007
18008 let selections = self.selections.all::<usize>(cx);
18009 let multi_buffer = self.buffer.read(cx);
18010 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18011 let mut new_selections_by_buffer = HashMap::default();
18012 for selection in selections {
18013 for (buffer, range, _) in
18014 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18015 {
18016 let mut range = range.to_point(buffer);
18017 range.start.column = 0;
18018 range.end.column = buffer.line_len(range.end.row);
18019 new_selections_by_buffer
18020 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18021 .or_insert(Vec::new())
18022 .push(range)
18023 }
18024 }
18025
18026 let proposed_changes_buffers = new_selections_by_buffer
18027 .into_iter()
18028 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18029 .collect::<Vec<_>>();
18030 let proposed_changes_editor = cx.new(|cx| {
18031 ProposedChangesEditor::new(
18032 "Proposed changes",
18033 proposed_changes_buffers,
18034 self.project.clone(),
18035 window,
18036 cx,
18037 )
18038 });
18039
18040 window.defer(cx, move |window, cx| {
18041 workspace.update(cx, |workspace, cx| {
18042 workspace.active_pane().update(cx, |pane, cx| {
18043 pane.add_item(
18044 Box::new(proposed_changes_editor),
18045 true,
18046 true,
18047 None,
18048 window,
18049 cx,
18050 );
18051 });
18052 });
18053 });
18054 }
18055
18056 pub fn open_excerpts_in_split(
18057 &mut self,
18058 _: &OpenExcerptsSplit,
18059 window: &mut Window,
18060 cx: &mut Context<Self>,
18061 ) {
18062 self.open_excerpts_common(None, true, window, cx)
18063 }
18064
18065 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18066 self.open_excerpts_common(None, false, window, cx)
18067 }
18068
18069 fn open_excerpts_common(
18070 &mut self,
18071 jump_data: Option<JumpData>,
18072 split: bool,
18073 window: &mut Window,
18074 cx: &mut Context<Self>,
18075 ) {
18076 let Some(workspace) = self.workspace() else {
18077 cx.propagate();
18078 return;
18079 };
18080
18081 if self.buffer.read(cx).is_singleton() {
18082 cx.propagate();
18083 return;
18084 }
18085
18086 let mut new_selections_by_buffer = HashMap::default();
18087 match &jump_data {
18088 Some(JumpData::MultiBufferPoint {
18089 excerpt_id,
18090 position,
18091 anchor,
18092 line_offset_from_top,
18093 }) => {
18094 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18095 if let Some(buffer) = multi_buffer_snapshot
18096 .buffer_id_for_excerpt(*excerpt_id)
18097 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18098 {
18099 let buffer_snapshot = buffer.read(cx).snapshot();
18100 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18101 language::ToPoint::to_point(anchor, &buffer_snapshot)
18102 } else {
18103 buffer_snapshot.clip_point(*position, Bias::Left)
18104 };
18105 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18106 new_selections_by_buffer.insert(
18107 buffer,
18108 (
18109 vec![jump_to_offset..jump_to_offset],
18110 Some(*line_offset_from_top),
18111 ),
18112 );
18113 }
18114 }
18115 Some(JumpData::MultiBufferRow {
18116 row,
18117 line_offset_from_top,
18118 }) => {
18119 let point = MultiBufferPoint::new(row.0, 0);
18120 if let Some((buffer, buffer_point, _)) =
18121 self.buffer.read(cx).point_to_buffer_point(point, cx)
18122 {
18123 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18124 new_selections_by_buffer
18125 .entry(buffer)
18126 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18127 .0
18128 .push(buffer_offset..buffer_offset)
18129 }
18130 }
18131 None => {
18132 let selections = self.selections.all::<usize>(cx);
18133 let multi_buffer = self.buffer.read(cx);
18134 for selection in selections {
18135 for (snapshot, range, _, anchor) in multi_buffer
18136 .snapshot(cx)
18137 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18138 {
18139 if let Some(anchor) = anchor {
18140 // selection is in a deleted hunk
18141 let Some(buffer_id) = anchor.buffer_id else {
18142 continue;
18143 };
18144 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18145 continue;
18146 };
18147 let offset = text::ToOffset::to_offset(
18148 &anchor.text_anchor,
18149 &buffer_handle.read(cx).snapshot(),
18150 );
18151 let range = offset..offset;
18152 new_selections_by_buffer
18153 .entry(buffer_handle)
18154 .or_insert((Vec::new(), None))
18155 .0
18156 .push(range)
18157 } else {
18158 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18159 else {
18160 continue;
18161 };
18162 new_selections_by_buffer
18163 .entry(buffer_handle)
18164 .or_insert((Vec::new(), None))
18165 .0
18166 .push(range)
18167 }
18168 }
18169 }
18170 }
18171 }
18172
18173 new_selections_by_buffer
18174 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18175
18176 if new_selections_by_buffer.is_empty() {
18177 return;
18178 }
18179
18180 // We defer the pane interaction because we ourselves are a workspace item
18181 // and activating a new item causes the pane to call a method on us reentrantly,
18182 // which panics if we're on the stack.
18183 window.defer(cx, move |window, cx| {
18184 workspace.update(cx, |workspace, cx| {
18185 let pane = if split {
18186 workspace.adjacent_pane(window, cx)
18187 } else {
18188 workspace.active_pane().clone()
18189 };
18190
18191 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18192 let editor = buffer
18193 .read(cx)
18194 .file()
18195 .is_none()
18196 .then(|| {
18197 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18198 // so `workspace.open_project_item` will never find them, always opening a new editor.
18199 // Instead, we try to activate the existing editor in the pane first.
18200 let (editor, pane_item_index) =
18201 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18202 let editor = item.downcast::<Editor>()?;
18203 let singleton_buffer =
18204 editor.read(cx).buffer().read(cx).as_singleton()?;
18205 if singleton_buffer == buffer {
18206 Some((editor, i))
18207 } else {
18208 None
18209 }
18210 })?;
18211 pane.update(cx, |pane, cx| {
18212 pane.activate_item(pane_item_index, true, true, window, cx)
18213 });
18214 Some(editor)
18215 })
18216 .flatten()
18217 .unwrap_or_else(|| {
18218 workspace.open_project_item::<Self>(
18219 pane.clone(),
18220 buffer,
18221 true,
18222 true,
18223 window,
18224 cx,
18225 )
18226 });
18227
18228 editor.update(cx, |editor, cx| {
18229 let autoscroll = match scroll_offset {
18230 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18231 None => Autoscroll::newest(),
18232 };
18233 let nav_history = editor.nav_history.take();
18234 editor.change_selections(Some(autoscroll), window, cx, |s| {
18235 s.select_ranges(ranges);
18236 });
18237 editor.nav_history = nav_history;
18238 });
18239 }
18240 })
18241 });
18242 }
18243
18244 // For now, don't allow opening excerpts in buffers that aren't backed by
18245 // regular project files.
18246 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18247 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18248 }
18249
18250 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18251 let snapshot = self.buffer.read(cx).read(cx);
18252 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18253 Some(
18254 ranges
18255 .iter()
18256 .map(move |range| {
18257 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18258 })
18259 .collect(),
18260 )
18261 }
18262
18263 fn selection_replacement_ranges(
18264 &self,
18265 range: Range<OffsetUtf16>,
18266 cx: &mut App,
18267 ) -> Vec<Range<OffsetUtf16>> {
18268 let selections = self.selections.all::<OffsetUtf16>(cx);
18269 let newest_selection = selections
18270 .iter()
18271 .max_by_key(|selection| selection.id)
18272 .unwrap();
18273 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18274 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18275 let snapshot = self.buffer.read(cx).read(cx);
18276 selections
18277 .into_iter()
18278 .map(|mut selection| {
18279 selection.start.0 =
18280 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18281 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18282 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18283 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18284 })
18285 .collect()
18286 }
18287
18288 fn report_editor_event(
18289 &self,
18290 event_type: &'static str,
18291 file_extension: Option<String>,
18292 cx: &App,
18293 ) {
18294 if cfg!(any(test, feature = "test-support")) {
18295 return;
18296 }
18297
18298 let Some(project) = &self.project else { return };
18299
18300 // If None, we are in a file without an extension
18301 let file = self
18302 .buffer
18303 .read(cx)
18304 .as_singleton()
18305 .and_then(|b| b.read(cx).file());
18306 let file_extension = file_extension.or(file
18307 .as_ref()
18308 .and_then(|file| Path::new(file.file_name(cx)).extension())
18309 .and_then(|e| e.to_str())
18310 .map(|a| a.to_string()));
18311
18312 let vim_mode = vim_enabled(cx);
18313
18314 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18315 let copilot_enabled = edit_predictions_provider
18316 == language::language_settings::EditPredictionProvider::Copilot;
18317 let copilot_enabled_for_language = self
18318 .buffer
18319 .read(cx)
18320 .language_settings(cx)
18321 .show_edit_predictions;
18322
18323 let project = project.read(cx);
18324 telemetry::event!(
18325 event_type,
18326 file_extension,
18327 vim_mode,
18328 copilot_enabled,
18329 copilot_enabled_for_language,
18330 edit_predictions_provider,
18331 is_via_ssh = project.is_via_ssh(),
18332 );
18333 }
18334
18335 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18336 /// with each line being an array of {text, highlight} objects.
18337 fn copy_highlight_json(
18338 &mut self,
18339 _: &CopyHighlightJson,
18340 window: &mut Window,
18341 cx: &mut Context<Self>,
18342 ) {
18343 #[derive(Serialize)]
18344 struct Chunk<'a> {
18345 text: String,
18346 highlight: Option<&'a str>,
18347 }
18348
18349 let snapshot = self.buffer.read(cx).snapshot(cx);
18350 let range = self
18351 .selected_text_range(false, window, cx)
18352 .and_then(|selection| {
18353 if selection.range.is_empty() {
18354 None
18355 } else {
18356 Some(selection.range)
18357 }
18358 })
18359 .unwrap_or_else(|| 0..snapshot.len());
18360
18361 let chunks = snapshot.chunks(range, true);
18362 let mut lines = Vec::new();
18363 let mut line: VecDeque<Chunk> = VecDeque::new();
18364
18365 let Some(style) = self.style.as_ref() else {
18366 return;
18367 };
18368
18369 for chunk in chunks {
18370 let highlight = chunk
18371 .syntax_highlight_id
18372 .and_then(|id| id.name(&style.syntax));
18373 let mut chunk_lines = chunk.text.split('\n').peekable();
18374 while let Some(text) = chunk_lines.next() {
18375 let mut merged_with_last_token = false;
18376 if let Some(last_token) = line.back_mut() {
18377 if last_token.highlight == highlight {
18378 last_token.text.push_str(text);
18379 merged_with_last_token = true;
18380 }
18381 }
18382
18383 if !merged_with_last_token {
18384 line.push_back(Chunk {
18385 text: text.into(),
18386 highlight,
18387 });
18388 }
18389
18390 if chunk_lines.peek().is_some() {
18391 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18392 line.pop_front();
18393 }
18394 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18395 line.pop_back();
18396 }
18397
18398 lines.push(mem::take(&mut line));
18399 }
18400 }
18401 }
18402
18403 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18404 return;
18405 };
18406 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18407 }
18408
18409 pub fn open_context_menu(
18410 &mut self,
18411 _: &OpenContextMenu,
18412 window: &mut Window,
18413 cx: &mut Context<Self>,
18414 ) {
18415 self.request_autoscroll(Autoscroll::newest(), cx);
18416 let position = self.selections.newest_display(cx).start;
18417 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18418 }
18419
18420 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18421 &self.inlay_hint_cache
18422 }
18423
18424 pub fn replay_insert_event(
18425 &mut self,
18426 text: &str,
18427 relative_utf16_range: Option<Range<isize>>,
18428 window: &mut Window,
18429 cx: &mut Context<Self>,
18430 ) {
18431 if !self.input_enabled {
18432 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18433 return;
18434 }
18435 if let Some(relative_utf16_range) = relative_utf16_range {
18436 let selections = self.selections.all::<OffsetUtf16>(cx);
18437 self.change_selections(None, window, cx, |s| {
18438 let new_ranges = selections.into_iter().map(|range| {
18439 let start = OffsetUtf16(
18440 range
18441 .head()
18442 .0
18443 .saturating_add_signed(relative_utf16_range.start),
18444 );
18445 let end = OffsetUtf16(
18446 range
18447 .head()
18448 .0
18449 .saturating_add_signed(relative_utf16_range.end),
18450 );
18451 start..end
18452 });
18453 s.select_ranges(new_ranges);
18454 });
18455 }
18456
18457 self.handle_input(text, window, cx);
18458 }
18459
18460 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18461 let Some(provider) = self.semantics_provider.as_ref() else {
18462 return false;
18463 };
18464
18465 let mut supports = false;
18466 self.buffer().update(cx, |this, cx| {
18467 this.for_each_buffer(|buffer| {
18468 supports |= provider.supports_inlay_hints(buffer, cx);
18469 });
18470 });
18471
18472 supports
18473 }
18474
18475 pub fn is_focused(&self, window: &Window) -> bool {
18476 self.focus_handle.is_focused(window)
18477 }
18478
18479 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18480 cx.emit(EditorEvent::Focused);
18481
18482 if let Some(descendant) = self
18483 .last_focused_descendant
18484 .take()
18485 .and_then(|descendant| descendant.upgrade())
18486 {
18487 window.focus(&descendant);
18488 } else {
18489 if let Some(blame) = self.blame.as_ref() {
18490 blame.update(cx, GitBlame::focus)
18491 }
18492
18493 self.blink_manager.update(cx, BlinkManager::enable);
18494 self.show_cursor_names(window, cx);
18495 self.buffer.update(cx, |buffer, cx| {
18496 buffer.finalize_last_transaction(cx);
18497 if self.leader_id.is_none() {
18498 buffer.set_active_selections(
18499 &self.selections.disjoint_anchors(),
18500 self.selections.line_mode,
18501 self.cursor_shape,
18502 cx,
18503 );
18504 }
18505 });
18506 }
18507 }
18508
18509 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18510 cx.emit(EditorEvent::FocusedIn)
18511 }
18512
18513 fn handle_focus_out(
18514 &mut self,
18515 event: FocusOutEvent,
18516 _window: &mut Window,
18517 cx: &mut Context<Self>,
18518 ) {
18519 if event.blurred != self.focus_handle {
18520 self.last_focused_descendant = Some(event.blurred);
18521 }
18522 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18523 }
18524
18525 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18526 self.blink_manager.update(cx, BlinkManager::disable);
18527 self.buffer
18528 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18529
18530 if let Some(blame) = self.blame.as_ref() {
18531 blame.update(cx, GitBlame::blur)
18532 }
18533 if !self.hover_state.focused(window, cx) {
18534 hide_hover(self, cx);
18535 }
18536 if !self
18537 .context_menu
18538 .borrow()
18539 .as_ref()
18540 .is_some_and(|context_menu| context_menu.focused(window, cx))
18541 {
18542 self.hide_context_menu(window, cx);
18543 }
18544 self.discard_inline_completion(false, cx);
18545 cx.emit(EditorEvent::Blurred);
18546 cx.notify();
18547 }
18548
18549 pub fn register_action<A: Action>(
18550 &mut self,
18551 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18552 ) -> Subscription {
18553 let id = self.next_editor_action_id.post_inc();
18554 let listener = Arc::new(listener);
18555 self.editor_actions.borrow_mut().insert(
18556 id,
18557 Box::new(move |window, _| {
18558 let listener = listener.clone();
18559 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18560 let action = action.downcast_ref().unwrap();
18561 if phase == DispatchPhase::Bubble {
18562 listener(action, window, cx)
18563 }
18564 })
18565 }),
18566 );
18567
18568 let editor_actions = self.editor_actions.clone();
18569 Subscription::new(move || {
18570 editor_actions.borrow_mut().remove(&id);
18571 })
18572 }
18573
18574 pub fn file_header_size(&self) -> u32 {
18575 FILE_HEADER_HEIGHT
18576 }
18577
18578 pub fn restore(
18579 &mut self,
18580 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18581 window: &mut Window,
18582 cx: &mut Context<Self>,
18583 ) {
18584 let workspace = self.workspace();
18585 let project = self.project.as_ref();
18586 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18587 let mut tasks = Vec::new();
18588 for (buffer_id, changes) in revert_changes {
18589 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18590 buffer.update(cx, |buffer, cx| {
18591 buffer.edit(
18592 changes
18593 .into_iter()
18594 .map(|(range, text)| (range, text.to_string())),
18595 None,
18596 cx,
18597 );
18598 });
18599
18600 if let Some(project) =
18601 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18602 {
18603 project.update(cx, |project, cx| {
18604 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18605 })
18606 }
18607 }
18608 }
18609 tasks
18610 });
18611 cx.spawn_in(window, async move |_, cx| {
18612 for (buffer, task) in save_tasks {
18613 let result = task.await;
18614 if result.is_err() {
18615 let Some(path) = buffer
18616 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18617 .ok()
18618 else {
18619 continue;
18620 };
18621 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18622 let Some(task) = cx
18623 .update_window_entity(&workspace, |workspace, window, cx| {
18624 workspace
18625 .open_path_preview(path, None, false, false, false, window, cx)
18626 })
18627 .ok()
18628 else {
18629 continue;
18630 };
18631 task.await.log_err();
18632 }
18633 }
18634 }
18635 })
18636 .detach();
18637 self.change_selections(None, window, cx, |selections| selections.refresh());
18638 }
18639
18640 pub fn to_pixel_point(
18641 &self,
18642 source: multi_buffer::Anchor,
18643 editor_snapshot: &EditorSnapshot,
18644 window: &mut Window,
18645 ) -> Option<gpui::Point<Pixels>> {
18646 let source_point = source.to_display_point(editor_snapshot);
18647 self.display_to_pixel_point(source_point, editor_snapshot, window)
18648 }
18649
18650 pub fn display_to_pixel_point(
18651 &self,
18652 source: DisplayPoint,
18653 editor_snapshot: &EditorSnapshot,
18654 window: &mut Window,
18655 ) -> Option<gpui::Point<Pixels>> {
18656 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18657 let text_layout_details = self.text_layout_details(window);
18658 let scroll_top = text_layout_details
18659 .scroll_anchor
18660 .scroll_position(editor_snapshot)
18661 .y;
18662
18663 if source.row().as_f32() < scroll_top.floor() {
18664 return None;
18665 }
18666 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18667 let source_y = line_height * (source.row().as_f32() - scroll_top);
18668 Some(gpui::Point::new(source_x, source_y))
18669 }
18670
18671 pub fn has_visible_completions_menu(&self) -> bool {
18672 !self.edit_prediction_preview_is_active()
18673 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18674 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18675 })
18676 }
18677
18678 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18679 self.addons
18680 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18681 }
18682
18683 pub fn unregister_addon<T: Addon>(&mut self) {
18684 self.addons.remove(&std::any::TypeId::of::<T>());
18685 }
18686
18687 pub fn addon<T: Addon>(&self) -> Option<&T> {
18688 let type_id = std::any::TypeId::of::<T>();
18689 self.addons
18690 .get(&type_id)
18691 .and_then(|item| item.to_any().downcast_ref::<T>())
18692 }
18693
18694 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18695 let type_id = std::any::TypeId::of::<T>();
18696 self.addons
18697 .get_mut(&type_id)
18698 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18699 }
18700
18701 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18702 let text_layout_details = self.text_layout_details(window);
18703 let style = &text_layout_details.editor_style;
18704 let font_id = window.text_system().resolve_font(&style.text.font());
18705 let font_size = style.text.font_size.to_pixels(window.rem_size());
18706 let line_height = style.text.line_height_in_pixels(window.rem_size());
18707 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18708
18709 gpui::Size::new(em_width, line_height)
18710 }
18711
18712 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18713 self.load_diff_task.clone()
18714 }
18715
18716 fn read_metadata_from_db(
18717 &mut self,
18718 item_id: u64,
18719 workspace_id: WorkspaceId,
18720 window: &mut Window,
18721 cx: &mut Context<Editor>,
18722 ) {
18723 if self.is_singleton(cx)
18724 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18725 {
18726 let buffer_snapshot = OnceCell::new();
18727
18728 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18729 if !folds.is_empty() {
18730 let snapshot =
18731 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18732 self.fold_ranges(
18733 folds
18734 .into_iter()
18735 .map(|(start, end)| {
18736 snapshot.clip_offset(start, Bias::Left)
18737 ..snapshot.clip_offset(end, Bias::Right)
18738 })
18739 .collect(),
18740 false,
18741 window,
18742 cx,
18743 );
18744 }
18745 }
18746
18747 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18748 if !selections.is_empty() {
18749 let snapshot =
18750 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18751 self.change_selections(None, window, cx, |s| {
18752 s.select_ranges(selections.into_iter().map(|(start, end)| {
18753 snapshot.clip_offset(start, Bias::Left)
18754 ..snapshot.clip_offset(end, Bias::Right)
18755 }));
18756 });
18757 }
18758 };
18759 }
18760
18761 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18762 }
18763}
18764
18765fn vim_enabled(cx: &App) -> bool {
18766 cx.global::<SettingsStore>()
18767 .raw_user_settings()
18768 .get("vim_mode")
18769 == Some(&serde_json::Value::Bool(true))
18770}
18771
18772// Consider user intent and default settings
18773fn choose_completion_range(
18774 completion: &Completion,
18775 intent: CompletionIntent,
18776 buffer: &Entity<Buffer>,
18777 cx: &mut Context<Editor>,
18778) -> Range<usize> {
18779 fn should_replace(
18780 completion: &Completion,
18781 insert_range: &Range<text::Anchor>,
18782 intent: CompletionIntent,
18783 completion_mode_setting: LspInsertMode,
18784 buffer: &Buffer,
18785 ) -> bool {
18786 // specific actions take precedence over settings
18787 match intent {
18788 CompletionIntent::CompleteWithInsert => return false,
18789 CompletionIntent::CompleteWithReplace => return true,
18790 CompletionIntent::Complete | CompletionIntent::Compose => {}
18791 }
18792
18793 match completion_mode_setting {
18794 LspInsertMode::Insert => false,
18795 LspInsertMode::Replace => true,
18796 LspInsertMode::ReplaceSubsequence => {
18797 let mut text_to_replace = buffer.chars_for_range(
18798 buffer.anchor_before(completion.replace_range.start)
18799 ..buffer.anchor_after(completion.replace_range.end),
18800 );
18801 let mut completion_text = completion.new_text.chars();
18802
18803 // is `text_to_replace` a subsequence of `completion_text`
18804 text_to_replace
18805 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18806 }
18807 LspInsertMode::ReplaceSuffix => {
18808 let range_after_cursor = insert_range.end..completion.replace_range.end;
18809
18810 let text_after_cursor = buffer
18811 .text_for_range(
18812 buffer.anchor_before(range_after_cursor.start)
18813 ..buffer.anchor_after(range_after_cursor.end),
18814 )
18815 .collect::<String>();
18816 completion.new_text.ends_with(&text_after_cursor)
18817 }
18818 }
18819 }
18820
18821 let buffer = buffer.read(cx);
18822
18823 if let CompletionSource::Lsp {
18824 insert_range: Some(insert_range),
18825 ..
18826 } = &completion.source
18827 {
18828 let completion_mode_setting =
18829 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18830 .completions
18831 .lsp_insert_mode;
18832
18833 if !should_replace(
18834 completion,
18835 &insert_range,
18836 intent,
18837 completion_mode_setting,
18838 buffer,
18839 ) {
18840 return insert_range.to_offset(buffer);
18841 }
18842 }
18843
18844 completion.replace_range.to_offset(buffer)
18845}
18846
18847fn insert_extra_newline_brackets(
18848 buffer: &MultiBufferSnapshot,
18849 range: Range<usize>,
18850 language: &language::LanguageScope,
18851) -> bool {
18852 let leading_whitespace_len = buffer
18853 .reversed_chars_at(range.start)
18854 .take_while(|c| c.is_whitespace() && *c != '\n')
18855 .map(|c| c.len_utf8())
18856 .sum::<usize>();
18857 let trailing_whitespace_len = buffer
18858 .chars_at(range.end)
18859 .take_while(|c| c.is_whitespace() && *c != '\n')
18860 .map(|c| c.len_utf8())
18861 .sum::<usize>();
18862 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18863
18864 language.brackets().any(|(pair, enabled)| {
18865 let pair_start = pair.start.trim_end();
18866 let pair_end = pair.end.trim_start();
18867
18868 enabled
18869 && pair.newline
18870 && buffer.contains_str_at(range.end, pair_end)
18871 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18872 })
18873}
18874
18875fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18876 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18877 [(buffer, range, _)] => (*buffer, range.clone()),
18878 _ => return false,
18879 };
18880 let pair = {
18881 let mut result: Option<BracketMatch> = None;
18882
18883 for pair in buffer
18884 .all_bracket_ranges(range.clone())
18885 .filter(move |pair| {
18886 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18887 })
18888 {
18889 let len = pair.close_range.end - pair.open_range.start;
18890
18891 if let Some(existing) = &result {
18892 let existing_len = existing.close_range.end - existing.open_range.start;
18893 if len > existing_len {
18894 continue;
18895 }
18896 }
18897
18898 result = Some(pair);
18899 }
18900
18901 result
18902 };
18903 let Some(pair) = pair else {
18904 return false;
18905 };
18906 pair.newline_only
18907 && buffer
18908 .chars_for_range(pair.open_range.end..range.start)
18909 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18910 .all(|c| c.is_whitespace() && c != '\n')
18911}
18912
18913fn update_uncommitted_diff_for_buffer(
18914 editor: Entity<Editor>,
18915 project: &Entity<Project>,
18916 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18917 buffer: Entity<MultiBuffer>,
18918 cx: &mut App,
18919) -> Task<()> {
18920 let mut tasks = Vec::new();
18921 project.update(cx, |project, cx| {
18922 for buffer in buffers {
18923 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18924 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18925 }
18926 }
18927 });
18928 cx.spawn(async move |cx| {
18929 let diffs = future::join_all(tasks).await;
18930 if editor
18931 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
18932 .unwrap_or(false)
18933 {
18934 return;
18935 }
18936
18937 buffer
18938 .update(cx, |buffer, cx| {
18939 for diff in diffs.into_iter().flatten() {
18940 buffer.add_diff(diff, cx);
18941 }
18942 })
18943 .ok();
18944 })
18945}
18946
18947fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18948 let tab_size = tab_size.get() as usize;
18949 let mut width = offset;
18950
18951 for ch in text.chars() {
18952 width += if ch == '\t' {
18953 tab_size - (width % tab_size)
18954 } else {
18955 1
18956 };
18957 }
18958
18959 width - offset
18960}
18961
18962#[cfg(test)]
18963mod tests {
18964 use super::*;
18965
18966 #[test]
18967 fn test_string_size_with_expanded_tabs() {
18968 let nz = |val| NonZeroU32::new(val).unwrap();
18969 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18970 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18971 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18972 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18973 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18974 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18975 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18976 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18977 }
18978}
18979
18980/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18981struct WordBreakingTokenizer<'a> {
18982 input: &'a str,
18983}
18984
18985impl<'a> WordBreakingTokenizer<'a> {
18986 fn new(input: &'a str) -> Self {
18987 Self { input }
18988 }
18989}
18990
18991fn is_char_ideographic(ch: char) -> bool {
18992 use unicode_script::Script::*;
18993 use unicode_script::UnicodeScript;
18994 matches!(ch.script(), Han | Tangut | Yi)
18995}
18996
18997fn is_grapheme_ideographic(text: &str) -> bool {
18998 text.chars().any(is_char_ideographic)
18999}
19000
19001fn is_grapheme_whitespace(text: &str) -> bool {
19002 text.chars().any(|x| x.is_whitespace())
19003}
19004
19005fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19006 text.chars().next().map_or(false, |ch| {
19007 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19008 })
19009}
19010
19011#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19012enum WordBreakToken<'a> {
19013 Word { token: &'a str, grapheme_len: usize },
19014 InlineWhitespace { token: &'a str, grapheme_len: usize },
19015 Newline,
19016}
19017
19018impl<'a> Iterator for WordBreakingTokenizer<'a> {
19019 /// Yields a span, the count of graphemes in the token, and whether it was
19020 /// whitespace. Note that it also breaks at word boundaries.
19021 type Item = WordBreakToken<'a>;
19022
19023 fn next(&mut self) -> Option<Self::Item> {
19024 use unicode_segmentation::UnicodeSegmentation;
19025 if self.input.is_empty() {
19026 return None;
19027 }
19028
19029 let mut iter = self.input.graphemes(true).peekable();
19030 let mut offset = 0;
19031 let mut grapheme_len = 0;
19032 if let Some(first_grapheme) = iter.next() {
19033 let is_newline = first_grapheme == "\n";
19034 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19035 offset += first_grapheme.len();
19036 grapheme_len += 1;
19037 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19038 if let Some(grapheme) = iter.peek().copied() {
19039 if should_stay_with_preceding_ideograph(grapheme) {
19040 offset += grapheme.len();
19041 grapheme_len += 1;
19042 }
19043 }
19044 } else {
19045 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19046 let mut next_word_bound = words.peek().copied();
19047 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19048 next_word_bound = words.next();
19049 }
19050 while let Some(grapheme) = iter.peek().copied() {
19051 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19052 break;
19053 };
19054 if is_grapheme_whitespace(grapheme) != is_whitespace
19055 || (grapheme == "\n") != is_newline
19056 {
19057 break;
19058 };
19059 offset += grapheme.len();
19060 grapheme_len += 1;
19061 iter.next();
19062 }
19063 }
19064 let token = &self.input[..offset];
19065 self.input = &self.input[offset..];
19066 if token == "\n" {
19067 Some(WordBreakToken::Newline)
19068 } else if is_whitespace {
19069 Some(WordBreakToken::InlineWhitespace {
19070 token,
19071 grapheme_len,
19072 })
19073 } else {
19074 Some(WordBreakToken::Word {
19075 token,
19076 grapheme_len,
19077 })
19078 }
19079 } else {
19080 None
19081 }
19082 }
19083}
19084
19085#[test]
19086fn test_word_breaking_tokenizer() {
19087 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19088 ("", &[]),
19089 (" ", &[whitespace(" ", 2)]),
19090 ("Ʒ", &[word("Ʒ", 1)]),
19091 ("Ǽ", &[word("Ǽ", 1)]),
19092 ("⋑", &[word("⋑", 1)]),
19093 ("⋑⋑", &[word("⋑⋑", 2)]),
19094 (
19095 "原理,进而",
19096 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19097 ),
19098 (
19099 "hello world",
19100 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19101 ),
19102 (
19103 "hello, world",
19104 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19105 ),
19106 (
19107 " hello world",
19108 &[
19109 whitespace(" ", 2),
19110 word("hello", 5),
19111 whitespace(" ", 1),
19112 word("world", 5),
19113 ],
19114 ),
19115 (
19116 "这是什么 \n 钢笔",
19117 &[
19118 word("这", 1),
19119 word("是", 1),
19120 word("什", 1),
19121 word("么", 1),
19122 whitespace(" ", 1),
19123 newline(),
19124 whitespace(" ", 1),
19125 word("钢", 1),
19126 word("笔", 1),
19127 ],
19128 ),
19129 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19130 ];
19131
19132 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19133 WordBreakToken::Word {
19134 token,
19135 grapheme_len,
19136 }
19137 }
19138
19139 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19140 WordBreakToken::InlineWhitespace {
19141 token,
19142 grapheme_len,
19143 }
19144 }
19145
19146 fn newline() -> WordBreakToken<'static> {
19147 WordBreakToken::Newline
19148 }
19149
19150 for (input, result) in tests {
19151 assert_eq!(
19152 WordBreakingTokenizer::new(input)
19153 .collect::<Vec<_>>()
19154 .as_slice(),
19155 *result,
19156 );
19157 }
19158}
19159
19160fn wrap_with_prefix(
19161 line_prefix: String,
19162 unwrapped_text: String,
19163 wrap_column: usize,
19164 tab_size: NonZeroU32,
19165 preserve_existing_whitespace: bool,
19166) -> String {
19167 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19168 let mut wrapped_text = String::new();
19169 let mut current_line = line_prefix.clone();
19170
19171 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19172 let mut current_line_len = line_prefix_len;
19173 let mut in_whitespace = false;
19174 for token in tokenizer {
19175 let have_preceding_whitespace = in_whitespace;
19176 match token {
19177 WordBreakToken::Word {
19178 token,
19179 grapheme_len,
19180 } => {
19181 in_whitespace = false;
19182 if current_line_len + grapheme_len > wrap_column
19183 && current_line_len != line_prefix_len
19184 {
19185 wrapped_text.push_str(current_line.trim_end());
19186 wrapped_text.push('\n');
19187 current_line.truncate(line_prefix.len());
19188 current_line_len = line_prefix_len;
19189 }
19190 current_line.push_str(token);
19191 current_line_len += grapheme_len;
19192 }
19193 WordBreakToken::InlineWhitespace {
19194 mut token,
19195 mut grapheme_len,
19196 } => {
19197 in_whitespace = true;
19198 if have_preceding_whitespace && !preserve_existing_whitespace {
19199 continue;
19200 }
19201 if !preserve_existing_whitespace {
19202 token = " ";
19203 grapheme_len = 1;
19204 }
19205 if current_line_len + grapheme_len > wrap_column {
19206 wrapped_text.push_str(current_line.trim_end());
19207 wrapped_text.push('\n');
19208 current_line.truncate(line_prefix.len());
19209 current_line_len = line_prefix_len;
19210 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19211 current_line.push_str(token);
19212 current_line_len += grapheme_len;
19213 }
19214 }
19215 WordBreakToken::Newline => {
19216 in_whitespace = true;
19217 if preserve_existing_whitespace {
19218 wrapped_text.push_str(current_line.trim_end());
19219 wrapped_text.push('\n');
19220 current_line.truncate(line_prefix.len());
19221 current_line_len = line_prefix_len;
19222 } else if have_preceding_whitespace {
19223 continue;
19224 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19225 {
19226 wrapped_text.push_str(current_line.trim_end());
19227 wrapped_text.push('\n');
19228 current_line.truncate(line_prefix.len());
19229 current_line_len = line_prefix_len;
19230 } else if current_line_len != line_prefix_len {
19231 current_line.push(' ');
19232 current_line_len += 1;
19233 }
19234 }
19235 }
19236 }
19237
19238 if !current_line.is_empty() {
19239 wrapped_text.push_str(¤t_line);
19240 }
19241 wrapped_text
19242}
19243
19244#[test]
19245fn test_wrap_with_prefix() {
19246 assert_eq!(
19247 wrap_with_prefix(
19248 "# ".to_string(),
19249 "abcdefg".to_string(),
19250 4,
19251 NonZeroU32::new(4).unwrap(),
19252 false,
19253 ),
19254 "# abcdefg"
19255 );
19256 assert_eq!(
19257 wrap_with_prefix(
19258 "".to_string(),
19259 "\thello world".to_string(),
19260 8,
19261 NonZeroU32::new(4).unwrap(),
19262 false,
19263 ),
19264 "hello\nworld"
19265 );
19266 assert_eq!(
19267 wrap_with_prefix(
19268 "// ".to_string(),
19269 "xx \nyy zz aa bb cc".to_string(),
19270 12,
19271 NonZeroU32::new(4).unwrap(),
19272 false,
19273 ),
19274 "// xx yy zz\n// aa bb cc"
19275 );
19276 assert_eq!(
19277 wrap_with_prefix(
19278 String::new(),
19279 "这是什么 \n 钢笔".to_string(),
19280 3,
19281 NonZeroU32::new(4).unwrap(),
19282 false,
19283 ),
19284 "这是什\n么 钢\n笔"
19285 );
19286}
19287
19288pub trait CollaborationHub {
19289 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19290 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19291 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19292}
19293
19294impl CollaborationHub for Entity<Project> {
19295 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19296 self.read(cx).collaborators()
19297 }
19298
19299 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19300 self.read(cx).user_store().read(cx).participant_indices()
19301 }
19302
19303 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19304 let this = self.read(cx);
19305 let user_ids = this.collaborators().values().map(|c| c.user_id);
19306 this.user_store().read_with(cx, |user_store, cx| {
19307 user_store.participant_names(user_ids, cx)
19308 })
19309 }
19310}
19311
19312pub trait SemanticsProvider {
19313 fn hover(
19314 &self,
19315 buffer: &Entity<Buffer>,
19316 position: text::Anchor,
19317 cx: &mut App,
19318 ) -> Option<Task<Vec<project::Hover>>>;
19319
19320 fn inline_values(
19321 &self,
19322 buffer_handle: Entity<Buffer>,
19323 range: Range<text::Anchor>,
19324 cx: &mut App,
19325 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19326
19327 fn inlay_hints(
19328 &self,
19329 buffer_handle: Entity<Buffer>,
19330 range: Range<text::Anchor>,
19331 cx: &mut App,
19332 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19333
19334 fn resolve_inlay_hint(
19335 &self,
19336 hint: InlayHint,
19337 buffer_handle: Entity<Buffer>,
19338 server_id: LanguageServerId,
19339 cx: &mut App,
19340 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19341
19342 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19343
19344 fn document_highlights(
19345 &self,
19346 buffer: &Entity<Buffer>,
19347 position: text::Anchor,
19348 cx: &mut App,
19349 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19350
19351 fn definitions(
19352 &self,
19353 buffer: &Entity<Buffer>,
19354 position: text::Anchor,
19355 kind: GotoDefinitionKind,
19356 cx: &mut App,
19357 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19358
19359 fn range_for_rename(
19360 &self,
19361 buffer: &Entity<Buffer>,
19362 position: text::Anchor,
19363 cx: &mut App,
19364 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19365
19366 fn perform_rename(
19367 &self,
19368 buffer: &Entity<Buffer>,
19369 position: text::Anchor,
19370 new_name: String,
19371 cx: &mut App,
19372 ) -> Option<Task<Result<ProjectTransaction>>>;
19373}
19374
19375pub trait CompletionProvider {
19376 fn completions(
19377 &self,
19378 excerpt_id: ExcerptId,
19379 buffer: &Entity<Buffer>,
19380 buffer_position: text::Anchor,
19381 trigger: CompletionContext,
19382 window: &mut Window,
19383 cx: &mut Context<Editor>,
19384 ) -> Task<Result<Option<Vec<Completion>>>>;
19385
19386 fn resolve_completions(
19387 &self,
19388 buffer: Entity<Buffer>,
19389 completion_indices: Vec<usize>,
19390 completions: Rc<RefCell<Box<[Completion]>>>,
19391 cx: &mut Context<Editor>,
19392 ) -> Task<Result<bool>>;
19393
19394 fn apply_additional_edits_for_completion(
19395 &self,
19396 _buffer: Entity<Buffer>,
19397 _completions: Rc<RefCell<Box<[Completion]>>>,
19398 _completion_index: usize,
19399 _push_to_history: bool,
19400 _cx: &mut Context<Editor>,
19401 ) -> Task<Result<Option<language::Transaction>>> {
19402 Task::ready(Ok(None))
19403 }
19404
19405 fn is_completion_trigger(
19406 &self,
19407 buffer: &Entity<Buffer>,
19408 position: language::Anchor,
19409 text: &str,
19410 trigger_in_words: bool,
19411 cx: &mut Context<Editor>,
19412 ) -> bool;
19413
19414 fn sort_completions(&self) -> bool {
19415 true
19416 }
19417
19418 fn filter_completions(&self) -> bool {
19419 true
19420 }
19421}
19422
19423pub trait CodeActionProvider {
19424 fn id(&self) -> Arc<str>;
19425
19426 fn code_actions(
19427 &self,
19428 buffer: &Entity<Buffer>,
19429 range: Range<text::Anchor>,
19430 window: &mut Window,
19431 cx: &mut App,
19432 ) -> Task<Result<Vec<CodeAction>>>;
19433
19434 fn apply_code_action(
19435 &self,
19436 buffer_handle: Entity<Buffer>,
19437 action: CodeAction,
19438 excerpt_id: ExcerptId,
19439 push_to_history: bool,
19440 window: &mut Window,
19441 cx: &mut App,
19442 ) -> Task<Result<ProjectTransaction>>;
19443}
19444
19445impl CodeActionProvider for Entity<Project> {
19446 fn id(&self) -> Arc<str> {
19447 "project".into()
19448 }
19449
19450 fn code_actions(
19451 &self,
19452 buffer: &Entity<Buffer>,
19453 range: Range<text::Anchor>,
19454 _window: &mut Window,
19455 cx: &mut App,
19456 ) -> Task<Result<Vec<CodeAction>>> {
19457 self.update(cx, |project, cx| {
19458 let code_lens = project.code_lens(buffer, range.clone(), cx);
19459 let code_actions = project.code_actions(buffer, range, None, cx);
19460 cx.background_spawn(async move {
19461 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19462 Ok(code_lens
19463 .context("code lens fetch")?
19464 .into_iter()
19465 .chain(code_actions.context("code action fetch")?)
19466 .collect())
19467 })
19468 })
19469 }
19470
19471 fn apply_code_action(
19472 &self,
19473 buffer_handle: Entity<Buffer>,
19474 action: CodeAction,
19475 _excerpt_id: ExcerptId,
19476 push_to_history: bool,
19477 _window: &mut Window,
19478 cx: &mut App,
19479 ) -> Task<Result<ProjectTransaction>> {
19480 self.update(cx, |project, cx| {
19481 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19482 })
19483 }
19484}
19485
19486fn snippet_completions(
19487 project: &Project,
19488 buffer: &Entity<Buffer>,
19489 buffer_position: text::Anchor,
19490 cx: &mut App,
19491) -> Task<Result<Vec<Completion>>> {
19492 let languages = buffer.read(cx).languages_at(buffer_position);
19493 let snippet_store = project.snippets().read(cx);
19494
19495 let scopes: Vec<_> = languages
19496 .iter()
19497 .filter_map(|language| {
19498 let language_name = language.lsp_id();
19499 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19500
19501 if snippets.is_empty() {
19502 None
19503 } else {
19504 Some((language.default_scope(), snippets))
19505 }
19506 })
19507 .collect();
19508
19509 if scopes.is_empty() {
19510 return Task::ready(Ok(vec![]));
19511 }
19512
19513 let snapshot = buffer.read(cx).text_snapshot();
19514 let chars: String = snapshot
19515 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19516 .collect();
19517 let executor = cx.background_executor().clone();
19518
19519 cx.background_spawn(async move {
19520 let mut all_results: Vec<Completion> = Vec::new();
19521 for (scope, snippets) in scopes.into_iter() {
19522 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19523 let mut last_word = chars
19524 .chars()
19525 .take_while(|c| classifier.is_word(*c))
19526 .collect::<String>();
19527 last_word = last_word.chars().rev().collect();
19528
19529 if last_word.is_empty() {
19530 return Ok(vec![]);
19531 }
19532
19533 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19534 let to_lsp = |point: &text::Anchor| {
19535 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19536 point_to_lsp(end)
19537 };
19538 let lsp_end = to_lsp(&buffer_position);
19539
19540 let candidates = snippets
19541 .iter()
19542 .enumerate()
19543 .flat_map(|(ix, snippet)| {
19544 snippet
19545 .prefix
19546 .iter()
19547 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19548 })
19549 .collect::<Vec<StringMatchCandidate>>();
19550
19551 let mut matches = fuzzy::match_strings(
19552 &candidates,
19553 &last_word,
19554 last_word.chars().any(|c| c.is_uppercase()),
19555 100,
19556 &Default::default(),
19557 executor.clone(),
19558 )
19559 .await;
19560
19561 // Remove all candidates where the query's start does not match the start of any word in the candidate
19562 if let Some(query_start) = last_word.chars().next() {
19563 matches.retain(|string_match| {
19564 split_words(&string_match.string).any(|word| {
19565 // Check that the first codepoint of the word as lowercase matches the first
19566 // codepoint of the query as lowercase
19567 word.chars()
19568 .flat_map(|codepoint| codepoint.to_lowercase())
19569 .zip(query_start.to_lowercase())
19570 .all(|(word_cp, query_cp)| word_cp == query_cp)
19571 })
19572 });
19573 }
19574
19575 let matched_strings = matches
19576 .into_iter()
19577 .map(|m| m.string)
19578 .collect::<HashSet<_>>();
19579
19580 let mut result: Vec<Completion> = snippets
19581 .iter()
19582 .filter_map(|snippet| {
19583 let matching_prefix = snippet
19584 .prefix
19585 .iter()
19586 .find(|prefix| matched_strings.contains(*prefix))?;
19587 let start = as_offset - last_word.len();
19588 let start = snapshot.anchor_before(start);
19589 let range = start..buffer_position;
19590 let lsp_start = to_lsp(&start);
19591 let lsp_range = lsp::Range {
19592 start: lsp_start,
19593 end: lsp_end,
19594 };
19595 Some(Completion {
19596 replace_range: range,
19597 new_text: snippet.body.clone(),
19598 source: CompletionSource::Lsp {
19599 insert_range: None,
19600 server_id: LanguageServerId(usize::MAX),
19601 resolved: true,
19602 lsp_completion: Box::new(lsp::CompletionItem {
19603 label: snippet.prefix.first().unwrap().clone(),
19604 kind: Some(CompletionItemKind::SNIPPET),
19605 label_details: snippet.description.as_ref().map(|description| {
19606 lsp::CompletionItemLabelDetails {
19607 detail: Some(description.clone()),
19608 description: None,
19609 }
19610 }),
19611 insert_text_format: Some(InsertTextFormat::SNIPPET),
19612 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19613 lsp::InsertReplaceEdit {
19614 new_text: snippet.body.clone(),
19615 insert: lsp_range,
19616 replace: lsp_range,
19617 },
19618 )),
19619 filter_text: Some(snippet.body.clone()),
19620 sort_text: Some(char::MAX.to_string()),
19621 ..lsp::CompletionItem::default()
19622 }),
19623 lsp_defaults: None,
19624 },
19625 label: CodeLabel {
19626 text: matching_prefix.clone(),
19627 runs: Vec::new(),
19628 filter_range: 0..matching_prefix.len(),
19629 },
19630 icon_path: None,
19631 documentation: snippet.description.clone().map(|description| {
19632 CompletionDocumentation::SingleLine(description.into())
19633 }),
19634 insert_text_mode: None,
19635 confirm: None,
19636 })
19637 })
19638 .collect();
19639
19640 all_results.append(&mut result);
19641 }
19642
19643 Ok(all_results)
19644 })
19645}
19646
19647impl CompletionProvider for Entity<Project> {
19648 fn completions(
19649 &self,
19650 _excerpt_id: ExcerptId,
19651 buffer: &Entity<Buffer>,
19652 buffer_position: text::Anchor,
19653 options: CompletionContext,
19654 _window: &mut Window,
19655 cx: &mut Context<Editor>,
19656 ) -> Task<Result<Option<Vec<Completion>>>> {
19657 self.update(cx, |project, cx| {
19658 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19659 let project_completions = project.completions(buffer, buffer_position, options, cx);
19660 cx.background_spawn(async move {
19661 let snippets_completions = snippets.await?;
19662 match project_completions.await? {
19663 Some(mut completions) => {
19664 completions.extend(snippets_completions);
19665 Ok(Some(completions))
19666 }
19667 None => {
19668 if snippets_completions.is_empty() {
19669 Ok(None)
19670 } else {
19671 Ok(Some(snippets_completions))
19672 }
19673 }
19674 }
19675 })
19676 })
19677 }
19678
19679 fn resolve_completions(
19680 &self,
19681 buffer: Entity<Buffer>,
19682 completion_indices: Vec<usize>,
19683 completions: Rc<RefCell<Box<[Completion]>>>,
19684 cx: &mut Context<Editor>,
19685 ) -> Task<Result<bool>> {
19686 self.update(cx, |project, cx| {
19687 project.lsp_store().update(cx, |lsp_store, cx| {
19688 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19689 })
19690 })
19691 }
19692
19693 fn apply_additional_edits_for_completion(
19694 &self,
19695 buffer: Entity<Buffer>,
19696 completions: Rc<RefCell<Box<[Completion]>>>,
19697 completion_index: usize,
19698 push_to_history: bool,
19699 cx: &mut Context<Editor>,
19700 ) -> Task<Result<Option<language::Transaction>>> {
19701 self.update(cx, |project, cx| {
19702 project.lsp_store().update(cx, |lsp_store, cx| {
19703 lsp_store.apply_additional_edits_for_completion(
19704 buffer,
19705 completions,
19706 completion_index,
19707 push_to_history,
19708 cx,
19709 )
19710 })
19711 })
19712 }
19713
19714 fn is_completion_trigger(
19715 &self,
19716 buffer: &Entity<Buffer>,
19717 position: language::Anchor,
19718 text: &str,
19719 trigger_in_words: bool,
19720 cx: &mut Context<Editor>,
19721 ) -> bool {
19722 let mut chars = text.chars();
19723 let char = if let Some(char) = chars.next() {
19724 char
19725 } else {
19726 return false;
19727 };
19728 if chars.next().is_some() {
19729 return false;
19730 }
19731
19732 let buffer = buffer.read(cx);
19733 let snapshot = buffer.snapshot();
19734 if !snapshot.settings_at(position, cx).show_completions_on_input {
19735 return false;
19736 }
19737 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19738 if trigger_in_words && classifier.is_word(char) {
19739 return true;
19740 }
19741
19742 buffer.completion_triggers().contains(text)
19743 }
19744}
19745
19746impl SemanticsProvider for Entity<Project> {
19747 fn hover(
19748 &self,
19749 buffer: &Entity<Buffer>,
19750 position: text::Anchor,
19751 cx: &mut App,
19752 ) -> Option<Task<Vec<project::Hover>>> {
19753 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19754 }
19755
19756 fn document_highlights(
19757 &self,
19758 buffer: &Entity<Buffer>,
19759 position: text::Anchor,
19760 cx: &mut App,
19761 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19762 Some(self.update(cx, |project, cx| {
19763 project.document_highlights(buffer, position, cx)
19764 }))
19765 }
19766
19767 fn definitions(
19768 &self,
19769 buffer: &Entity<Buffer>,
19770 position: text::Anchor,
19771 kind: GotoDefinitionKind,
19772 cx: &mut App,
19773 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19774 Some(self.update(cx, |project, cx| match kind {
19775 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19776 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19777 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19778 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19779 }))
19780 }
19781
19782 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19783 // TODO: make this work for remote projects
19784 self.update(cx, |project, cx| {
19785 if project
19786 .active_debug_session(cx)
19787 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19788 {
19789 return true;
19790 }
19791
19792 buffer.update(cx, |buffer, cx| {
19793 project.any_language_server_supports_inlay_hints(buffer, cx)
19794 })
19795 })
19796 }
19797
19798 fn inline_values(
19799 &self,
19800 buffer_handle: Entity<Buffer>,
19801 range: Range<text::Anchor>,
19802 cx: &mut App,
19803 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19804 self.update(cx, |project, cx| {
19805 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19806
19807 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19808 })
19809 }
19810
19811 fn inlay_hints(
19812 &self,
19813 buffer_handle: Entity<Buffer>,
19814 range: Range<text::Anchor>,
19815 cx: &mut App,
19816 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19817 Some(self.update(cx, |project, cx| {
19818 project.inlay_hints(buffer_handle, range, cx)
19819 }))
19820 }
19821
19822 fn resolve_inlay_hint(
19823 &self,
19824 hint: InlayHint,
19825 buffer_handle: Entity<Buffer>,
19826 server_id: LanguageServerId,
19827 cx: &mut App,
19828 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19829 Some(self.update(cx, |project, cx| {
19830 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19831 }))
19832 }
19833
19834 fn range_for_rename(
19835 &self,
19836 buffer: &Entity<Buffer>,
19837 position: text::Anchor,
19838 cx: &mut App,
19839 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19840 Some(self.update(cx, |project, cx| {
19841 let buffer = buffer.clone();
19842 let task = project.prepare_rename(buffer.clone(), position, cx);
19843 cx.spawn(async move |_, cx| {
19844 Ok(match task.await? {
19845 PrepareRenameResponse::Success(range) => Some(range),
19846 PrepareRenameResponse::InvalidPosition => None,
19847 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19848 // Fallback on using TreeSitter info to determine identifier range
19849 buffer.update(cx, |buffer, _| {
19850 let snapshot = buffer.snapshot();
19851 let (range, kind) = snapshot.surrounding_word(position);
19852 if kind != Some(CharKind::Word) {
19853 return None;
19854 }
19855 Some(
19856 snapshot.anchor_before(range.start)
19857 ..snapshot.anchor_after(range.end),
19858 )
19859 })?
19860 }
19861 })
19862 })
19863 }))
19864 }
19865
19866 fn perform_rename(
19867 &self,
19868 buffer: &Entity<Buffer>,
19869 position: text::Anchor,
19870 new_name: String,
19871 cx: &mut App,
19872 ) -> Option<Task<Result<ProjectTransaction>>> {
19873 Some(self.update(cx, |project, cx| {
19874 project.perform_rename(buffer.clone(), position, new_name, cx)
19875 }))
19876 }
19877}
19878
19879fn inlay_hint_settings(
19880 location: Anchor,
19881 snapshot: &MultiBufferSnapshot,
19882 cx: &mut Context<Editor>,
19883) -> InlayHintSettings {
19884 let file = snapshot.file_at(location);
19885 let language = snapshot.language_at(location).map(|l| l.name());
19886 language_settings(language, file, cx).inlay_hints
19887}
19888
19889fn consume_contiguous_rows(
19890 contiguous_row_selections: &mut Vec<Selection<Point>>,
19891 selection: &Selection<Point>,
19892 display_map: &DisplaySnapshot,
19893 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19894) -> (MultiBufferRow, MultiBufferRow) {
19895 contiguous_row_selections.push(selection.clone());
19896 let start_row = MultiBufferRow(selection.start.row);
19897 let mut end_row = ending_row(selection, display_map);
19898
19899 while let Some(next_selection) = selections.peek() {
19900 if next_selection.start.row <= end_row.0 {
19901 end_row = ending_row(next_selection, display_map);
19902 contiguous_row_selections.push(selections.next().unwrap().clone());
19903 } else {
19904 break;
19905 }
19906 }
19907 (start_row, end_row)
19908}
19909
19910fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19911 if next_selection.end.column > 0 || next_selection.is_empty() {
19912 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19913 } else {
19914 MultiBufferRow(next_selection.end.row)
19915 }
19916}
19917
19918impl EditorSnapshot {
19919 pub fn remote_selections_in_range<'a>(
19920 &'a self,
19921 range: &'a Range<Anchor>,
19922 collaboration_hub: &dyn CollaborationHub,
19923 cx: &'a App,
19924 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19925 let participant_names = collaboration_hub.user_names(cx);
19926 let participant_indices = collaboration_hub.user_participant_indices(cx);
19927 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19928 let collaborators_by_replica_id = collaborators_by_peer_id
19929 .iter()
19930 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19931 .collect::<HashMap<_, _>>();
19932 self.buffer_snapshot
19933 .selections_in_range(range, false)
19934 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19935 if replica_id == AGENT_REPLICA_ID {
19936 Some(RemoteSelection {
19937 replica_id,
19938 selection,
19939 cursor_shape,
19940 line_mode,
19941 collaborator_id: CollaboratorId::Agent,
19942 user_name: Some("Agent".into()),
19943 color: cx.theme().players().agent(),
19944 })
19945 } else {
19946 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19947 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19948 let user_name = participant_names.get(&collaborator.user_id).cloned();
19949 Some(RemoteSelection {
19950 replica_id,
19951 selection,
19952 cursor_shape,
19953 line_mode,
19954 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
19955 user_name,
19956 color: if let Some(index) = participant_index {
19957 cx.theme().players().color_for_participant(index.0)
19958 } else {
19959 cx.theme().players().absent()
19960 },
19961 })
19962 }
19963 })
19964 }
19965
19966 pub fn hunks_for_ranges(
19967 &self,
19968 ranges: impl IntoIterator<Item = Range<Point>>,
19969 ) -> Vec<MultiBufferDiffHunk> {
19970 let mut hunks = Vec::new();
19971 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19972 HashMap::default();
19973 for query_range in ranges {
19974 let query_rows =
19975 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19976 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19977 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19978 ) {
19979 // Include deleted hunks that are adjacent to the query range, because
19980 // otherwise they would be missed.
19981 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19982 if hunk.status().is_deleted() {
19983 intersects_range |= hunk.row_range.start == query_rows.end;
19984 intersects_range |= hunk.row_range.end == query_rows.start;
19985 }
19986 if intersects_range {
19987 if !processed_buffer_rows
19988 .entry(hunk.buffer_id)
19989 .or_default()
19990 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19991 {
19992 continue;
19993 }
19994 hunks.push(hunk);
19995 }
19996 }
19997 }
19998
19999 hunks
20000 }
20001
20002 fn display_diff_hunks_for_rows<'a>(
20003 &'a self,
20004 display_rows: Range<DisplayRow>,
20005 folded_buffers: &'a HashSet<BufferId>,
20006 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20007 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20008 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20009
20010 self.buffer_snapshot
20011 .diff_hunks_in_range(buffer_start..buffer_end)
20012 .filter_map(|hunk| {
20013 if folded_buffers.contains(&hunk.buffer_id) {
20014 return None;
20015 }
20016
20017 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20018 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20019
20020 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20021 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20022
20023 let display_hunk = if hunk_display_start.column() != 0 {
20024 DisplayDiffHunk::Folded {
20025 display_row: hunk_display_start.row(),
20026 }
20027 } else {
20028 let mut end_row = hunk_display_end.row();
20029 if hunk_display_end.column() > 0 {
20030 end_row.0 += 1;
20031 }
20032 let is_created_file = hunk.is_created_file();
20033 DisplayDiffHunk::Unfolded {
20034 status: hunk.status(),
20035 diff_base_byte_range: hunk.diff_base_byte_range,
20036 display_row_range: hunk_display_start.row()..end_row,
20037 multi_buffer_range: Anchor::range_in_buffer(
20038 hunk.excerpt_id,
20039 hunk.buffer_id,
20040 hunk.buffer_range,
20041 ),
20042 is_created_file,
20043 }
20044 };
20045
20046 Some(display_hunk)
20047 })
20048 }
20049
20050 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20051 self.display_snapshot.buffer_snapshot.language_at(position)
20052 }
20053
20054 pub fn is_focused(&self) -> bool {
20055 self.is_focused
20056 }
20057
20058 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20059 self.placeholder_text.as_ref()
20060 }
20061
20062 pub fn scroll_position(&self) -> gpui::Point<f32> {
20063 self.scroll_anchor.scroll_position(&self.display_snapshot)
20064 }
20065
20066 fn gutter_dimensions(
20067 &self,
20068 font_id: FontId,
20069 font_size: Pixels,
20070 max_line_number_width: Pixels,
20071 cx: &App,
20072 ) -> Option<GutterDimensions> {
20073 if !self.show_gutter {
20074 return None;
20075 }
20076
20077 let descent = cx.text_system().descent(font_id, font_size);
20078 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20079 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20080
20081 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20082 matches!(
20083 ProjectSettings::get_global(cx).git.git_gutter,
20084 Some(GitGutterSetting::TrackedFiles)
20085 )
20086 });
20087 let gutter_settings = EditorSettings::get_global(cx).gutter;
20088 let show_line_numbers = self
20089 .show_line_numbers
20090 .unwrap_or(gutter_settings.line_numbers);
20091 let line_gutter_width = if show_line_numbers {
20092 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20093 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20094 max_line_number_width.max(min_width_for_number_on_gutter)
20095 } else {
20096 0.0.into()
20097 };
20098
20099 let show_code_actions = self
20100 .show_code_actions
20101 .unwrap_or(gutter_settings.code_actions);
20102
20103 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20104 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20105
20106 let git_blame_entries_width =
20107 self.git_blame_gutter_max_author_length
20108 .map(|max_author_length| {
20109 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20110 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20111
20112 /// The number of characters to dedicate to gaps and margins.
20113 const SPACING_WIDTH: usize = 4;
20114
20115 let max_char_count = max_author_length.min(renderer.max_author_length())
20116 + ::git::SHORT_SHA_LENGTH
20117 + MAX_RELATIVE_TIMESTAMP.len()
20118 + SPACING_WIDTH;
20119
20120 em_advance * max_char_count
20121 });
20122
20123 let is_singleton = self.buffer_snapshot.is_singleton();
20124
20125 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20126 left_padding += if !is_singleton {
20127 em_width * 4.0
20128 } else if show_code_actions || show_runnables || show_breakpoints {
20129 em_width * 3.0
20130 } else if show_git_gutter && show_line_numbers {
20131 em_width * 2.0
20132 } else if show_git_gutter || show_line_numbers {
20133 em_width
20134 } else {
20135 px(0.)
20136 };
20137
20138 let shows_folds = is_singleton && gutter_settings.folds;
20139
20140 let right_padding = if shows_folds && show_line_numbers {
20141 em_width * 4.0
20142 } else if shows_folds || (!is_singleton && show_line_numbers) {
20143 em_width * 3.0
20144 } else if show_line_numbers {
20145 em_width
20146 } else {
20147 px(0.)
20148 };
20149
20150 Some(GutterDimensions {
20151 left_padding,
20152 right_padding,
20153 width: line_gutter_width + left_padding + right_padding,
20154 margin: -descent,
20155 git_blame_entries_width,
20156 })
20157 }
20158
20159 pub fn render_crease_toggle(
20160 &self,
20161 buffer_row: MultiBufferRow,
20162 row_contains_cursor: bool,
20163 editor: Entity<Editor>,
20164 window: &mut Window,
20165 cx: &mut App,
20166 ) -> Option<AnyElement> {
20167 let folded = self.is_line_folded(buffer_row);
20168 let mut is_foldable = false;
20169
20170 if let Some(crease) = self
20171 .crease_snapshot
20172 .query_row(buffer_row, &self.buffer_snapshot)
20173 {
20174 is_foldable = true;
20175 match crease {
20176 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20177 if let Some(render_toggle) = render_toggle {
20178 let toggle_callback =
20179 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20180 if folded {
20181 editor.update(cx, |editor, cx| {
20182 editor.fold_at(buffer_row, window, cx)
20183 });
20184 } else {
20185 editor.update(cx, |editor, cx| {
20186 editor.unfold_at(buffer_row, window, cx)
20187 });
20188 }
20189 });
20190 return Some((render_toggle)(
20191 buffer_row,
20192 folded,
20193 toggle_callback,
20194 window,
20195 cx,
20196 ));
20197 }
20198 }
20199 }
20200 }
20201
20202 is_foldable |= self.starts_indent(buffer_row);
20203
20204 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20205 Some(
20206 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20207 .toggle_state(folded)
20208 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20209 if folded {
20210 this.unfold_at(buffer_row, window, cx);
20211 } else {
20212 this.fold_at(buffer_row, window, cx);
20213 }
20214 }))
20215 .into_any_element(),
20216 )
20217 } else {
20218 None
20219 }
20220 }
20221
20222 pub fn render_crease_trailer(
20223 &self,
20224 buffer_row: MultiBufferRow,
20225 window: &mut Window,
20226 cx: &mut App,
20227 ) -> Option<AnyElement> {
20228 let folded = self.is_line_folded(buffer_row);
20229 if let Crease::Inline { render_trailer, .. } = self
20230 .crease_snapshot
20231 .query_row(buffer_row, &self.buffer_snapshot)?
20232 {
20233 let render_trailer = render_trailer.as_ref()?;
20234 Some(render_trailer(buffer_row, folded, window, cx))
20235 } else {
20236 None
20237 }
20238 }
20239}
20240
20241impl Deref for EditorSnapshot {
20242 type Target = DisplaySnapshot;
20243
20244 fn deref(&self) -> &Self::Target {
20245 &self.display_snapshot
20246 }
20247}
20248
20249#[derive(Clone, Debug, PartialEq, Eq)]
20250pub enum EditorEvent {
20251 InputIgnored {
20252 text: Arc<str>,
20253 },
20254 InputHandled {
20255 utf16_range_to_replace: Option<Range<isize>>,
20256 text: Arc<str>,
20257 },
20258 ExcerptsAdded {
20259 buffer: Entity<Buffer>,
20260 predecessor: ExcerptId,
20261 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20262 },
20263 ExcerptsRemoved {
20264 ids: Vec<ExcerptId>,
20265 removed_buffer_ids: Vec<BufferId>,
20266 },
20267 BufferFoldToggled {
20268 ids: Vec<ExcerptId>,
20269 folded: bool,
20270 },
20271 ExcerptsEdited {
20272 ids: Vec<ExcerptId>,
20273 },
20274 ExcerptsExpanded {
20275 ids: Vec<ExcerptId>,
20276 },
20277 BufferEdited,
20278 Edited {
20279 transaction_id: clock::Lamport,
20280 },
20281 Reparsed(BufferId),
20282 Focused,
20283 FocusedIn,
20284 Blurred,
20285 DirtyChanged,
20286 Saved,
20287 TitleChanged,
20288 DiffBaseChanged,
20289 SelectionsChanged {
20290 local: bool,
20291 },
20292 ScrollPositionChanged {
20293 local: bool,
20294 autoscroll: bool,
20295 },
20296 Closed,
20297 TransactionUndone {
20298 transaction_id: clock::Lamport,
20299 },
20300 TransactionBegun {
20301 transaction_id: clock::Lamport,
20302 },
20303 Reloaded,
20304 CursorShapeChanged,
20305 PushedToNavHistory {
20306 anchor: Anchor,
20307 is_deactivate: bool,
20308 },
20309}
20310
20311impl EventEmitter<EditorEvent> for Editor {}
20312
20313impl Focusable for Editor {
20314 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20315 self.focus_handle.clone()
20316 }
20317}
20318
20319impl Render for Editor {
20320 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20321 let settings = ThemeSettings::get_global(cx);
20322
20323 let mut text_style = match self.mode {
20324 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20325 color: cx.theme().colors().editor_foreground,
20326 font_family: settings.ui_font.family.clone(),
20327 font_features: settings.ui_font.features.clone(),
20328 font_fallbacks: settings.ui_font.fallbacks.clone(),
20329 font_size: rems(0.875).into(),
20330 font_weight: settings.ui_font.weight,
20331 line_height: relative(settings.buffer_line_height.value()),
20332 ..Default::default()
20333 },
20334 EditorMode::Full { .. } => TextStyle {
20335 color: cx.theme().colors().editor_foreground,
20336 font_family: settings.buffer_font.family.clone(),
20337 font_features: settings.buffer_font.features.clone(),
20338 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20339 font_size: settings.buffer_font_size(cx).into(),
20340 font_weight: settings.buffer_font.weight,
20341 line_height: relative(settings.buffer_line_height.value()),
20342 ..Default::default()
20343 },
20344 };
20345 if let Some(text_style_refinement) = &self.text_style_refinement {
20346 text_style.refine(text_style_refinement)
20347 }
20348
20349 let background = match self.mode {
20350 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20351 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20352 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20353 };
20354
20355 EditorElement::new(
20356 &cx.entity(),
20357 EditorStyle {
20358 background,
20359 horizontal_padding: Pixels::default(),
20360 local_player: cx.theme().players().local(),
20361 text: text_style,
20362 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20363 syntax: cx.theme().syntax().clone(),
20364 status: cx.theme().status().clone(),
20365 inlay_hints_style: make_inlay_hints_style(cx),
20366 inline_completion_styles: make_suggestion_styles(cx),
20367 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20368 },
20369 )
20370 }
20371}
20372
20373impl EntityInputHandler for Editor {
20374 fn text_for_range(
20375 &mut self,
20376 range_utf16: Range<usize>,
20377 adjusted_range: &mut Option<Range<usize>>,
20378 _: &mut Window,
20379 cx: &mut Context<Self>,
20380 ) -> Option<String> {
20381 let snapshot = self.buffer.read(cx).read(cx);
20382 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20383 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20384 if (start.0..end.0) != range_utf16 {
20385 adjusted_range.replace(start.0..end.0);
20386 }
20387 Some(snapshot.text_for_range(start..end).collect())
20388 }
20389
20390 fn selected_text_range(
20391 &mut self,
20392 ignore_disabled_input: bool,
20393 _: &mut Window,
20394 cx: &mut Context<Self>,
20395 ) -> Option<UTF16Selection> {
20396 // Prevent the IME menu from appearing when holding down an alphabetic key
20397 // while input is disabled.
20398 if !ignore_disabled_input && !self.input_enabled {
20399 return None;
20400 }
20401
20402 let selection = self.selections.newest::<OffsetUtf16>(cx);
20403 let range = selection.range();
20404
20405 Some(UTF16Selection {
20406 range: range.start.0..range.end.0,
20407 reversed: selection.reversed,
20408 })
20409 }
20410
20411 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20412 let snapshot = self.buffer.read(cx).read(cx);
20413 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20414 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20415 }
20416
20417 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20418 self.clear_highlights::<InputComposition>(cx);
20419 self.ime_transaction.take();
20420 }
20421
20422 fn replace_text_in_range(
20423 &mut self,
20424 range_utf16: Option<Range<usize>>,
20425 text: &str,
20426 window: &mut Window,
20427 cx: &mut Context<Self>,
20428 ) {
20429 if !self.input_enabled {
20430 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20431 return;
20432 }
20433
20434 self.transact(window, cx, |this, window, cx| {
20435 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20436 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20437 Some(this.selection_replacement_ranges(range_utf16, cx))
20438 } else {
20439 this.marked_text_ranges(cx)
20440 };
20441
20442 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20443 let newest_selection_id = this.selections.newest_anchor().id;
20444 this.selections
20445 .all::<OffsetUtf16>(cx)
20446 .iter()
20447 .zip(ranges_to_replace.iter())
20448 .find_map(|(selection, range)| {
20449 if selection.id == newest_selection_id {
20450 Some(
20451 (range.start.0 as isize - selection.head().0 as isize)
20452 ..(range.end.0 as isize - selection.head().0 as isize),
20453 )
20454 } else {
20455 None
20456 }
20457 })
20458 });
20459
20460 cx.emit(EditorEvent::InputHandled {
20461 utf16_range_to_replace: range_to_replace,
20462 text: text.into(),
20463 });
20464
20465 if let Some(new_selected_ranges) = new_selected_ranges {
20466 this.change_selections(None, window, cx, |selections| {
20467 selections.select_ranges(new_selected_ranges)
20468 });
20469 this.backspace(&Default::default(), window, cx);
20470 }
20471
20472 this.handle_input(text, window, cx);
20473 });
20474
20475 if let Some(transaction) = self.ime_transaction {
20476 self.buffer.update(cx, |buffer, cx| {
20477 buffer.group_until_transaction(transaction, cx);
20478 });
20479 }
20480
20481 self.unmark_text(window, cx);
20482 }
20483
20484 fn replace_and_mark_text_in_range(
20485 &mut self,
20486 range_utf16: Option<Range<usize>>,
20487 text: &str,
20488 new_selected_range_utf16: Option<Range<usize>>,
20489 window: &mut Window,
20490 cx: &mut Context<Self>,
20491 ) {
20492 if !self.input_enabled {
20493 return;
20494 }
20495
20496 let transaction = self.transact(window, cx, |this, window, cx| {
20497 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20498 let snapshot = this.buffer.read(cx).read(cx);
20499 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20500 for marked_range in &mut marked_ranges {
20501 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20502 marked_range.start.0 += relative_range_utf16.start;
20503 marked_range.start =
20504 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20505 marked_range.end =
20506 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20507 }
20508 }
20509 Some(marked_ranges)
20510 } else if let Some(range_utf16) = range_utf16 {
20511 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20512 Some(this.selection_replacement_ranges(range_utf16, cx))
20513 } else {
20514 None
20515 };
20516
20517 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20518 let newest_selection_id = this.selections.newest_anchor().id;
20519 this.selections
20520 .all::<OffsetUtf16>(cx)
20521 .iter()
20522 .zip(ranges_to_replace.iter())
20523 .find_map(|(selection, range)| {
20524 if selection.id == newest_selection_id {
20525 Some(
20526 (range.start.0 as isize - selection.head().0 as isize)
20527 ..(range.end.0 as isize - selection.head().0 as isize),
20528 )
20529 } else {
20530 None
20531 }
20532 })
20533 });
20534
20535 cx.emit(EditorEvent::InputHandled {
20536 utf16_range_to_replace: range_to_replace,
20537 text: text.into(),
20538 });
20539
20540 if let Some(ranges) = ranges_to_replace {
20541 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20542 }
20543
20544 let marked_ranges = {
20545 let snapshot = this.buffer.read(cx).read(cx);
20546 this.selections
20547 .disjoint_anchors()
20548 .iter()
20549 .map(|selection| {
20550 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20551 })
20552 .collect::<Vec<_>>()
20553 };
20554
20555 if text.is_empty() {
20556 this.unmark_text(window, cx);
20557 } else {
20558 this.highlight_text::<InputComposition>(
20559 marked_ranges.clone(),
20560 HighlightStyle {
20561 underline: Some(UnderlineStyle {
20562 thickness: px(1.),
20563 color: None,
20564 wavy: false,
20565 }),
20566 ..Default::default()
20567 },
20568 cx,
20569 );
20570 }
20571
20572 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20573 let use_autoclose = this.use_autoclose;
20574 let use_auto_surround = this.use_auto_surround;
20575 this.set_use_autoclose(false);
20576 this.set_use_auto_surround(false);
20577 this.handle_input(text, window, cx);
20578 this.set_use_autoclose(use_autoclose);
20579 this.set_use_auto_surround(use_auto_surround);
20580
20581 if let Some(new_selected_range) = new_selected_range_utf16 {
20582 let snapshot = this.buffer.read(cx).read(cx);
20583 let new_selected_ranges = marked_ranges
20584 .into_iter()
20585 .map(|marked_range| {
20586 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20587 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20588 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20589 snapshot.clip_offset_utf16(new_start, Bias::Left)
20590 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20591 })
20592 .collect::<Vec<_>>();
20593
20594 drop(snapshot);
20595 this.change_selections(None, window, cx, |selections| {
20596 selections.select_ranges(new_selected_ranges)
20597 });
20598 }
20599 });
20600
20601 self.ime_transaction = self.ime_transaction.or(transaction);
20602 if let Some(transaction) = self.ime_transaction {
20603 self.buffer.update(cx, |buffer, cx| {
20604 buffer.group_until_transaction(transaction, cx);
20605 });
20606 }
20607
20608 if self.text_highlights::<InputComposition>(cx).is_none() {
20609 self.ime_transaction.take();
20610 }
20611 }
20612
20613 fn bounds_for_range(
20614 &mut self,
20615 range_utf16: Range<usize>,
20616 element_bounds: gpui::Bounds<Pixels>,
20617 window: &mut Window,
20618 cx: &mut Context<Self>,
20619 ) -> Option<gpui::Bounds<Pixels>> {
20620 let text_layout_details = self.text_layout_details(window);
20621 let gpui::Size {
20622 width: em_width,
20623 height: line_height,
20624 } = self.character_size(window);
20625
20626 let snapshot = self.snapshot(window, cx);
20627 let scroll_position = snapshot.scroll_position();
20628 let scroll_left = scroll_position.x * em_width;
20629
20630 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20631 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20632 + self.gutter_dimensions.width
20633 + self.gutter_dimensions.margin;
20634 let y = line_height * (start.row().as_f32() - scroll_position.y);
20635
20636 Some(Bounds {
20637 origin: element_bounds.origin + point(x, y),
20638 size: size(em_width, line_height),
20639 })
20640 }
20641
20642 fn character_index_for_point(
20643 &mut self,
20644 point: gpui::Point<Pixels>,
20645 _window: &mut Window,
20646 _cx: &mut Context<Self>,
20647 ) -> Option<usize> {
20648 let position_map = self.last_position_map.as_ref()?;
20649 if !position_map.text_hitbox.contains(&point) {
20650 return None;
20651 }
20652 let display_point = position_map.point_for_position(point).previous_valid;
20653 let anchor = position_map
20654 .snapshot
20655 .display_point_to_anchor(display_point, Bias::Left);
20656 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20657 Some(utf16_offset.0)
20658 }
20659}
20660
20661trait SelectionExt {
20662 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20663 fn spanned_rows(
20664 &self,
20665 include_end_if_at_line_start: bool,
20666 map: &DisplaySnapshot,
20667 ) -> Range<MultiBufferRow>;
20668}
20669
20670impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20671 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20672 let start = self
20673 .start
20674 .to_point(&map.buffer_snapshot)
20675 .to_display_point(map);
20676 let end = self
20677 .end
20678 .to_point(&map.buffer_snapshot)
20679 .to_display_point(map);
20680 if self.reversed {
20681 end..start
20682 } else {
20683 start..end
20684 }
20685 }
20686
20687 fn spanned_rows(
20688 &self,
20689 include_end_if_at_line_start: bool,
20690 map: &DisplaySnapshot,
20691 ) -> Range<MultiBufferRow> {
20692 let start = self.start.to_point(&map.buffer_snapshot);
20693 let mut end = self.end.to_point(&map.buffer_snapshot);
20694 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20695 end.row -= 1;
20696 }
20697
20698 let buffer_start = map.prev_line_boundary(start).0;
20699 let buffer_end = map.next_line_boundary(end).0;
20700 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20701 }
20702}
20703
20704impl<T: InvalidationRegion> InvalidationStack<T> {
20705 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20706 where
20707 S: Clone + ToOffset,
20708 {
20709 while let Some(region) = self.last() {
20710 let all_selections_inside_invalidation_ranges =
20711 if selections.len() == region.ranges().len() {
20712 selections
20713 .iter()
20714 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20715 .all(|(selection, invalidation_range)| {
20716 let head = selection.head().to_offset(buffer);
20717 invalidation_range.start <= head && invalidation_range.end >= head
20718 })
20719 } else {
20720 false
20721 };
20722
20723 if all_selections_inside_invalidation_ranges {
20724 break;
20725 } else {
20726 self.pop();
20727 }
20728 }
20729 }
20730}
20731
20732impl<T> Default for InvalidationStack<T> {
20733 fn default() -> Self {
20734 Self(Default::default())
20735 }
20736}
20737
20738impl<T> Deref for InvalidationStack<T> {
20739 type Target = Vec<T>;
20740
20741 fn deref(&self) -> &Self::Target {
20742 &self.0
20743 }
20744}
20745
20746impl<T> DerefMut for InvalidationStack<T> {
20747 fn deref_mut(&mut self) -> &mut Self::Target {
20748 &mut self.0
20749 }
20750}
20751
20752impl InvalidationRegion for SnippetState {
20753 fn ranges(&self) -> &[Range<Anchor>] {
20754 &self.ranges[self.active_index]
20755 }
20756}
20757
20758fn inline_completion_edit_text(
20759 current_snapshot: &BufferSnapshot,
20760 edits: &[(Range<Anchor>, String)],
20761 edit_preview: &EditPreview,
20762 include_deletions: bool,
20763 cx: &App,
20764) -> HighlightedText {
20765 let edits = edits
20766 .iter()
20767 .map(|(anchor, text)| {
20768 (
20769 anchor.start.text_anchor..anchor.end.text_anchor,
20770 text.clone(),
20771 )
20772 })
20773 .collect::<Vec<_>>();
20774
20775 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20776}
20777
20778pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20779 match severity {
20780 DiagnosticSeverity::ERROR => colors.error,
20781 DiagnosticSeverity::WARNING => colors.warning,
20782 DiagnosticSeverity::INFORMATION => colors.info,
20783 DiagnosticSeverity::HINT => colors.info,
20784 _ => colors.ignored,
20785 }
20786}
20787
20788pub fn styled_runs_for_code_label<'a>(
20789 label: &'a CodeLabel,
20790 syntax_theme: &'a theme::SyntaxTheme,
20791) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20792 let fade_out = HighlightStyle {
20793 fade_out: Some(0.35),
20794 ..Default::default()
20795 };
20796
20797 let mut prev_end = label.filter_range.end;
20798 label
20799 .runs
20800 .iter()
20801 .enumerate()
20802 .flat_map(move |(ix, (range, highlight_id))| {
20803 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20804 style
20805 } else {
20806 return Default::default();
20807 };
20808 let mut muted_style = style;
20809 muted_style.highlight(fade_out);
20810
20811 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20812 if range.start >= label.filter_range.end {
20813 if range.start > prev_end {
20814 runs.push((prev_end..range.start, fade_out));
20815 }
20816 runs.push((range.clone(), muted_style));
20817 } else if range.end <= label.filter_range.end {
20818 runs.push((range.clone(), style));
20819 } else {
20820 runs.push((range.start..label.filter_range.end, style));
20821 runs.push((label.filter_range.end..range.end, muted_style));
20822 }
20823 prev_end = cmp::max(prev_end, range.end);
20824
20825 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20826 runs.push((prev_end..label.text.len(), fade_out));
20827 }
20828
20829 runs
20830 })
20831}
20832
20833pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20834 let mut prev_index = 0;
20835 let mut prev_codepoint: Option<char> = None;
20836 text.char_indices()
20837 .chain([(text.len(), '\0')])
20838 .filter_map(move |(index, codepoint)| {
20839 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20840 let is_boundary = index == text.len()
20841 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20842 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20843 if is_boundary {
20844 let chunk = &text[prev_index..index];
20845 prev_index = index;
20846 Some(chunk)
20847 } else {
20848 None
20849 }
20850 })
20851}
20852
20853pub trait RangeToAnchorExt: Sized {
20854 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20855
20856 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20857 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20858 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20859 }
20860}
20861
20862impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20863 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20864 let start_offset = self.start.to_offset(snapshot);
20865 let end_offset = self.end.to_offset(snapshot);
20866 if start_offset == end_offset {
20867 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20868 } else {
20869 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20870 }
20871 }
20872}
20873
20874pub trait RowExt {
20875 fn as_f32(&self) -> f32;
20876
20877 fn next_row(&self) -> Self;
20878
20879 fn previous_row(&self) -> Self;
20880
20881 fn minus(&self, other: Self) -> u32;
20882}
20883
20884impl RowExt for DisplayRow {
20885 fn as_f32(&self) -> f32 {
20886 self.0 as f32
20887 }
20888
20889 fn next_row(&self) -> Self {
20890 Self(self.0 + 1)
20891 }
20892
20893 fn previous_row(&self) -> Self {
20894 Self(self.0.saturating_sub(1))
20895 }
20896
20897 fn minus(&self, other: Self) -> u32 {
20898 self.0 - other.0
20899 }
20900}
20901
20902impl RowExt for MultiBufferRow {
20903 fn as_f32(&self) -> f32 {
20904 self.0 as f32
20905 }
20906
20907 fn next_row(&self) -> Self {
20908 Self(self.0 + 1)
20909 }
20910
20911 fn previous_row(&self) -> Self {
20912 Self(self.0.saturating_sub(1))
20913 }
20914
20915 fn minus(&self, other: Self) -> u32 {
20916 self.0 - other.0
20917 }
20918}
20919
20920trait RowRangeExt {
20921 type Row;
20922
20923 fn len(&self) -> usize;
20924
20925 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20926}
20927
20928impl RowRangeExt for Range<MultiBufferRow> {
20929 type Row = MultiBufferRow;
20930
20931 fn len(&self) -> usize {
20932 (self.end.0 - self.start.0) as usize
20933 }
20934
20935 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20936 (self.start.0..self.end.0).map(MultiBufferRow)
20937 }
20938}
20939
20940impl RowRangeExt for Range<DisplayRow> {
20941 type Row = DisplayRow;
20942
20943 fn len(&self) -> usize {
20944 (self.end.0 - self.start.0) as usize
20945 }
20946
20947 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20948 (self.start.0..self.end.0).map(DisplayRow)
20949 }
20950}
20951
20952/// If select range has more than one line, we
20953/// just point the cursor to range.start.
20954fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20955 if range.start.row == range.end.row {
20956 range
20957 } else {
20958 range.start..range.start
20959 }
20960}
20961pub struct KillRing(ClipboardItem);
20962impl Global for KillRing {}
20963
20964const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20965
20966enum BreakpointPromptEditAction {
20967 Log,
20968 Condition,
20969 HitCondition,
20970}
20971
20972struct BreakpointPromptEditor {
20973 pub(crate) prompt: Entity<Editor>,
20974 editor: WeakEntity<Editor>,
20975 breakpoint_anchor: Anchor,
20976 breakpoint: Breakpoint,
20977 edit_action: BreakpointPromptEditAction,
20978 block_ids: HashSet<CustomBlockId>,
20979 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20980 _subscriptions: Vec<Subscription>,
20981}
20982
20983impl BreakpointPromptEditor {
20984 const MAX_LINES: u8 = 4;
20985
20986 fn new(
20987 editor: WeakEntity<Editor>,
20988 breakpoint_anchor: Anchor,
20989 breakpoint: Breakpoint,
20990 edit_action: BreakpointPromptEditAction,
20991 window: &mut Window,
20992 cx: &mut Context<Self>,
20993 ) -> Self {
20994 let base_text = match edit_action {
20995 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20996 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20997 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20998 }
20999 .map(|msg| msg.to_string())
21000 .unwrap_or_default();
21001
21002 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21003 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21004
21005 let prompt = cx.new(|cx| {
21006 let mut prompt = Editor::new(
21007 EditorMode::AutoHeight {
21008 max_lines: Self::MAX_LINES as usize,
21009 },
21010 buffer,
21011 None,
21012 window,
21013 cx,
21014 );
21015 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21016 prompt.set_show_cursor_when_unfocused(false, cx);
21017 prompt.set_placeholder_text(
21018 match edit_action {
21019 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21020 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21021 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21022 },
21023 cx,
21024 );
21025
21026 prompt
21027 });
21028
21029 Self {
21030 prompt,
21031 editor,
21032 breakpoint_anchor,
21033 breakpoint,
21034 edit_action,
21035 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
21036 block_ids: Default::default(),
21037 _subscriptions: vec![],
21038 }
21039 }
21040
21041 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21042 self.block_ids.extend(block_ids)
21043 }
21044
21045 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21046 if let Some(editor) = self.editor.upgrade() {
21047 let message = self
21048 .prompt
21049 .read(cx)
21050 .buffer
21051 .read(cx)
21052 .as_singleton()
21053 .expect("A multi buffer in breakpoint prompt isn't possible")
21054 .read(cx)
21055 .as_rope()
21056 .to_string();
21057
21058 editor.update(cx, |editor, cx| {
21059 editor.edit_breakpoint_at_anchor(
21060 self.breakpoint_anchor,
21061 self.breakpoint.clone(),
21062 match self.edit_action {
21063 BreakpointPromptEditAction::Log => {
21064 BreakpointEditAction::EditLogMessage(message.into())
21065 }
21066 BreakpointPromptEditAction::Condition => {
21067 BreakpointEditAction::EditCondition(message.into())
21068 }
21069 BreakpointPromptEditAction::HitCondition => {
21070 BreakpointEditAction::EditHitCondition(message.into())
21071 }
21072 },
21073 cx,
21074 );
21075
21076 editor.remove_blocks(self.block_ids.clone(), None, cx);
21077 cx.focus_self(window);
21078 });
21079 }
21080 }
21081
21082 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21083 self.editor
21084 .update(cx, |editor, cx| {
21085 editor.remove_blocks(self.block_ids.clone(), None, cx);
21086 window.focus(&editor.focus_handle);
21087 })
21088 .log_err();
21089 }
21090
21091 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21092 let settings = ThemeSettings::get_global(cx);
21093 let text_style = TextStyle {
21094 color: if self.prompt.read(cx).read_only(cx) {
21095 cx.theme().colors().text_disabled
21096 } else {
21097 cx.theme().colors().text
21098 },
21099 font_family: settings.buffer_font.family.clone(),
21100 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21101 font_size: settings.buffer_font_size(cx).into(),
21102 font_weight: settings.buffer_font.weight,
21103 line_height: relative(settings.buffer_line_height.value()),
21104 ..Default::default()
21105 };
21106 EditorElement::new(
21107 &self.prompt,
21108 EditorStyle {
21109 background: cx.theme().colors().editor_background,
21110 local_player: cx.theme().players().local(),
21111 text: text_style,
21112 ..Default::default()
21113 },
21114 )
21115 }
21116}
21117
21118impl Render for BreakpointPromptEditor {
21119 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21120 let gutter_dimensions = *self.gutter_dimensions.lock();
21121 h_flex()
21122 .key_context("Editor")
21123 .bg(cx.theme().colors().editor_background)
21124 .border_y_1()
21125 .border_color(cx.theme().status().info_border)
21126 .size_full()
21127 .py(window.line_height() / 2.5)
21128 .on_action(cx.listener(Self::confirm))
21129 .on_action(cx.listener(Self::cancel))
21130 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21131 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21132 }
21133}
21134
21135impl Focusable for BreakpointPromptEditor {
21136 fn focus_handle(&self, cx: &App) -> FocusHandle {
21137 self.prompt.focus_handle(cx)
21138 }
21139}
21140
21141fn all_edits_insertions_or_deletions(
21142 edits: &Vec<(Range<Anchor>, String)>,
21143 snapshot: &MultiBufferSnapshot,
21144) -> bool {
21145 let mut all_insertions = true;
21146 let mut all_deletions = true;
21147
21148 for (range, new_text) in edits.iter() {
21149 let range_is_empty = range.to_offset(&snapshot).is_empty();
21150 let text_is_empty = new_text.is_empty();
21151
21152 if range_is_empty != text_is_empty {
21153 if range_is_empty {
21154 all_deletions = false;
21155 } else {
21156 all_insertions = false;
21157 }
21158 } else {
21159 return false;
21160 }
21161
21162 if !all_insertions && !all_deletions {
21163 return false;
21164 }
21165 }
21166 all_insertions || all_deletions
21167}
21168
21169struct MissingEditPredictionKeybindingTooltip;
21170
21171impl Render for MissingEditPredictionKeybindingTooltip {
21172 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21173 ui::tooltip_container(window, cx, |container, _, cx| {
21174 container
21175 .flex_shrink_0()
21176 .max_w_80()
21177 .min_h(rems_from_px(124.))
21178 .justify_between()
21179 .child(
21180 v_flex()
21181 .flex_1()
21182 .text_ui_sm(cx)
21183 .child(Label::new("Conflict with Accept Keybinding"))
21184 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21185 )
21186 .child(
21187 h_flex()
21188 .pb_1()
21189 .gap_1()
21190 .items_end()
21191 .w_full()
21192 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21193 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21194 }))
21195 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21196 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21197 })),
21198 )
21199 })
21200 }
21201}
21202
21203#[derive(Debug, Clone, Copy, PartialEq)]
21204pub struct LineHighlight {
21205 pub background: Background,
21206 pub border: Option<gpui::Hsla>,
21207 pub include_gutter: bool,
21208 pub type_id: Option<TypeId>,
21209}
21210
21211fn render_diff_hunk_controls(
21212 row: u32,
21213 status: &DiffHunkStatus,
21214 hunk_range: Range<Anchor>,
21215 is_created_file: bool,
21216 line_height: Pixels,
21217 editor: &Entity<Editor>,
21218 _window: &mut Window,
21219 cx: &mut App,
21220) -> AnyElement {
21221 h_flex()
21222 .h(line_height)
21223 .mr_1()
21224 .gap_1()
21225 .px_0p5()
21226 .pb_1()
21227 .border_x_1()
21228 .border_b_1()
21229 .border_color(cx.theme().colors().border_variant)
21230 .rounded_b_lg()
21231 .bg(cx.theme().colors().editor_background)
21232 .gap_1()
21233 .occlude()
21234 .shadow_md()
21235 .child(if status.has_secondary_hunk() {
21236 Button::new(("stage", row as u64), "Stage")
21237 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21238 .tooltip({
21239 let focus_handle = editor.focus_handle(cx);
21240 move |window, cx| {
21241 Tooltip::for_action_in(
21242 "Stage Hunk",
21243 &::git::ToggleStaged,
21244 &focus_handle,
21245 window,
21246 cx,
21247 )
21248 }
21249 })
21250 .on_click({
21251 let editor = editor.clone();
21252 move |_event, _window, cx| {
21253 editor.update(cx, |editor, cx| {
21254 editor.stage_or_unstage_diff_hunks(
21255 true,
21256 vec![hunk_range.start..hunk_range.start],
21257 cx,
21258 );
21259 });
21260 }
21261 })
21262 } else {
21263 Button::new(("unstage", row as u64), "Unstage")
21264 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21265 .tooltip({
21266 let focus_handle = editor.focus_handle(cx);
21267 move |window, cx| {
21268 Tooltip::for_action_in(
21269 "Unstage Hunk",
21270 &::git::ToggleStaged,
21271 &focus_handle,
21272 window,
21273 cx,
21274 )
21275 }
21276 })
21277 .on_click({
21278 let editor = editor.clone();
21279 move |_event, _window, cx| {
21280 editor.update(cx, |editor, cx| {
21281 editor.stage_or_unstage_diff_hunks(
21282 false,
21283 vec![hunk_range.start..hunk_range.start],
21284 cx,
21285 );
21286 });
21287 }
21288 })
21289 })
21290 .child(
21291 Button::new(("restore", row as u64), "Restore")
21292 .tooltip({
21293 let focus_handle = editor.focus_handle(cx);
21294 move |window, cx| {
21295 Tooltip::for_action_in(
21296 "Restore Hunk",
21297 &::git::Restore,
21298 &focus_handle,
21299 window,
21300 cx,
21301 )
21302 }
21303 })
21304 .on_click({
21305 let editor = editor.clone();
21306 move |_event, window, cx| {
21307 editor.update(cx, |editor, cx| {
21308 let snapshot = editor.snapshot(window, cx);
21309 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21310 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21311 });
21312 }
21313 })
21314 .disabled(is_created_file),
21315 )
21316 .when(
21317 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21318 |el| {
21319 el.child(
21320 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21321 .shape(IconButtonShape::Square)
21322 .icon_size(IconSize::Small)
21323 // .disabled(!has_multiple_hunks)
21324 .tooltip({
21325 let focus_handle = editor.focus_handle(cx);
21326 move |window, cx| {
21327 Tooltip::for_action_in(
21328 "Next Hunk",
21329 &GoToHunk,
21330 &focus_handle,
21331 window,
21332 cx,
21333 )
21334 }
21335 })
21336 .on_click({
21337 let editor = editor.clone();
21338 move |_event, window, cx| {
21339 editor.update(cx, |editor, cx| {
21340 let snapshot = editor.snapshot(window, cx);
21341 let position =
21342 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21343 editor.go_to_hunk_before_or_after_position(
21344 &snapshot,
21345 position,
21346 Direction::Next,
21347 window,
21348 cx,
21349 );
21350 editor.expand_selected_diff_hunks(cx);
21351 });
21352 }
21353 }),
21354 )
21355 .child(
21356 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21357 .shape(IconButtonShape::Square)
21358 .icon_size(IconSize::Small)
21359 // .disabled(!has_multiple_hunks)
21360 .tooltip({
21361 let focus_handle = editor.focus_handle(cx);
21362 move |window, cx| {
21363 Tooltip::for_action_in(
21364 "Previous Hunk",
21365 &GoToPreviousHunk,
21366 &focus_handle,
21367 window,
21368 cx,
21369 )
21370 }
21371 })
21372 .on_click({
21373 let editor = editor.clone();
21374 move |_event, window, cx| {
21375 editor.update(cx, |editor, cx| {
21376 let snapshot = editor.snapshot(window, cx);
21377 let point =
21378 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21379 editor.go_to_hunk_before_or_after_position(
21380 &snapshot,
21381 point,
21382 Direction::Prev,
21383 window,
21384 cx,
21385 );
21386 editor.expand_selected_diff_hunks(cx);
21387 });
21388 }
21389 }),
21390 )
21391 },
21392 )
21393 .into_any_element()
21394}