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;
18pub mod 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_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::DiagnosticSeverity,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: Option<usize>,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub border: Hsla,
551 pub local_player: PlayerColor,
552 pub text: TextStyle,
553 pub scrollbar_width: Pixels,
554 pub syntax: Arc<SyntaxTheme>,
555 pub status: StatusColors,
556 pub inlay_hints_style: HighlightStyle,
557 pub inline_completion_styles: InlineCompletionStyles,
558 pub unnecessary_code_fade: f32,
559 pub show_underlines: bool,
560}
561
562impl Default for EditorStyle {
563 fn default() -> Self {
564 Self {
565 background: Hsla::default(),
566 border: Hsla::default(),
567 local_player: PlayerColor::default(),
568 text: TextStyle::default(),
569 scrollbar_width: Pixels::default(),
570 syntax: Default::default(),
571 // HACK: Status colors don't have a real default.
572 // We should look into removing the status colors from the editor
573 // style and retrieve them directly from the theme.
574 status: StatusColors::dark(),
575 inlay_hints_style: HighlightStyle::default(),
576 inline_completion_styles: InlineCompletionStyles {
577 insertion: HighlightStyle::default(),
578 whitespace: HighlightStyle::default(),
579 },
580 unnecessary_code_fade: Default::default(),
581 show_underlines: true,
582 }
583 }
584}
585
586pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
587 let show_background = language_settings::language_settings(None, None, cx)
588 .inlay_hints
589 .show_background;
590
591 HighlightStyle {
592 color: Some(cx.theme().status().hint),
593 background_color: show_background.then(|| cx.theme().status().hint_background),
594 ..HighlightStyle::default()
595 }
596}
597
598pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
599 InlineCompletionStyles {
600 insertion: HighlightStyle {
601 color: Some(cx.theme().status().predictive),
602 ..HighlightStyle::default()
603 },
604 whitespace: HighlightStyle {
605 background_color: Some(cx.theme().status().created_background),
606 ..HighlightStyle::default()
607 },
608 }
609}
610
611type CompletionId = usize;
612
613pub(crate) enum EditDisplayMode {
614 TabAccept,
615 DiffPopover,
616 Inline,
617}
618
619enum InlineCompletion {
620 Edit {
621 edits: Vec<(Range<Anchor>, String)>,
622 edit_preview: Option<EditPreview>,
623 display_mode: EditDisplayMode,
624 snapshot: BufferSnapshot,
625 },
626 Move {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct InlineCompletionState {
633 inlay_ids: Vec<InlayId>,
634 completion: InlineCompletion,
635 completion_id: Option<SharedString>,
636 invalidation_range: Range<Anchor>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum InlineCompletionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuInlineCompletionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 match *self {
775 Self::Disabled => true,
776 _ => false,
777 }
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Clone, Debug)]
815struct RunnableTasks {
816 templates: Vec<(TaskSourceKind, TaskTemplate)>,
817 offset: multi_buffer::Anchor,
818 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
819 column: u32,
820 // Values of all named captures, including those starting with '_'
821 extra_variables: HashMap<String, String>,
822 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
823 context_range: Range<BufferOffset>,
824}
825
826impl RunnableTasks {
827 fn resolve<'a>(
828 &'a self,
829 cx: &'a task::TaskContext,
830 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
831 self.templates.iter().filter_map(|(kind, template)| {
832 template
833 .resolve_task(&kind.to_id_base(), cx)
834 .map(|task| (kind.clone(), task))
835 })
836 }
837}
838
839#[derive(Clone)]
840pub struct ResolvedTasks {
841 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
842 position: Anchor,
843}
844
845#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
846struct BufferOffset(usize);
847
848// Addons allow storing per-editor state in other crates (e.g. Vim)
849pub trait Addon: 'static {
850 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
851
852 fn render_buffer_header_controls(
853 &self,
854 _: &ExcerptInfo,
855 _: &Window,
856 _: &App,
857 ) -> Option<AnyElement> {
858 None
859 }
860
861 fn to_any(&self) -> &dyn std::any::Any;
862
863 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
864 None
865 }
866}
867
868/// A set of caret positions, registered when the editor was edited.
869pub struct ChangeList {
870 changes: Vec<Vec<Anchor>>,
871 /// Currently "selected" change.
872 position: Option<usize>,
873}
874
875impl ChangeList {
876 pub fn new() -> Self {
877 Self {
878 changes: Vec::new(),
879 position: None,
880 }
881 }
882
883 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
884 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
885 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
886 if self.changes.is_empty() {
887 return None;
888 }
889
890 let prev = self.position.unwrap_or(self.changes.len());
891 let next = if direction == Direction::Prev {
892 prev.saturating_sub(count)
893 } else {
894 (prev + count).min(self.changes.len() - 1)
895 };
896 self.position = Some(next);
897 self.changes.get(next).map(|anchors| anchors.as_slice())
898 }
899
900 /// Adds a new change to the list, resetting the change list position.
901 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
902 self.position.take();
903 if pop_state {
904 self.changes.pop();
905 }
906 self.changes.push(new_positions.clone());
907 }
908
909 pub fn last(&self) -> Option<&[Anchor]> {
910 self.changes.last().map(|anchors| anchors.as_slice())
911 }
912}
913
914#[derive(Clone)]
915struct InlineBlamePopoverState {
916 scroll_handle: ScrollHandle,
917 commit_message: Option<ParsedCommitMessage>,
918 markdown: Entity<Markdown>,
919}
920
921struct InlineBlamePopover {
922 position: gpui::Point<Pixels>,
923 hide_task: Option<Task<()>>,
924 popover_bounds: Option<Bounds<Pixels>>,
925 popover_state: InlineBlamePopoverState,
926}
927
928enum SelectionDragState {
929 /// State when no drag related activity is detected.
930 None,
931 /// State when the mouse is down on a selection that is about to be dragged.
932 ReadyToDrag {
933 selection: Selection<Anchor>,
934 click_position: gpui::Point<Pixels>,
935 mouse_down_time: Instant,
936 },
937 /// State when the mouse is dragging the selection in the editor.
938 Dragging {
939 selection: Selection<Anchor>,
940 drop_cursor: Selection<Anchor>,
941 hide_drop_cursor: bool,
942 },
943}
944
945enum ColumnarSelectionState {
946 FromMouse {
947 selection_tail: Anchor,
948 display_point: Option<DisplayPoint>,
949 },
950 FromSelection {
951 selection_tail: Anchor,
952 },
953}
954
955/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
956/// a breakpoint on them.
957#[derive(Clone, Copy, Debug, PartialEq, Eq)]
958struct PhantomBreakpointIndicator {
959 display_row: DisplayRow,
960 /// There's a small debounce between hovering over the line and showing the indicator.
961 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
962 is_active: bool,
963 collides_with_existing_breakpoint: bool,
964}
965
966/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
967///
968/// See the [module level documentation](self) for more information.
969pub struct Editor {
970 focus_handle: FocusHandle,
971 last_focused_descendant: Option<WeakFocusHandle>,
972 /// The text buffer being edited
973 buffer: Entity<MultiBuffer>,
974 /// Map of how text in the buffer should be displayed.
975 /// Handles soft wraps, folds, fake inlay text insertions, etc.
976 pub display_map: Entity<DisplayMap>,
977 pub selections: SelectionsCollection,
978 pub scroll_manager: ScrollManager,
979 /// When inline assist editors are linked, they all render cursors because
980 /// typing enters text into each of them, even the ones that aren't focused.
981 pub(crate) show_cursor_when_unfocused: bool,
982 columnar_selection_state: Option<ColumnarSelectionState>,
983 add_selections_state: Option<AddSelectionsState>,
984 select_next_state: Option<SelectNextState>,
985 select_prev_state: Option<SelectNextState>,
986 selection_history: SelectionHistory,
987 defer_selection_effects: bool,
988 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
989 autoclose_regions: Vec<AutocloseRegion>,
990 snippet_stack: InvalidationStack<SnippetState>,
991 select_syntax_node_history: SelectSyntaxNodeHistory,
992 ime_transaction: Option<TransactionId>,
993 pub diagnostics_max_severity: DiagnosticSeverity,
994 active_diagnostics: ActiveDiagnostic,
995 show_inline_diagnostics: bool,
996 inline_diagnostics_update: Task<()>,
997 inline_diagnostics_enabled: bool,
998 diagnostics_enabled: bool,
999 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1000 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1001 hard_wrap: Option<usize>,
1002
1003 // TODO: make this a access method
1004 pub project: Option<Entity<Project>>,
1005 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1006 completion_provider: Option<Rc<dyn CompletionProvider>>,
1007 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1008 blink_manager: Entity<BlinkManager>,
1009 show_cursor_names: bool,
1010 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1011 pub show_local_selections: bool,
1012 mode: EditorMode,
1013 show_breadcrumbs: bool,
1014 show_gutter: bool,
1015 show_scrollbars: ScrollbarAxes,
1016 minimap_visibility: MinimapVisibility,
1017 offset_content: bool,
1018 disable_expand_excerpt_buttons: bool,
1019 show_line_numbers: Option<bool>,
1020 use_relative_line_numbers: Option<bool>,
1021 show_git_diff_gutter: Option<bool>,
1022 show_code_actions: Option<bool>,
1023 show_runnables: Option<bool>,
1024 show_breakpoints: Option<bool>,
1025 show_wrap_guides: Option<bool>,
1026 show_indent_guides: Option<bool>,
1027 placeholder_text: Option<Arc<str>>,
1028 highlight_order: usize,
1029 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1030 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1031 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1032 scrollbar_marker_state: ScrollbarMarkerState,
1033 active_indent_guides_state: ActiveIndentGuidesState,
1034 nav_history: Option<ItemNavHistory>,
1035 context_menu: RefCell<Option<CodeContextMenu>>,
1036 context_menu_options: Option<ContextMenuOptions>,
1037 mouse_context_menu: Option<MouseContextMenu>,
1038 completion_tasks: Vec<(CompletionId, Task<()>)>,
1039 inline_blame_popover: Option<InlineBlamePopover>,
1040 inline_blame_popover_show_task: Option<Task<()>>,
1041 signature_help_state: SignatureHelpState,
1042 auto_signature_help: Option<bool>,
1043 find_all_references_task_sources: Vec<Anchor>,
1044 next_completion_id: CompletionId,
1045 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1046 code_actions_task: Option<Task<Result<()>>>,
1047 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1048 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1049 document_highlights_task: Option<Task<()>>,
1050 linked_editing_range_task: Option<Task<Option<()>>>,
1051 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1052 pending_rename: Option<RenameState>,
1053 searchable: bool,
1054 cursor_shape: CursorShape,
1055 current_line_highlight: Option<CurrentLineHighlight>,
1056 collapse_matches: bool,
1057 autoindent_mode: Option<AutoindentMode>,
1058 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1059 input_enabled: bool,
1060 use_modal_editing: bool,
1061 read_only: bool,
1062 leader_id: Option<CollaboratorId>,
1063 remote_id: Option<ViewId>,
1064 pub hover_state: HoverState,
1065 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1066 gutter_hovered: bool,
1067 hovered_link_state: Option<HoveredLinkState>,
1068 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1069 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1070 active_inline_completion: Option<InlineCompletionState>,
1071 /// Used to prevent flickering as the user types while the menu is open
1072 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1073 edit_prediction_settings: EditPredictionSettings,
1074 inline_completions_hidden_for_vim_mode: bool,
1075 show_inline_completions_override: Option<bool>,
1076 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1077 edit_prediction_preview: EditPredictionPreview,
1078 edit_prediction_indent_conflict: bool,
1079 edit_prediction_requires_modifier_in_indent_conflict: bool,
1080 inlay_hint_cache: InlayHintCache,
1081 next_inlay_id: usize,
1082 _subscriptions: Vec<Subscription>,
1083 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1084 gutter_dimensions: GutterDimensions,
1085 style: Option<EditorStyle>,
1086 text_style_refinement: Option<TextStyleRefinement>,
1087 next_editor_action_id: EditorActionId,
1088 editor_actions: Rc<
1089 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1090 >,
1091 use_autoclose: bool,
1092 use_auto_surround: bool,
1093 auto_replace_emoji_shortcode: bool,
1094 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1095 show_git_blame_gutter: bool,
1096 show_git_blame_inline: bool,
1097 show_git_blame_inline_delay_task: Option<Task<()>>,
1098 git_blame_inline_enabled: bool,
1099 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1100 serialize_dirty_buffers: bool,
1101 show_selection_menu: Option<bool>,
1102 blame: Option<Entity<GitBlame>>,
1103 blame_subscription: Option<Subscription>,
1104 custom_context_menu: Option<
1105 Box<
1106 dyn 'static
1107 + Fn(
1108 &mut Self,
1109 DisplayPoint,
1110 &mut Window,
1111 &mut Context<Self>,
1112 ) -> Option<Entity<ui::ContextMenu>>,
1113 >,
1114 >,
1115 last_bounds: Option<Bounds<Pixels>>,
1116 last_position_map: Option<Rc<PositionMap>>,
1117 expect_bounds_change: Option<Bounds<Pixels>>,
1118 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1119 tasks_update_task: Option<Task<()>>,
1120 breakpoint_store: Option<Entity<BreakpointStore>>,
1121 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1122 hovered_diff_hunk_row: Option<DisplayRow>,
1123 pull_diagnostics_task: Task<()>,
1124 in_project_search: bool,
1125 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1126 breadcrumb_header: Option<String>,
1127 focused_block: Option<FocusedBlock>,
1128 next_scroll_position: NextScrollCursorCenterTopBottom,
1129 addons: HashMap<TypeId, Box<dyn Addon>>,
1130 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1131 load_diff_task: Option<Shared<Task<()>>>,
1132 /// Whether we are temporarily displaying a diff other than git's
1133 temporary_diff_override: bool,
1134 selection_mark_mode: bool,
1135 toggle_fold_multiple_buffers: Task<()>,
1136 _scroll_cursor_center_top_bottom_task: Task<()>,
1137 serialize_selections: Task<()>,
1138 serialize_folds: Task<()>,
1139 mouse_cursor_hidden: bool,
1140 minimap: Option<Entity<Self>>,
1141 hide_mouse_mode: HideMouseMode,
1142 pub change_list: ChangeList,
1143 inline_value_cache: InlineValueCache,
1144 selection_drag_state: SelectionDragState,
1145 drag_and_drop_selection_enabled: bool,
1146 next_color_inlay_id: usize,
1147 colors: Option<LspColorData>,
1148 folding_newlines: Task<()>,
1149}
1150
1151#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1152enum NextScrollCursorCenterTopBottom {
1153 #[default]
1154 Center,
1155 Top,
1156 Bottom,
1157}
1158
1159impl NextScrollCursorCenterTopBottom {
1160 fn next(&self) -> Self {
1161 match self {
1162 Self::Center => Self::Top,
1163 Self::Top => Self::Bottom,
1164 Self::Bottom => Self::Center,
1165 }
1166 }
1167}
1168
1169#[derive(Clone)]
1170pub struct EditorSnapshot {
1171 pub mode: EditorMode,
1172 show_gutter: bool,
1173 show_line_numbers: Option<bool>,
1174 show_git_diff_gutter: Option<bool>,
1175 show_code_actions: Option<bool>,
1176 show_runnables: Option<bool>,
1177 show_breakpoints: Option<bool>,
1178 git_blame_gutter_max_author_length: Option<usize>,
1179 pub display_snapshot: DisplaySnapshot,
1180 pub placeholder_text: Option<Arc<str>>,
1181 is_focused: bool,
1182 scroll_anchor: ScrollAnchor,
1183 ongoing_scroll: OngoingScroll,
1184 current_line_highlight: CurrentLineHighlight,
1185 gutter_hovered: bool,
1186}
1187
1188#[derive(Default, Debug, Clone, Copy)]
1189pub struct GutterDimensions {
1190 pub left_padding: Pixels,
1191 pub right_padding: Pixels,
1192 pub width: Pixels,
1193 pub margin: Pixels,
1194 pub git_blame_entries_width: Option<Pixels>,
1195}
1196
1197impl GutterDimensions {
1198 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1199 Self {
1200 margin: Self::default_gutter_margin(font_id, font_size, cx),
1201 ..Default::default()
1202 }
1203 }
1204
1205 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1206 -cx.text_system().descent(font_id, font_size)
1207 }
1208 /// The full width of the space taken up by the gutter.
1209 pub fn full_width(&self) -> Pixels {
1210 self.margin + self.width
1211 }
1212
1213 /// The width of the space reserved for the fold indicators,
1214 /// use alongside 'justify_end' and `gutter_width` to
1215 /// right align content with the line numbers
1216 pub fn fold_area_width(&self) -> Pixels {
1217 self.margin + self.right_padding
1218 }
1219}
1220
1221struct CharacterDimensions {
1222 em_width: Pixels,
1223 em_advance: Pixels,
1224 line_height: Pixels,
1225}
1226
1227#[derive(Debug)]
1228pub struct RemoteSelection {
1229 pub replica_id: ReplicaId,
1230 pub selection: Selection<Anchor>,
1231 pub cursor_shape: CursorShape,
1232 pub collaborator_id: CollaboratorId,
1233 pub line_mode: bool,
1234 pub user_name: Option<SharedString>,
1235 pub color: PlayerColor,
1236}
1237
1238#[derive(Clone, Debug)]
1239struct SelectionHistoryEntry {
1240 selections: Arc<[Selection<Anchor>]>,
1241 select_next_state: Option<SelectNextState>,
1242 select_prev_state: Option<SelectNextState>,
1243 add_selections_state: Option<AddSelectionsState>,
1244}
1245
1246#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1247enum SelectionHistoryMode {
1248 Normal,
1249 Undoing,
1250 Redoing,
1251 Skipping,
1252}
1253
1254#[derive(Clone, PartialEq, Eq, Hash)]
1255struct HoveredCursor {
1256 replica_id: u16,
1257 selection_id: usize,
1258}
1259
1260impl Default for SelectionHistoryMode {
1261 fn default() -> Self {
1262 Self::Normal
1263 }
1264}
1265
1266#[derive(Debug)]
1267/// SelectionEffects controls the side-effects of updating the selection.
1268///
1269/// The default behaviour does "what you mostly want":
1270/// - it pushes to the nav history if the cursor moved by >10 lines
1271/// - it re-triggers completion requests
1272/// - it scrolls to fit
1273///
1274/// You might want to modify these behaviours. For example when doing a "jump"
1275/// like go to definition, we always want to add to nav history; but when scrolling
1276/// in vim mode we never do.
1277///
1278/// Similarly, you might want to disable scrolling if you don't want the viewport to
1279/// move.
1280pub struct SelectionEffects {
1281 nav_history: Option<bool>,
1282 completions: bool,
1283 scroll: Option<Autoscroll>,
1284}
1285
1286impl Default for SelectionEffects {
1287 fn default() -> Self {
1288 Self {
1289 nav_history: None,
1290 completions: true,
1291 scroll: Some(Autoscroll::fit()),
1292 }
1293 }
1294}
1295impl SelectionEffects {
1296 pub fn scroll(scroll: Autoscroll) -> Self {
1297 Self {
1298 scroll: Some(scroll),
1299 ..Default::default()
1300 }
1301 }
1302
1303 pub fn no_scroll() -> Self {
1304 Self {
1305 scroll: None,
1306 ..Default::default()
1307 }
1308 }
1309
1310 pub fn completions(self, completions: bool) -> Self {
1311 Self {
1312 completions,
1313 ..self
1314 }
1315 }
1316
1317 pub fn nav_history(self, nav_history: bool) -> Self {
1318 Self {
1319 nav_history: Some(nav_history),
1320 ..self
1321 }
1322 }
1323}
1324
1325struct DeferredSelectionEffectsState {
1326 changed: bool,
1327 effects: SelectionEffects,
1328 old_cursor_position: Anchor,
1329 history_entry: SelectionHistoryEntry,
1330}
1331
1332#[derive(Default)]
1333struct SelectionHistory {
1334 #[allow(clippy::type_complexity)]
1335 selections_by_transaction:
1336 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1337 mode: SelectionHistoryMode,
1338 undo_stack: VecDeque<SelectionHistoryEntry>,
1339 redo_stack: VecDeque<SelectionHistoryEntry>,
1340}
1341
1342impl SelectionHistory {
1343 #[track_caller]
1344 fn insert_transaction(
1345 &mut self,
1346 transaction_id: TransactionId,
1347 selections: Arc<[Selection<Anchor>]>,
1348 ) {
1349 if selections.is_empty() {
1350 log::error!(
1351 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1352 std::panic::Location::caller()
1353 );
1354 return;
1355 }
1356 self.selections_by_transaction
1357 .insert(transaction_id, (selections, None));
1358 }
1359
1360 #[allow(clippy::type_complexity)]
1361 fn transaction(
1362 &self,
1363 transaction_id: TransactionId,
1364 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1365 self.selections_by_transaction.get(&transaction_id)
1366 }
1367
1368 #[allow(clippy::type_complexity)]
1369 fn transaction_mut(
1370 &mut self,
1371 transaction_id: TransactionId,
1372 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1373 self.selections_by_transaction.get_mut(&transaction_id)
1374 }
1375
1376 fn push(&mut self, entry: SelectionHistoryEntry) {
1377 if !entry.selections.is_empty() {
1378 match self.mode {
1379 SelectionHistoryMode::Normal => {
1380 self.push_undo(entry);
1381 self.redo_stack.clear();
1382 }
1383 SelectionHistoryMode::Undoing => self.push_redo(entry),
1384 SelectionHistoryMode::Redoing => self.push_undo(entry),
1385 SelectionHistoryMode::Skipping => {}
1386 }
1387 }
1388 }
1389
1390 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1391 if self
1392 .undo_stack
1393 .back()
1394 .map_or(true, |e| e.selections != entry.selections)
1395 {
1396 self.undo_stack.push_back(entry);
1397 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1398 self.undo_stack.pop_front();
1399 }
1400 }
1401 }
1402
1403 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1404 if self
1405 .redo_stack
1406 .back()
1407 .map_or(true, |e| e.selections != entry.selections)
1408 {
1409 self.redo_stack.push_back(entry);
1410 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1411 self.redo_stack.pop_front();
1412 }
1413 }
1414 }
1415}
1416
1417#[derive(Clone, Copy)]
1418pub struct RowHighlightOptions {
1419 pub autoscroll: bool,
1420 pub include_gutter: bool,
1421}
1422
1423impl Default for RowHighlightOptions {
1424 fn default() -> Self {
1425 Self {
1426 autoscroll: Default::default(),
1427 include_gutter: true,
1428 }
1429 }
1430}
1431
1432struct RowHighlight {
1433 index: usize,
1434 range: Range<Anchor>,
1435 color: Hsla,
1436 options: RowHighlightOptions,
1437 type_id: TypeId,
1438}
1439
1440#[derive(Clone, Debug)]
1441struct AddSelectionsState {
1442 groups: Vec<AddSelectionsGroup>,
1443}
1444
1445#[derive(Clone, Debug)]
1446struct AddSelectionsGroup {
1447 above: bool,
1448 stack: Vec<usize>,
1449}
1450
1451#[derive(Clone)]
1452struct SelectNextState {
1453 query: AhoCorasick,
1454 wordwise: bool,
1455 done: bool,
1456}
1457
1458impl std::fmt::Debug for SelectNextState {
1459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1460 f.debug_struct(std::any::type_name::<Self>())
1461 .field("wordwise", &self.wordwise)
1462 .field("done", &self.done)
1463 .finish()
1464 }
1465}
1466
1467#[derive(Debug)]
1468struct AutocloseRegion {
1469 selection_id: usize,
1470 range: Range<Anchor>,
1471 pair: BracketPair,
1472}
1473
1474#[derive(Debug)]
1475struct SnippetState {
1476 ranges: Vec<Vec<Range<Anchor>>>,
1477 active_index: usize,
1478 choices: Vec<Option<Vec<String>>>,
1479}
1480
1481#[doc(hidden)]
1482pub struct RenameState {
1483 pub range: Range<Anchor>,
1484 pub old_name: Arc<str>,
1485 pub editor: Entity<Editor>,
1486 block_id: CustomBlockId,
1487}
1488
1489struct InvalidationStack<T>(Vec<T>);
1490
1491struct RegisteredInlineCompletionProvider {
1492 provider: Arc<dyn InlineCompletionProviderHandle>,
1493 _subscription: Subscription,
1494}
1495
1496#[derive(Debug, PartialEq, Eq)]
1497pub struct ActiveDiagnosticGroup {
1498 pub active_range: Range<Anchor>,
1499 pub active_message: String,
1500 pub group_id: usize,
1501 pub blocks: HashSet<CustomBlockId>,
1502}
1503
1504#[derive(Debug, PartialEq, Eq)]
1505
1506pub(crate) enum ActiveDiagnostic {
1507 None,
1508 All,
1509 Group(ActiveDiagnosticGroup),
1510}
1511
1512#[derive(Serialize, Deserialize, Clone, Debug)]
1513pub struct ClipboardSelection {
1514 /// The number of bytes in this selection.
1515 pub len: usize,
1516 /// Whether this was a full-line selection.
1517 pub is_entire_line: bool,
1518 /// The indentation of the first line when this content was originally copied.
1519 pub first_line_indent: u32,
1520}
1521
1522// selections, scroll behavior, was newest selection reversed
1523type SelectSyntaxNodeHistoryState = (
1524 Box<[Selection<usize>]>,
1525 SelectSyntaxNodeScrollBehavior,
1526 bool,
1527);
1528
1529#[derive(Default)]
1530struct SelectSyntaxNodeHistory {
1531 stack: Vec<SelectSyntaxNodeHistoryState>,
1532 // disable temporarily to allow changing selections without losing the stack
1533 pub disable_clearing: bool,
1534}
1535
1536impl SelectSyntaxNodeHistory {
1537 pub fn try_clear(&mut self) {
1538 if !self.disable_clearing {
1539 self.stack.clear();
1540 }
1541 }
1542
1543 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1544 self.stack.push(selection);
1545 }
1546
1547 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1548 self.stack.pop()
1549 }
1550}
1551
1552enum SelectSyntaxNodeScrollBehavior {
1553 CursorTop,
1554 FitSelection,
1555 CursorBottom,
1556}
1557
1558#[derive(Debug)]
1559pub(crate) struct NavigationData {
1560 cursor_anchor: Anchor,
1561 cursor_position: Point,
1562 scroll_anchor: ScrollAnchor,
1563 scroll_top_row: u32,
1564}
1565
1566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1567pub enum GotoDefinitionKind {
1568 Symbol,
1569 Declaration,
1570 Type,
1571 Implementation,
1572}
1573
1574#[derive(Debug, Clone)]
1575enum InlayHintRefreshReason {
1576 ModifiersChanged(bool),
1577 Toggle(bool),
1578 SettingsChange(InlayHintSettings),
1579 NewLinesShown,
1580 BufferEdited(HashSet<Arc<Language>>),
1581 RefreshRequested,
1582 ExcerptsRemoved(Vec<ExcerptId>),
1583}
1584
1585impl InlayHintRefreshReason {
1586 fn description(&self) -> &'static str {
1587 match self {
1588 Self::ModifiersChanged(_) => "modifiers changed",
1589 Self::Toggle(_) => "toggle",
1590 Self::SettingsChange(_) => "settings change",
1591 Self::NewLinesShown => "new lines shown",
1592 Self::BufferEdited(_) => "buffer edited",
1593 Self::RefreshRequested => "refresh requested",
1594 Self::ExcerptsRemoved(_) => "excerpts removed",
1595 }
1596 }
1597}
1598
1599pub enum FormatTarget {
1600 Buffers(HashSet<Entity<Buffer>>),
1601 Ranges(Vec<Range<MultiBufferPoint>>),
1602}
1603
1604pub(crate) struct FocusedBlock {
1605 id: BlockId,
1606 focus_handle: WeakFocusHandle,
1607}
1608
1609#[derive(Clone)]
1610enum JumpData {
1611 MultiBufferRow {
1612 row: MultiBufferRow,
1613 line_offset_from_top: u32,
1614 },
1615 MultiBufferPoint {
1616 excerpt_id: ExcerptId,
1617 position: Point,
1618 anchor: text::Anchor,
1619 line_offset_from_top: u32,
1620 },
1621}
1622
1623pub enum MultibufferSelectionMode {
1624 First,
1625 All,
1626}
1627
1628#[derive(Clone, Copy, Debug, Default)]
1629pub struct RewrapOptions {
1630 pub override_language_settings: bool,
1631 pub preserve_existing_whitespace: bool,
1632}
1633
1634impl Editor {
1635 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1636 let buffer = cx.new(|cx| Buffer::local("", cx));
1637 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1638 Self::new(
1639 EditorMode::SingleLine { auto_width: false },
1640 buffer,
1641 None,
1642 window,
1643 cx,
1644 )
1645 }
1646
1647 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1648 let buffer = cx.new(|cx| Buffer::local("", cx));
1649 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1650 Self::new(EditorMode::full(), buffer, None, window, cx)
1651 }
1652
1653 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(
1657 EditorMode::SingleLine { auto_width: true },
1658 buffer,
1659 None,
1660 window,
1661 cx,
1662 )
1663 }
1664
1665 pub fn auto_height(
1666 min_lines: usize,
1667 max_lines: usize,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(
1674 EditorMode::AutoHeight {
1675 min_lines,
1676 max_lines: Some(max_lines),
1677 },
1678 buffer,
1679 None,
1680 window,
1681 cx,
1682 )
1683 }
1684
1685 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1686 /// The editor grows as tall as needed to fit its content.
1687 pub fn auto_height_unbounded(
1688 min_lines: usize,
1689 window: &mut Window,
1690 cx: &mut Context<Self>,
1691 ) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(
1695 EditorMode::AutoHeight {
1696 min_lines,
1697 max_lines: None,
1698 },
1699 buffer,
1700 None,
1701 window,
1702 cx,
1703 )
1704 }
1705
1706 pub fn for_buffer(
1707 buffer: Entity<Buffer>,
1708 project: Option<Entity<Project>>,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1713 Self::new(EditorMode::full(), buffer, project, window, cx)
1714 }
1715
1716 pub fn for_multibuffer(
1717 buffer: Entity<MultiBuffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 Self::new(EditorMode::full(), buffer, project, window, cx)
1723 }
1724
1725 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let mut clone = Self::new(
1727 self.mode.clone(),
1728 self.buffer.clone(),
1729 self.project.clone(),
1730 window,
1731 cx,
1732 );
1733 self.display_map.update(cx, |display_map, cx| {
1734 let snapshot = display_map.snapshot(cx);
1735 clone.display_map.update(cx, |display_map, cx| {
1736 display_map.set_state(&snapshot, cx);
1737 });
1738 });
1739 clone.folds_did_change(cx);
1740 clone.selections.clone_state(&self.selections);
1741 clone.scroll_manager.clone_state(&self.scroll_manager);
1742 clone.searchable = self.searchable;
1743 clone.read_only = self.read_only;
1744 clone
1745 }
1746
1747 pub fn new(
1748 mode: EditorMode,
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Editor::new_internal(mode, buffer, project, None, window, cx)
1755 }
1756
1757 fn new_internal(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 display_map: Option<Entity<DisplayMap>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 debug_assert!(
1766 display_map.is_none() || mode.is_minimap(),
1767 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1768 );
1769
1770 let full_mode = mode.is_full();
1771 let diagnostics_max_severity = if full_mode {
1772 EditorSettings::get_global(cx)
1773 .diagnostics_max_severity
1774 .unwrap_or(DiagnosticSeverity::Hint)
1775 } else {
1776 DiagnosticSeverity::Off
1777 };
1778 let style = window.text_style();
1779 let font_size = style.font_size.to_pixels(window.rem_size());
1780 let editor = cx.entity().downgrade();
1781 let fold_placeholder = FoldPlaceholder {
1782 constrain_width: true,
1783 render: Arc::new(move |fold_id, fold_range, cx| {
1784 let editor = editor.clone();
1785 div()
1786 .id(fold_id)
1787 .bg(cx.theme().colors().ghost_element_background)
1788 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1789 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1790 .rounded_xs()
1791 .size_full()
1792 .cursor_pointer()
1793 .child("⋯")
1794 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1795 .on_click(move |_, _window, cx| {
1796 editor
1797 .update(cx, |editor, cx| {
1798 editor.unfold_ranges(
1799 &[fold_range.start..fold_range.end],
1800 true,
1801 false,
1802 cx,
1803 );
1804 cx.stop_propagation();
1805 })
1806 .ok();
1807 })
1808 .into_any()
1809 }),
1810 merge_adjacent: true,
1811 ..FoldPlaceholder::default()
1812 };
1813 let display_map = display_map.unwrap_or_else(|| {
1814 cx.new(|cx| {
1815 DisplayMap::new(
1816 buffer.clone(),
1817 style.font(),
1818 font_size,
1819 None,
1820 FILE_HEADER_HEIGHT,
1821 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1822 fold_placeholder,
1823 diagnostics_max_severity,
1824 cx,
1825 )
1826 })
1827 });
1828
1829 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1830
1831 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1832
1833 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1834 .then(|| language_settings::SoftWrap::None);
1835
1836 let mut project_subscriptions = Vec::new();
1837 if mode.is_full() {
1838 if let Some(project) = project.as_ref() {
1839 project_subscriptions.push(cx.subscribe_in(
1840 project,
1841 window,
1842 |editor, _, event, window, cx| match event {
1843 project::Event::RefreshCodeLens => {
1844 // we always query lens with actions, without storing them, always refreshing them
1845 }
1846 project::Event::RefreshInlayHints => {
1847 editor
1848 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1849 }
1850 project::Event::LanguageServerAdded(..)
1851 | project::Event::LanguageServerRemoved(..) => {
1852 if editor.tasks_update_task.is_none() {
1853 editor.tasks_update_task =
1854 Some(editor.refresh_runnables(window, cx));
1855 }
1856 editor.update_lsp_data(true, None, window, cx);
1857 }
1858 project::Event::SnippetEdit(id, snippet_edits) => {
1859 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1860 let focus_handle = editor.focus_handle(cx);
1861 if focus_handle.is_focused(window) {
1862 let snapshot = buffer.read(cx).snapshot();
1863 for (range, snippet) in snippet_edits {
1864 let editor_range =
1865 language::range_from_lsp(*range).to_offset(&snapshot);
1866 editor
1867 .insert_snippet(
1868 &[editor_range],
1869 snippet.clone(),
1870 window,
1871 cx,
1872 )
1873 .ok();
1874 }
1875 }
1876 }
1877 }
1878 _ => {}
1879 },
1880 ));
1881 if let Some(task_inventory) = project
1882 .read(cx)
1883 .task_store()
1884 .read(cx)
1885 .task_inventory()
1886 .cloned()
1887 {
1888 project_subscriptions.push(cx.observe_in(
1889 &task_inventory,
1890 window,
1891 |editor, _, window, cx| {
1892 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1893 },
1894 ));
1895 };
1896
1897 project_subscriptions.push(cx.subscribe_in(
1898 &project.read(cx).breakpoint_store(),
1899 window,
1900 |editor, _, event, window, cx| match event {
1901 BreakpointStoreEvent::ClearDebugLines => {
1902 editor.clear_row_highlights::<ActiveDebugLine>();
1903 editor.refresh_inline_values(cx);
1904 }
1905 BreakpointStoreEvent::SetDebugLine => {
1906 if editor.go_to_active_debug_line(window, cx) {
1907 cx.stop_propagation();
1908 }
1909
1910 editor.refresh_inline_values(cx);
1911 }
1912 _ => {}
1913 },
1914 ));
1915 let git_store = project.read(cx).git_store().clone();
1916 let project = project.clone();
1917 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1918 match event {
1919 GitStoreEvent::RepositoryUpdated(
1920 _,
1921 RepositoryEvent::Updated {
1922 new_instance: true, ..
1923 },
1924 _,
1925 ) => {
1926 this.load_diff_task = Some(
1927 update_uncommitted_diff_for_buffer(
1928 cx.entity(),
1929 &project,
1930 this.buffer.read(cx).all_buffers(),
1931 this.buffer.clone(),
1932 cx,
1933 )
1934 .shared(),
1935 );
1936 }
1937 _ => {}
1938 }
1939 }));
1940 }
1941 }
1942
1943 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1944
1945 let inlay_hint_settings =
1946 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1947 let focus_handle = cx.focus_handle();
1948 cx.on_focus(&focus_handle, window, Self::handle_focus)
1949 .detach();
1950 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1951 .detach();
1952 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1953 .detach();
1954 cx.on_blur(&focus_handle, window, Self::handle_blur)
1955 .detach();
1956 cx.observe_pending_input(window, Self::observe_pending_input)
1957 .detach();
1958
1959 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1960 Some(false)
1961 } else {
1962 None
1963 };
1964
1965 let breakpoint_store = match (&mode, project.as_ref()) {
1966 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1967 _ => None,
1968 };
1969
1970 let mut code_action_providers = Vec::new();
1971 let mut load_uncommitted_diff = None;
1972 if let Some(project) = project.clone() {
1973 load_uncommitted_diff = Some(
1974 update_uncommitted_diff_for_buffer(
1975 cx.entity(),
1976 &project,
1977 buffer.read(cx).all_buffers(),
1978 buffer.clone(),
1979 cx,
1980 )
1981 .shared(),
1982 );
1983 code_action_providers.push(Rc::new(project) as Rc<_>);
1984 }
1985
1986 let mut editor = Self {
1987 focus_handle,
1988 show_cursor_when_unfocused: false,
1989 last_focused_descendant: None,
1990 buffer: buffer.clone(),
1991 display_map: display_map.clone(),
1992 selections,
1993 scroll_manager: ScrollManager::new(cx),
1994 columnar_selection_state: None,
1995 add_selections_state: None,
1996 select_next_state: None,
1997 select_prev_state: None,
1998 selection_history: SelectionHistory::default(),
1999 defer_selection_effects: false,
2000 deferred_selection_effects_state: None,
2001 autoclose_regions: Vec::new(),
2002 snippet_stack: InvalidationStack::default(),
2003 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2004 ime_transaction: None,
2005 active_diagnostics: ActiveDiagnostic::None,
2006 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2007 inline_diagnostics_update: Task::ready(()),
2008 inline_diagnostics: Vec::new(),
2009 soft_wrap_mode_override,
2010 diagnostics_max_severity,
2011 hard_wrap: None,
2012 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2013 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2014 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2015 project,
2016 blink_manager: blink_manager.clone(),
2017 show_local_selections: true,
2018 show_scrollbars: ScrollbarAxes {
2019 horizontal: full_mode,
2020 vertical: full_mode,
2021 },
2022 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2023 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
2024 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2025 show_gutter: mode.is_full(),
2026 show_line_numbers: None,
2027 use_relative_line_numbers: None,
2028 disable_expand_excerpt_buttons: false,
2029 show_git_diff_gutter: None,
2030 show_code_actions: None,
2031 show_runnables: None,
2032 show_breakpoints: None,
2033 show_wrap_guides: None,
2034 show_indent_guides,
2035 placeholder_text: None,
2036 highlight_order: 0,
2037 highlighted_rows: HashMap::default(),
2038 background_highlights: TreeMap::default(),
2039 gutter_highlights: TreeMap::default(),
2040 scrollbar_marker_state: ScrollbarMarkerState::default(),
2041 active_indent_guides_state: ActiveIndentGuidesState::default(),
2042 nav_history: None,
2043 context_menu: RefCell::new(None),
2044 context_menu_options: None,
2045 mouse_context_menu: None,
2046 completion_tasks: Vec::new(),
2047 inline_blame_popover: None,
2048 inline_blame_popover_show_task: None,
2049 signature_help_state: SignatureHelpState::default(),
2050 auto_signature_help: None,
2051 find_all_references_task_sources: Vec::new(),
2052 next_completion_id: 0,
2053 next_inlay_id: 0,
2054 code_action_providers,
2055 available_code_actions: None,
2056 code_actions_task: None,
2057 quick_selection_highlight_task: None,
2058 debounced_selection_highlight_task: None,
2059 document_highlights_task: None,
2060 linked_editing_range_task: None,
2061 pending_rename: None,
2062 searchable: true,
2063 cursor_shape: EditorSettings::get_global(cx)
2064 .cursor_shape
2065 .unwrap_or_default(),
2066 current_line_highlight: None,
2067 autoindent_mode: Some(AutoindentMode::EachLine),
2068 collapse_matches: false,
2069 workspace: None,
2070 input_enabled: true,
2071 use_modal_editing: mode.is_full(),
2072 read_only: mode.is_minimap(),
2073 use_autoclose: true,
2074 use_auto_surround: true,
2075 auto_replace_emoji_shortcode: false,
2076 jsx_tag_auto_close_enabled_in_any_buffer: false,
2077 leader_id: None,
2078 remote_id: None,
2079 hover_state: HoverState::default(),
2080 pending_mouse_down: None,
2081 hovered_link_state: None,
2082 edit_prediction_provider: None,
2083 active_inline_completion: None,
2084 stale_inline_completion_in_menu: None,
2085 edit_prediction_preview: EditPredictionPreview::Inactive {
2086 released_too_fast: false,
2087 },
2088 inline_diagnostics_enabled: mode.is_full(),
2089 diagnostics_enabled: mode.is_full(),
2090 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2091 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2092
2093 gutter_hovered: false,
2094 pixel_position_of_newest_cursor: None,
2095 last_bounds: None,
2096 last_position_map: None,
2097 expect_bounds_change: None,
2098 gutter_dimensions: GutterDimensions::default(),
2099 style: None,
2100 show_cursor_names: false,
2101 hovered_cursors: HashMap::default(),
2102 next_editor_action_id: EditorActionId::default(),
2103 editor_actions: Rc::default(),
2104 inline_completions_hidden_for_vim_mode: false,
2105 show_inline_completions_override: None,
2106 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2107 edit_prediction_settings: EditPredictionSettings::Disabled,
2108 edit_prediction_indent_conflict: false,
2109 edit_prediction_requires_modifier_in_indent_conflict: true,
2110 custom_context_menu: None,
2111 show_git_blame_gutter: false,
2112 show_git_blame_inline: false,
2113 show_selection_menu: None,
2114 show_git_blame_inline_delay_task: None,
2115 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2116 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2117 serialize_dirty_buffers: !mode.is_minimap()
2118 && ProjectSettings::get_global(cx)
2119 .session
2120 .restore_unsaved_buffers,
2121 blame: None,
2122 blame_subscription: None,
2123 tasks: BTreeMap::default(),
2124
2125 breakpoint_store,
2126 gutter_breakpoint_indicator: (None, None),
2127 hovered_diff_hunk_row: None,
2128 _subscriptions: vec![
2129 cx.observe(&buffer, Self::on_buffer_changed),
2130 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2131 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2132 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2133 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2134 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2135 cx.observe_window_activation(window, |editor, window, cx| {
2136 let active = window.is_window_active();
2137 editor.blink_manager.update(cx, |blink_manager, cx| {
2138 if active {
2139 blink_manager.enable(cx);
2140 } else {
2141 blink_manager.disable(cx);
2142 }
2143 });
2144 if active {
2145 editor.show_mouse_cursor(cx);
2146 }
2147 }),
2148 ],
2149 tasks_update_task: None,
2150 pull_diagnostics_task: Task::ready(()),
2151 colors: None,
2152 next_color_inlay_id: 0,
2153 linked_edit_ranges: Default::default(),
2154 in_project_search: false,
2155 previous_search_ranges: None,
2156 breadcrumb_header: None,
2157 focused_block: None,
2158 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2159 addons: HashMap::default(),
2160 registered_buffers: HashMap::default(),
2161 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2162 selection_mark_mode: false,
2163 toggle_fold_multiple_buffers: Task::ready(()),
2164 serialize_selections: Task::ready(()),
2165 serialize_folds: Task::ready(()),
2166 text_style_refinement: None,
2167 load_diff_task: load_uncommitted_diff,
2168 temporary_diff_override: false,
2169 mouse_cursor_hidden: false,
2170 minimap: None,
2171 hide_mouse_mode: EditorSettings::get_global(cx)
2172 .hide_mouse
2173 .unwrap_or_default(),
2174 change_list: ChangeList::new(),
2175 mode,
2176 selection_drag_state: SelectionDragState::None,
2177 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2178 folding_newlines: Task::ready(()),
2179 };
2180 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2181 editor
2182 ._subscriptions
2183 .push(cx.observe(breakpoints, |_, _, cx| {
2184 cx.notify();
2185 }));
2186 }
2187 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2188 editor._subscriptions.extend(project_subscriptions);
2189
2190 editor._subscriptions.push(cx.subscribe_in(
2191 &cx.entity(),
2192 window,
2193 |editor, _, e: &EditorEvent, window, cx| match e {
2194 EditorEvent::ScrollPositionChanged { local, .. } => {
2195 if *local {
2196 let new_anchor = editor.scroll_manager.anchor();
2197 let snapshot = editor.snapshot(window, cx);
2198 editor.update_restoration_data(cx, move |data| {
2199 data.scroll_position = (
2200 new_anchor.top_row(&snapshot.buffer_snapshot),
2201 new_anchor.offset,
2202 );
2203 });
2204 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2205 editor.inline_blame_popover.take();
2206 }
2207 }
2208 EditorEvent::Edited { .. } => {
2209 if !vim_enabled(cx) {
2210 let (map, selections) = editor.selections.all_adjusted_display(cx);
2211 let pop_state = editor
2212 .change_list
2213 .last()
2214 .map(|previous| {
2215 previous.len() == selections.len()
2216 && previous.iter().enumerate().all(|(ix, p)| {
2217 p.to_display_point(&map).row()
2218 == selections[ix].head().row()
2219 })
2220 })
2221 .unwrap_or(false);
2222 let new_positions = selections
2223 .into_iter()
2224 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2225 .collect();
2226 editor
2227 .change_list
2228 .push_to_change_list(pop_state, new_positions);
2229 }
2230 }
2231 _ => (),
2232 },
2233 ));
2234
2235 if let Some(dap_store) = editor
2236 .project
2237 .as_ref()
2238 .map(|project| project.read(cx).dap_store())
2239 {
2240 let weak_editor = cx.weak_entity();
2241
2242 editor
2243 ._subscriptions
2244 .push(
2245 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2246 let session_entity = cx.entity();
2247 weak_editor
2248 .update(cx, |editor, cx| {
2249 editor._subscriptions.push(
2250 cx.subscribe(&session_entity, Self::on_debug_session_event),
2251 );
2252 })
2253 .ok();
2254 }),
2255 );
2256
2257 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2258 editor
2259 ._subscriptions
2260 .push(cx.subscribe(&session, Self::on_debug_session_event));
2261 }
2262 }
2263
2264 // skip adding the initial selection to selection history
2265 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2266 editor.end_selection(window, cx);
2267 editor.selection_history.mode = SelectionHistoryMode::Normal;
2268
2269 editor.scroll_manager.show_scrollbars(window, cx);
2270 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2271
2272 if full_mode {
2273 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2274 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2275
2276 if editor.git_blame_inline_enabled {
2277 editor.start_git_blame_inline(false, window, cx);
2278 }
2279
2280 editor.go_to_active_debug_line(window, cx);
2281
2282 if let Some(buffer) = buffer.read(cx).as_singleton() {
2283 if let Some(project) = editor.project.as_ref() {
2284 let handle = project.update(cx, |project, cx| {
2285 project.register_buffer_with_language_servers(&buffer, cx)
2286 });
2287 editor
2288 .registered_buffers
2289 .insert(buffer.read(cx).remote_id(), handle);
2290 }
2291 }
2292
2293 editor.minimap =
2294 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2295 editor.colors = Some(LspColorData::new(cx));
2296 editor.update_lsp_data(false, None, window, cx);
2297 }
2298
2299 editor.report_editor_event("Editor Opened", None, cx);
2300 editor
2301 }
2302
2303 pub fn deploy_mouse_context_menu(
2304 &mut self,
2305 position: gpui::Point<Pixels>,
2306 context_menu: Entity<ContextMenu>,
2307 window: &mut Window,
2308 cx: &mut Context<Self>,
2309 ) {
2310 self.mouse_context_menu = Some(MouseContextMenu::new(
2311 self,
2312 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2313 context_menu,
2314 window,
2315 cx,
2316 ));
2317 }
2318
2319 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2320 self.mouse_context_menu
2321 .as_ref()
2322 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2323 }
2324
2325 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2326 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2327 }
2328
2329 fn key_context_internal(
2330 &self,
2331 has_active_edit_prediction: bool,
2332 window: &Window,
2333 cx: &App,
2334 ) -> KeyContext {
2335 let mut key_context = KeyContext::new_with_defaults();
2336 key_context.add("Editor");
2337 let mode = match self.mode {
2338 EditorMode::SingleLine { .. } => "single_line",
2339 EditorMode::AutoHeight { .. } => "auto_height",
2340 EditorMode::Minimap { .. } => "minimap",
2341 EditorMode::Full { .. } => "full",
2342 };
2343
2344 if EditorSettings::jupyter_enabled(cx) {
2345 key_context.add("jupyter");
2346 }
2347
2348 key_context.set("mode", mode);
2349 if self.pending_rename.is_some() {
2350 key_context.add("renaming");
2351 }
2352
2353 match self.context_menu.borrow().as_ref() {
2354 Some(CodeContextMenu::Completions(_)) => {
2355 key_context.add("menu");
2356 key_context.add("showing_completions");
2357 }
2358 Some(CodeContextMenu::CodeActions(_)) => {
2359 key_context.add("menu");
2360 key_context.add("showing_code_actions")
2361 }
2362 None => {}
2363 }
2364
2365 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2366 if !self.focus_handle(cx).contains_focused(window, cx)
2367 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2368 {
2369 for addon in self.addons.values() {
2370 addon.extend_key_context(&mut key_context, cx)
2371 }
2372 }
2373
2374 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2375 if let Some(extension) = singleton_buffer
2376 .read(cx)
2377 .file()
2378 .and_then(|file| file.path().extension()?.to_str())
2379 {
2380 key_context.set("extension", extension.to_string());
2381 }
2382 } else {
2383 key_context.add("multibuffer");
2384 }
2385
2386 if has_active_edit_prediction {
2387 if self.edit_prediction_in_conflict() {
2388 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2389 } else {
2390 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2391 key_context.add("copilot_suggestion");
2392 }
2393 }
2394
2395 if self.selection_mark_mode {
2396 key_context.add("selection_mode");
2397 }
2398
2399 key_context
2400 }
2401
2402 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2403 if self.mouse_cursor_hidden {
2404 self.mouse_cursor_hidden = false;
2405 cx.notify();
2406 }
2407 }
2408
2409 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2410 let hide_mouse_cursor = match origin {
2411 HideMouseCursorOrigin::TypingAction => {
2412 matches!(
2413 self.hide_mouse_mode,
2414 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2415 )
2416 }
2417 HideMouseCursorOrigin::MovementAction => {
2418 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2419 }
2420 };
2421 if self.mouse_cursor_hidden != hide_mouse_cursor {
2422 self.mouse_cursor_hidden = hide_mouse_cursor;
2423 cx.notify();
2424 }
2425 }
2426
2427 pub fn edit_prediction_in_conflict(&self) -> bool {
2428 if !self.show_edit_predictions_in_menu() {
2429 return false;
2430 }
2431
2432 let showing_completions = self
2433 .context_menu
2434 .borrow()
2435 .as_ref()
2436 .map_or(false, |context| {
2437 matches!(context, CodeContextMenu::Completions(_))
2438 });
2439
2440 showing_completions
2441 || self.edit_prediction_requires_modifier()
2442 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2443 // bindings to insert tab characters.
2444 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2445 }
2446
2447 pub fn accept_edit_prediction_keybind(
2448 &self,
2449 accept_partial: bool,
2450 window: &Window,
2451 cx: &App,
2452 ) -> AcceptEditPredictionBinding {
2453 let key_context = self.key_context_internal(true, window, cx);
2454 let in_conflict = self.edit_prediction_in_conflict();
2455
2456 let bindings = if accept_partial {
2457 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2458 } else {
2459 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2460 };
2461
2462 // TODO: if the binding contains multiple keystrokes, display all of them, not
2463 // just the first one.
2464 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2465 !in_conflict
2466 || binding
2467 .keystrokes()
2468 .first()
2469 .map_or(false, |keystroke| keystroke.modifiers.modified())
2470 }))
2471 }
2472
2473 pub fn new_file(
2474 workspace: &mut Workspace,
2475 _: &workspace::NewFile,
2476 window: &mut Window,
2477 cx: &mut Context<Workspace>,
2478 ) {
2479 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2480 "Failed to create buffer",
2481 window,
2482 cx,
2483 |e, _, _| match e.error_code() {
2484 ErrorCode::RemoteUpgradeRequired => Some(format!(
2485 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2486 e.error_tag("required").unwrap_or("the latest version")
2487 )),
2488 _ => None,
2489 },
2490 );
2491 }
2492
2493 pub fn new_in_workspace(
2494 workspace: &mut Workspace,
2495 window: &mut Window,
2496 cx: &mut Context<Workspace>,
2497 ) -> Task<Result<Entity<Editor>>> {
2498 let project = workspace.project().clone();
2499 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2500
2501 cx.spawn_in(window, async move |workspace, cx| {
2502 let buffer = create.await?;
2503 workspace.update_in(cx, |workspace, window, cx| {
2504 let editor =
2505 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2506 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2507 editor
2508 })
2509 })
2510 }
2511
2512 fn new_file_vertical(
2513 workspace: &mut Workspace,
2514 _: &workspace::NewFileSplitVertical,
2515 window: &mut Window,
2516 cx: &mut Context<Workspace>,
2517 ) {
2518 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2519 }
2520
2521 fn new_file_horizontal(
2522 workspace: &mut Workspace,
2523 _: &workspace::NewFileSplitHorizontal,
2524 window: &mut Window,
2525 cx: &mut Context<Workspace>,
2526 ) {
2527 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2528 }
2529
2530 fn new_file_in_direction(
2531 workspace: &mut Workspace,
2532 direction: SplitDirection,
2533 window: &mut Window,
2534 cx: &mut Context<Workspace>,
2535 ) {
2536 let project = workspace.project().clone();
2537 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2538
2539 cx.spawn_in(window, async move |workspace, cx| {
2540 let buffer = create.await?;
2541 workspace.update_in(cx, move |workspace, window, cx| {
2542 workspace.split_item(
2543 direction,
2544 Box::new(
2545 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2546 ),
2547 window,
2548 cx,
2549 )
2550 })?;
2551 anyhow::Ok(())
2552 })
2553 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2554 match e.error_code() {
2555 ErrorCode::RemoteUpgradeRequired => Some(format!(
2556 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2557 e.error_tag("required").unwrap_or("the latest version")
2558 )),
2559 _ => None,
2560 }
2561 });
2562 }
2563
2564 pub fn leader_id(&self) -> Option<CollaboratorId> {
2565 self.leader_id
2566 }
2567
2568 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2569 &self.buffer
2570 }
2571
2572 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2573 self.workspace.as_ref()?.0.upgrade()
2574 }
2575
2576 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2577 self.buffer().read(cx).title(cx)
2578 }
2579
2580 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2581 let git_blame_gutter_max_author_length = self
2582 .render_git_blame_gutter(cx)
2583 .then(|| {
2584 if let Some(blame) = self.blame.as_ref() {
2585 let max_author_length =
2586 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2587 Some(max_author_length)
2588 } else {
2589 None
2590 }
2591 })
2592 .flatten();
2593
2594 EditorSnapshot {
2595 mode: self.mode.clone(),
2596 show_gutter: self.show_gutter,
2597 show_line_numbers: self.show_line_numbers,
2598 show_git_diff_gutter: self.show_git_diff_gutter,
2599 show_code_actions: self.show_code_actions,
2600 show_runnables: self.show_runnables,
2601 show_breakpoints: self.show_breakpoints,
2602 git_blame_gutter_max_author_length,
2603 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2604 scroll_anchor: self.scroll_manager.anchor(),
2605 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2606 placeholder_text: self.placeholder_text.clone(),
2607 is_focused: self.focus_handle.is_focused(window),
2608 current_line_highlight: self
2609 .current_line_highlight
2610 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2611 gutter_hovered: self.gutter_hovered,
2612 }
2613 }
2614
2615 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2616 self.buffer.read(cx).language_at(point, cx)
2617 }
2618
2619 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2620 self.buffer.read(cx).read(cx).file_at(point).cloned()
2621 }
2622
2623 pub fn active_excerpt(
2624 &self,
2625 cx: &App,
2626 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2627 self.buffer
2628 .read(cx)
2629 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2630 }
2631
2632 pub fn mode(&self) -> &EditorMode {
2633 &self.mode
2634 }
2635
2636 pub fn set_mode(&mut self, mode: EditorMode) {
2637 self.mode = mode;
2638 }
2639
2640 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2641 self.collaboration_hub.as_deref()
2642 }
2643
2644 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2645 self.collaboration_hub = Some(hub);
2646 }
2647
2648 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2649 self.in_project_search = in_project_search;
2650 }
2651
2652 pub fn set_custom_context_menu(
2653 &mut self,
2654 f: impl 'static
2655 + Fn(
2656 &mut Self,
2657 DisplayPoint,
2658 &mut Window,
2659 &mut Context<Self>,
2660 ) -> Option<Entity<ui::ContextMenu>>,
2661 ) {
2662 self.custom_context_menu = Some(Box::new(f))
2663 }
2664
2665 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2666 self.completion_provider = provider;
2667 }
2668
2669 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2670 self.semantics_provider.clone()
2671 }
2672
2673 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2674 self.semantics_provider = provider;
2675 }
2676
2677 pub fn set_edit_prediction_provider<T>(
2678 &mut self,
2679 provider: Option<Entity<T>>,
2680 window: &mut Window,
2681 cx: &mut Context<Self>,
2682 ) where
2683 T: EditPredictionProvider,
2684 {
2685 self.edit_prediction_provider =
2686 provider.map(|provider| RegisteredInlineCompletionProvider {
2687 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2688 if this.focus_handle.is_focused(window) {
2689 this.update_visible_inline_completion(window, cx);
2690 }
2691 }),
2692 provider: Arc::new(provider),
2693 });
2694 self.update_edit_prediction_settings(cx);
2695 self.refresh_inline_completion(false, false, window, cx);
2696 }
2697
2698 pub fn placeholder_text(&self) -> Option<&str> {
2699 self.placeholder_text.as_deref()
2700 }
2701
2702 pub fn set_placeholder_text(
2703 &mut self,
2704 placeholder_text: impl Into<Arc<str>>,
2705 cx: &mut Context<Self>,
2706 ) {
2707 let placeholder_text = Some(placeholder_text.into());
2708 if self.placeholder_text != placeholder_text {
2709 self.placeholder_text = placeholder_text;
2710 cx.notify();
2711 }
2712 }
2713
2714 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2715 self.cursor_shape = cursor_shape;
2716
2717 // Disrupt blink for immediate user feedback that the cursor shape has changed
2718 self.blink_manager.update(cx, BlinkManager::show_cursor);
2719
2720 cx.notify();
2721 }
2722
2723 pub fn set_current_line_highlight(
2724 &mut self,
2725 current_line_highlight: Option<CurrentLineHighlight>,
2726 ) {
2727 self.current_line_highlight = current_line_highlight;
2728 }
2729
2730 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2731 self.collapse_matches = collapse_matches;
2732 }
2733
2734 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2735 let buffers = self.buffer.read(cx).all_buffers();
2736 let Some(project) = self.project.as_ref() else {
2737 return;
2738 };
2739 project.update(cx, |project, cx| {
2740 for buffer in buffers {
2741 self.registered_buffers
2742 .entry(buffer.read(cx).remote_id())
2743 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2744 }
2745 })
2746 }
2747
2748 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2749 if self.collapse_matches {
2750 return range.start..range.start;
2751 }
2752 range.clone()
2753 }
2754
2755 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2756 if self.display_map.read(cx).clip_at_line_ends != clip {
2757 self.display_map
2758 .update(cx, |map, _| map.clip_at_line_ends = clip);
2759 }
2760 }
2761
2762 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2763 self.input_enabled = input_enabled;
2764 }
2765
2766 pub fn set_inline_completions_hidden_for_vim_mode(
2767 &mut self,
2768 hidden: bool,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 if hidden != self.inline_completions_hidden_for_vim_mode {
2773 self.inline_completions_hidden_for_vim_mode = hidden;
2774 if hidden {
2775 self.update_visible_inline_completion(window, cx);
2776 } else {
2777 self.refresh_inline_completion(true, false, window, cx);
2778 }
2779 }
2780 }
2781
2782 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2783 self.menu_inline_completions_policy = value;
2784 }
2785
2786 pub fn set_autoindent(&mut self, autoindent: bool) {
2787 if autoindent {
2788 self.autoindent_mode = Some(AutoindentMode::EachLine);
2789 } else {
2790 self.autoindent_mode = None;
2791 }
2792 }
2793
2794 pub fn read_only(&self, cx: &App) -> bool {
2795 self.read_only || self.buffer.read(cx).read_only()
2796 }
2797
2798 pub fn set_read_only(&mut self, read_only: bool) {
2799 self.read_only = read_only;
2800 }
2801
2802 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2803 self.use_autoclose = autoclose;
2804 }
2805
2806 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2807 self.use_auto_surround = auto_surround;
2808 }
2809
2810 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2811 self.auto_replace_emoji_shortcode = auto_replace;
2812 }
2813
2814 pub fn toggle_edit_predictions(
2815 &mut self,
2816 _: &ToggleEditPrediction,
2817 window: &mut Window,
2818 cx: &mut Context<Self>,
2819 ) {
2820 if self.show_inline_completions_override.is_some() {
2821 self.set_show_edit_predictions(None, window, cx);
2822 } else {
2823 let show_edit_predictions = !self.edit_predictions_enabled();
2824 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2825 }
2826 }
2827
2828 pub fn set_show_edit_predictions(
2829 &mut self,
2830 show_edit_predictions: Option<bool>,
2831 window: &mut Window,
2832 cx: &mut Context<Self>,
2833 ) {
2834 self.show_inline_completions_override = show_edit_predictions;
2835 self.update_edit_prediction_settings(cx);
2836
2837 if let Some(false) = show_edit_predictions {
2838 self.discard_inline_completion(false, cx);
2839 } else {
2840 self.refresh_inline_completion(false, true, window, cx);
2841 }
2842 }
2843
2844 fn inline_completions_disabled_in_scope(
2845 &self,
2846 buffer: &Entity<Buffer>,
2847 buffer_position: language::Anchor,
2848 cx: &App,
2849 ) -> bool {
2850 let snapshot = buffer.read(cx).snapshot();
2851 let settings = snapshot.settings_at(buffer_position, cx);
2852
2853 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2854 return false;
2855 };
2856
2857 scope.override_name().map_or(false, |scope_name| {
2858 settings
2859 .edit_predictions_disabled_in
2860 .iter()
2861 .any(|s| s == scope_name)
2862 })
2863 }
2864
2865 pub fn set_use_modal_editing(&mut self, to: bool) {
2866 self.use_modal_editing = to;
2867 }
2868
2869 pub fn use_modal_editing(&self) -> bool {
2870 self.use_modal_editing
2871 }
2872
2873 fn selections_did_change(
2874 &mut self,
2875 local: bool,
2876 old_cursor_position: &Anchor,
2877 effects: SelectionEffects,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 window.invalidate_character_coordinates();
2882
2883 // Copy selections to primary selection buffer
2884 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2885 if local {
2886 let selections = self.selections.all::<usize>(cx);
2887 let buffer_handle = self.buffer.read(cx).read(cx);
2888
2889 let mut text = String::new();
2890 for (index, selection) in selections.iter().enumerate() {
2891 let text_for_selection = buffer_handle
2892 .text_for_range(selection.start..selection.end)
2893 .collect::<String>();
2894
2895 text.push_str(&text_for_selection);
2896 if index != selections.len() - 1 {
2897 text.push('\n');
2898 }
2899 }
2900
2901 if !text.is_empty() {
2902 cx.write_to_primary(ClipboardItem::new_string(text));
2903 }
2904 }
2905
2906 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2907 self.buffer.update(cx, |buffer, cx| {
2908 buffer.set_active_selections(
2909 &self.selections.disjoint_anchors(),
2910 self.selections.line_mode,
2911 self.cursor_shape,
2912 cx,
2913 )
2914 });
2915 }
2916 let display_map = self
2917 .display_map
2918 .update(cx, |display_map, cx| display_map.snapshot(cx));
2919 let buffer = &display_map.buffer_snapshot;
2920 if self.selections.count() == 1 {
2921 self.add_selections_state = None;
2922 }
2923 self.select_next_state = None;
2924 self.select_prev_state = None;
2925 self.select_syntax_node_history.try_clear();
2926 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2927 self.snippet_stack
2928 .invalidate(&self.selections.disjoint_anchors(), buffer);
2929 self.take_rename(false, window, cx);
2930
2931 let newest_selection = self.selections.newest_anchor();
2932 let new_cursor_position = newest_selection.head();
2933 let selection_start = newest_selection.start;
2934
2935 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
2936 self.push_to_nav_history(
2937 *old_cursor_position,
2938 Some(new_cursor_position.to_point(buffer)),
2939 false,
2940 effects.nav_history == Some(true),
2941 cx,
2942 );
2943 }
2944
2945 if local {
2946 if let Some(buffer_id) = new_cursor_position.buffer_id {
2947 if !self.registered_buffers.contains_key(&buffer_id) {
2948 if let Some(project) = self.project.as_ref() {
2949 project.update(cx, |project, cx| {
2950 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2951 return;
2952 };
2953 self.registered_buffers.insert(
2954 buffer_id,
2955 project.register_buffer_with_language_servers(&buffer, cx),
2956 );
2957 })
2958 }
2959 }
2960 }
2961
2962 let mut context_menu = self.context_menu.borrow_mut();
2963 let completion_menu = match context_menu.as_ref() {
2964 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2965 Some(CodeContextMenu::CodeActions(_)) => {
2966 *context_menu = None;
2967 None
2968 }
2969 None => None,
2970 };
2971 let completion_position = completion_menu.map(|menu| menu.initial_position);
2972 drop(context_menu);
2973
2974 if effects.completions {
2975 if let Some(completion_position) = completion_position {
2976 let start_offset = selection_start.to_offset(buffer);
2977 let position_matches = start_offset == completion_position.to_offset(buffer);
2978 let continue_showing = if position_matches {
2979 if self.snippet_stack.is_empty() {
2980 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2981 } else {
2982 // Snippet choices can be shown even when the cursor is in whitespace.
2983 // Dismissing the menu with actions like backspace is handled by
2984 // invalidation regions.
2985 true
2986 }
2987 } else {
2988 false
2989 };
2990
2991 if continue_showing {
2992 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2993 } else {
2994 self.hide_context_menu(window, cx);
2995 }
2996 }
2997 }
2998
2999 hide_hover(self, cx);
3000
3001 if old_cursor_position.to_display_point(&display_map).row()
3002 != new_cursor_position.to_display_point(&display_map).row()
3003 {
3004 self.available_code_actions.take();
3005 }
3006 self.refresh_code_actions(window, cx);
3007 self.refresh_document_highlights(cx);
3008 self.refresh_selected_text_highlights(false, window, cx);
3009 refresh_matching_bracket_highlights(self, window, cx);
3010 self.update_visible_inline_completion(window, cx);
3011 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3012 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3013 self.inline_blame_popover.take();
3014 if self.git_blame_inline_enabled {
3015 self.start_inline_blame_timer(window, cx);
3016 }
3017 }
3018
3019 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3020 cx.emit(EditorEvent::SelectionsChanged { local });
3021
3022 let selections = &self.selections.disjoint;
3023 if selections.len() == 1 {
3024 cx.emit(SearchEvent::ActiveMatchChanged)
3025 }
3026 if local {
3027 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3028 let inmemory_selections = selections
3029 .iter()
3030 .map(|s| {
3031 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3032 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3033 })
3034 .collect();
3035 self.update_restoration_data(cx, |data| {
3036 data.selections = inmemory_selections;
3037 });
3038
3039 if WorkspaceSettings::get(None, cx).restore_on_startup
3040 != RestoreOnStartupBehavior::None
3041 {
3042 if let Some(workspace_id) =
3043 self.workspace.as_ref().and_then(|workspace| workspace.1)
3044 {
3045 let snapshot = self.buffer().read(cx).snapshot(cx);
3046 let selections = selections.clone();
3047 let background_executor = cx.background_executor().clone();
3048 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3049 self.serialize_selections = cx.background_spawn(async move {
3050 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3051 let db_selections = selections
3052 .iter()
3053 .map(|selection| {
3054 (
3055 selection.start.to_offset(&snapshot),
3056 selection.end.to_offset(&snapshot),
3057 )
3058 })
3059 .collect();
3060
3061 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3062 .await
3063 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3064 .log_err();
3065 });
3066 }
3067 }
3068 }
3069 }
3070
3071 cx.notify();
3072 }
3073
3074 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3075 use text::ToOffset as _;
3076 use text::ToPoint as _;
3077
3078 if self.mode.is_minimap()
3079 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3080 {
3081 return;
3082 }
3083
3084 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3085 return;
3086 };
3087
3088 let snapshot = singleton.read(cx).snapshot();
3089 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3090 let display_snapshot = display_map.snapshot(cx);
3091
3092 display_snapshot
3093 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3094 .map(|fold| {
3095 fold.range.start.text_anchor.to_point(&snapshot)
3096 ..fold.range.end.text_anchor.to_point(&snapshot)
3097 })
3098 .collect()
3099 });
3100 self.update_restoration_data(cx, |data| {
3101 data.folds = inmemory_folds;
3102 });
3103
3104 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3105 return;
3106 };
3107 let background_executor = cx.background_executor().clone();
3108 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3109 let db_folds = self.display_map.update(cx, |display_map, cx| {
3110 display_map
3111 .snapshot(cx)
3112 .folds_in_range(0..snapshot.len())
3113 .map(|fold| {
3114 (
3115 fold.range.start.text_anchor.to_offset(&snapshot),
3116 fold.range.end.text_anchor.to_offset(&snapshot),
3117 )
3118 })
3119 .collect()
3120 });
3121 self.serialize_folds = cx.background_spawn(async move {
3122 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3123 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3124 .await
3125 .with_context(|| {
3126 format!(
3127 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3128 )
3129 })
3130 .log_err();
3131 });
3132 }
3133
3134 pub fn sync_selections(
3135 &mut self,
3136 other: Entity<Editor>,
3137 cx: &mut Context<Self>,
3138 ) -> gpui::Subscription {
3139 let other_selections = other.read(cx).selections.disjoint.to_vec();
3140 self.selections.change_with(cx, |selections| {
3141 selections.select_anchors(other_selections);
3142 });
3143
3144 let other_subscription =
3145 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3146 EditorEvent::SelectionsChanged { local: true } => {
3147 let other_selections = other.read(cx).selections.disjoint.to_vec();
3148 if other_selections.is_empty() {
3149 return;
3150 }
3151 this.selections.change_with(cx, |selections| {
3152 selections.select_anchors(other_selections);
3153 });
3154 }
3155 _ => {}
3156 });
3157
3158 let this_subscription =
3159 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3160 EditorEvent::SelectionsChanged { local: true } => {
3161 let these_selections = this.selections.disjoint.to_vec();
3162 if these_selections.is_empty() {
3163 return;
3164 }
3165 other.update(cx, |other_editor, cx| {
3166 other_editor.selections.change_with(cx, |selections| {
3167 selections.select_anchors(these_selections);
3168 })
3169 });
3170 }
3171 _ => {}
3172 });
3173
3174 Subscription::join(other_subscription, this_subscription)
3175 }
3176
3177 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3178 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3179 /// effects of selection change occur at the end of the transaction.
3180 pub fn change_selections<R>(
3181 &mut self,
3182 effects: SelectionEffects,
3183 window: &mut Window,
3184 cx: &mut Context<Self>,
3185 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3186 ) -> R {
3187 if let Some(state) = &mut self.deferred_selection_effects_state {
3188 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3189 state.effects.completions = effects.completions;
3190 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3191 let (changed, result) = self.selections.change_with(cx, change);
3192 state.changed |= changed;
3193 return result;
3194 }
3195 let mut state = DeferredSelectionEffectsState {
3196 changed: false,
3197 effects,
3198 old_cursor_position: self.selections.newest_anchor().head(),
3199 history_entry: SelectionHistoryEntry {
3200 selections: self.selections.disjoint_anchors(),
3201 select_next_state: self.select_next_state.clone(),
3202 select_prev_state: self.select_prev_state.clone(),
3203 add_selections_state: self.add_selections_state.clone(),
3204 },
3205 };
3206 let (changed, result) = self.selections.change_with(cx, change);
3207 state.changed = state.changed || changed;
3208 if self.defer_selection_effects {
3209 self.deferred_selection_effects_state = Some(state);
3210 } else {
3211 self.apply_selection_effects(state, window, cx);
3212 }
3213 result
3214 }
3215
3216 /// Defers the effects of selection change, so that the effects of multiple calls to
3217 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3218 /// to selection history and the state of popovers based on selection position aren't
3219 /// erroneously updated.
3220 pub fn with_selection_effects_deferred<R>(
3221 &mut self,
3222 window: &mut Window,
3223 cx: &mut Context<Self>,
3224 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3225 ) -> R {
3226 let already_deferred = self.defer_selection_effects;
3227 self.defer_selection_effects = true;
3228 let result = update(self, window, cx);
3229 if !already_deferred {
3230 self.defer_selection_effects = false;
3231 if let Some(state) = self.deferred_selection_effects_state.take() {
3232 self.apply_selection_effects(state, window, cx);
3233 }
3234 }
3235 result
3236 }
3237
3238 fn apply_selection_effects(
3239 &mut self,
3240 state: DeferredSelectionEffectsState,
3241 window: &mut Window,
3242 cx: &mut Context<Self>,
3243 ) {
3244 if state.changed {
3245 self.selection_history.push(state.history_entry);
3246
3247 if let Some(autoscroll) = state.effects.scroll {
3248 self.request_autoscroll(autoscroll, cx);
3249 }
3250
3251 let old_cursor_position = &state.old_cursor_position;
3252
3253 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3254
3255 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3256 self.show_signature_help(&ShowSignatureHelp, window, cx);
3257 }
3258 }
3259 }
3260
3261 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3262 where
3263 I: IntoIterator<Item = (Range<S>, T)>,
3264 S: ToOffset,
3265 T: Into<Arc<str>>,
3266 {
3267 if self.read_only(cx) {
3268 return;
3269 }
3270
3271 self.buffer
3272 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3273 }
3274
3275 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3276 where
3277 I: IntoIterator<Item = (Range<S>, T)>,
3278 S: ToOffset,
3279 T: Into<Arc<str>>,
3280 {
3281 if self.read_only(cx) {
3282 return;
3283 }
3284
3285 self.buffer.update(cx, |buffer, cx| {
3286 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3287 });
3288 }
3289
3290 pub fn edit_with_block_indent<I, S, T>(
3291 &mut self,
3292 edits: I,
3293 original_indent_columns: Vec<Option<u32>>,
3294 cx: &mut Context<Self>,
3295 ) where
3296 I: IntoIterator<Item = (Range<S>, T)>,
3297 S: ToOffset,
3298 T: Into<Arc<str>>,
3299 {
3300 if self.read_only(cx) {
3301 return;
3302 }
3303
3304 self.buffer.update(cx, |buffer, cx| {
3305 buffer.edit(
3306 edits,
3307 Some(AutoindentMode::Block {
3308 original_indent_columns,
3309 }),
3310 cx,
3311 )
3312 });
3313 }
3314
3315 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3316 self.hide_context_menu(window, cx);
3317
3318 match phase {
3319 SelectPhase::Begin {
3320 position,
3321 add,
3322 click_count,
3323 } => self.begin_selection(position, add, click_count, window, cx),
3324 SelectPhase::BeginColumnar {
3325 position,
3326 goal_column,
3327 reset,
3328 mode,
3329 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3330 SelectPhase::Extend {
3331 position,
3332 click_count,
3333 } => self.extend_selection(position, click_count, window, cx),
3334 SelectPhase::Update {
3335 position,
3336 goal_column,
3337 scroll_delta,
3338 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3339 SelectPhase::End => self.end_selection(window, cx),
3340 }
3341 }
3342
3343 fn extend_selection(
3344 &mut self,
3345 position: DisplayPoint,
3346 click_count: usize,
3347 window: &mut Window,
3348 cx: &mut Context<Self>,
3349 ) {
3350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3351 let tail = self.selections.newest::<usize>(cx).tail();
3352 self.begin_selection(position, false, click_count, window, cx);
3353
3354 let position = position.to_offset(&display_map, Bias::Left);
3355 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3356
3357 let mut pending_selection = self
3358 .selections
3359 .pending_anchor()
3360 .expect("extend_selection not called with pending selection");
3361 if position >= tail {
3362 pending_selection.start = tail_anchor;
3363 } else {
3364 pending_selection.end = tail_anchor;
3365 pending_selection.reversed = true;
3366 }
3367
3368 let mut pending_mode = self.selections.pending_mode().unwrap();
3369 match &mut pending_mode {
3370 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3371 _ => {}
3372 }
3373
3374 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3375 SelectionEffects::scroll(Autoscroll::fit())
3376 } else {
3377 SelectionEffects::no_scroll()
3378 };
3379
3380 self.change_selections(effects, window, cx, |s| {
3381 s.set_pending(pending_selection, pending_mode)
3382 });
3383 }
3384
3385 fn begin_selection(
3386 &mut self,
3387 position: DisplayPoint,
3388 add: bool,
3389 click_count: usize,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 if !self.focus_handle.is_focused(window) {
3394 self.last_focused_descendant = None;
3395 window.focus(&self.focus_handle);
3396 }
3397
3398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3399 let buffer = &display_map.buffer_snapshot;
3400 let position = display_map.clip_point(position, Bias::Left);
3401
3402 let start;
3403 let end;
3404 let mode;
3405 let mut auto_scroll;
3406 match click_count {
3407 1 => {
3408 start = buffer.anchor_before(position.to_point(&display_map));
3409 end = start;
3410 mode = SelectMode::Character;
3411 auto_scroll = true;
3412 }
3413 2 => {
3414 let position = display_map
3415 .clip_point(position, Bias::Left)
3416 .to_offset(&display_map, Bias::Left);
3417 let (range, _) = buffer.surrounding_word(position, false);
3418 start = buffer.anchor_before(range.start);
3419 end = buffer.anchor_before(range.end);
3420 mode = SelectMode::Word(start..end);
3421 auto_scroll = true;
3422 }
3423 3 => {
3424 let position = display_map
3425 .clip_point(position, Bias::Left)
3426 .to_point(&display_map);
3427 let line_start = display_map.prev_line_boundary(position).0;
3428 let next_line_start = buffer.clip_point(
3429 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3430 Bias::Left,
3431 );
3432 start = buffer.anchor_before(line_start);
3433 end = buffer.anchor_before(next_line_start);
3434 mode = SelectMode::Line(start..end);
3435 auto_scroll = true;
3436 }
3437 _ => {
3438 start = buffer.anchor_before(0);
3439 end = buffer.anchor_before(buffer.len());
3440 mode = SelectMode::All;
3441 auto_scroll = false;
3442 }
3443 }
3444 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3445
3446 let point_to_delete: Option<usize> = {
3447 let selected_points: Vec<Selection<Point>> =
3448 self.selections.disjoint_in_range(start..end, cx);
3449
3450 if !add || click_count > 1 {
3451 None
3452 } else if !selected_points.is_empty() {
3453 Some(selected_points[0].id)
3454 } else {
3455 let clicked_point_already_selected =
3456 self.selections.disjoint.iter().find(|selection| {
3457 selection.start.to_point(buffer) == start.to_point(buffer)
3458 || selection.end.to_point(buffer) == end.to_point(buffer)
3459 });
3460
3461 clicked_point_already_selected.map(|selection| selection.id)
3462 }
3463 };
3464
3465 let selections_count = self.selections.count();
3466 let effects = if auto_scroll {
3467 SelectionEffects::default()
3468 } else {
3469 SelectionEffects::no_scroll()
3470 };
3471
3472 self.change_selections(effects, window, cx, |s| {
3473 if let Some(point_to_delete) = point_to_delete {
3474 s.delete(point_to_delete);
3475
3476 if selections_count == 1 {
3477 s.set_pending_anchor_range(start..end, mode);
3478 }
3479 } else {
3480 if !add {
3481 s.clear_disjoint();
3482 }
3483
3484 s.set_pending_anchor_range(start..end, mode);
3485 }
3486 });
3487 }
3488
3489 fn begin_columnar_selection(
3490 &mut self,
3491 position: DisplayPoint,
3492 goal_column: u32,
3493 reset: bool,
3494 mode: ColumnarMode,
3495 window: &mut Window,
3496 cx: &mut Context<Self>,
3497 ) {
3498 if !self.focus_handle.is_focused(window) {
3499 self.last_focused_descendant = None;
3500 window.focus(&self.focus_handle);
3501 }
3502
3503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3504
3505 if reset {
3506 let pointer_position = display_map
3507 .buffer_snapshot
3508 .anchor_before(position.to_point(&display_map));
3509
3510 self.change_selections(
3511 SelectionEffects::scroll(Autoscroll::newest()),
3512 window,
3513 cx,
3514 |s| {
3515 s.clear_disjoint();
3516 s.set_pending_anchor_range(
3517 pointer_position..pointer_position,
3518 SelectMode::Character,
3519 );
3520 },
3521 );
3522 };
3523
3524 let tail = self.selections.newest::<Point>(cx).tail();
3525 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3526 self.columnar_selection_state = match mode {
3527 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3528 selection_tail: selection_anchor,
3529 display_point: if reset {
3530 if position.column() != goal_column {
3531 Some(DisplayPoint::new(position.row(), goal_column))
3532 } else {
3533 None
3534 }
3535 } else {
3536 None
3537 },
3538 }),
3539 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3540 selection_tail: selection_anchor,
3541 }),
3542 };
3543
3544 if !reset {
3545 self.select_columns(position, goal_column, &display_map, window, cx);
3546 }
3547 }
3548
3549 fn update_selection(
3550 &mut self,
3551 position: DisplayPoint,
3552 goal_column: u32,
3553 scroll_delta: gpui::Point<f32>,
3554 window: &mut Window,
3555 cx: &mut Context<Self>,
3556 ) {
3557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3558
3559 if self.columnar_selection_state.is_some() {
3560 self.select_columns(position, goal_column, &display_map, window, cx);
3561 } else if let Some(mut pending) = self.selections.pending_anchor() {
3562 let buffer = &display_map.buffer_snapshot;
3563 let head;
3564 let tail;
3565 let mode = self.selections.pending_mode().unwrap();
3566 match &mode {
3567 SelectMode::Character => {
3568 head = position.to_point(&display_map);
3569 tail = pending.tail().to_point(buffer);
3570 }
3571 SelectMode::Word(original_range) => {
3572 let offset = display_map
3573 .clip_point(position, Bias::Left)
3574 .to_offset(&display_map, Bias::Left);
3575 let original_range = original_range.to_offset(buffer);
3576
3577 let head_offset = if buffer.is_inside_word(offset, false)
3578 || original_range.contains(&offset)
3579 {
3580 let (word_range, _) = buffer.surrounding_word(offset, false);
3581 if word_range.start < original_range.start {
3582 word_range.start
3583 } else {
3584 word_range.end
3585 }
3586 } else {
3587 offset
3588 };
3589
3590 head = head_offset.to_point(buffer);
3591 if head_offset <= original_range.start {
3592 tail = original_range.end.to_point(buffer);
3593 } else {
3594 tail = original_range.start.to_point(buffer);
3595 }
3596 }
3597 SelectMode::Line(original_range) => {
3598 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3599
3600 let position = display_map
3601 .clip_point(position, Bias::Left)
3602 .to_point(&display_map);
3603 let line_start = display_map.prev_line_boundary(position).0;
3604 let next_line_start = buffer.clip_point(
3605 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3606 Bias::Left,
3607 );
3608
3609 if line_start < original_range.start {
3610 head = line_start
3611 } else {
3612 head = next_line_start
3613 }
3614
3615 if head <= original_range.start {
3616 tail = original_range.end;
3617 } else {
3618 tail = original_range.start;
3619 }
3620 }
3621 SelectMode::All => {
3622 return;
3623 }
3624 };
3625
3626 if head < tail {
3627 pending.start = buffer.anchor_before(head);
3628 pending.end = buffer.anchor_before(tail);
3629 pending.reversed = true;
3630 } else {
3631 pending.start = buffer.anchor_before(tail);
3632 pending.end = buffer.anchor_before(head);
3633 pending.reversed = false;
3634 }
3635
3636 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3637 s.set_pending(pending, mode);
3638 });
3639 } else {
3640 log::error!("update_selection dispatched with no pending selection");
3641 return;
3642 }
3643
3644 self.apply_scroll_delta(scroll_delta, window, cx);
3645 cx.notify();
3646 }
3647
3648 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3649 self.columnar_selection_state.take();
3650 if self.selections.pending_anchor().is_some() {
3651 let selections = self.selections.all::<usize>(cx);
3652 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3653 s.select(selections);
3654 s.clear_pending();
3655 });
3656 }
3657 }
3658
3659 fn select_columns(
3660 &mut self,
3661 head: DisplayPoint,
3662 goal_column: u32,
3663 display_map: &DisplaySnapshot,
3664 window: &mut Window,
3665 cx: &mut Context<Self>,
3666 ) {
3667 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3668 return;
3669 };
3670
3671 let tail = match columnar_state {
3672 ColumnarSelectionState::FromMouse {
3673 selection_tail,
3674 display_point,
3675 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3676 ColumnarSelectionState::FromSelection { selection_tail } => {
3677 selection_tail.to_display_point(&display_map)
3678 }
3679 };
3680
3681 let start_row = cmp::min(tail.row(), head.row());
3682 let end_row = cmp::max(tail.row(), head.row());
3683 let start_column = cmp::min(tail.column(), goal_column);
3684 let end_column = cmp::max(tail.column(), goal_column);
3685 let reversed = start_column < tail.column();
3686
3687 let selection_ranges = (start_row.0..=end_row.0)
3688 .map(DisplayRow)
3689 .filter_map(|row| {
3690 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3691 || start_column <= display_map.line_len(row))
3692 && !display_map.is_block_line(row)
3693 {
3694 let start = display_map
3695 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3696 .to_point(display_map);
3697 let end = display_map
3698 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3699 .to_point(display_map);
3700 if reversed {
3701 Some(end..start)
3702 } else {
3703 Some(start..end)
3704 }
3705 } else {
3706 None
3707 }
3708 })
3709 .collect::<Vec<_>>();
3710
3711 let ranges = match columnar_state {
3712 ColumnarSelectionState::FromMouse { .. } => {
3713 let mut non_empty_ranges = selection_ranges
3714 .iter()
3715 .filter(|selection_range| selection_range.start != selection_range.end)
3716 .peekable();
3717 if non_empty_ranges.peek().is_some() {
3718 non_empty_ranges.cloned().collect()
3719 } else {
3720 selection_ranges
3721 }
3722 }
3723 _ => selection_ranges,
3724 };
3725
3726 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3727 s.select_ranges(ranges);
3728 });
3729 cx.notify();
3730 }
3731
3732 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3733 self.selections
3734 .all_adjusted(cx)
3735 .iter()
3736 .any(|selection| !selection.is_empty())
3737 }
3738
3739 pub fn has_pending_nonempty_selection(&self) -> bool {
3740 let pending_nonempty_selection = match self.selections.pending_anchor() {
3741 Some(Selection { start, end, .. }) => start != end,
3742 None => false,
3743 };
3744
3745 pending_nonempty_selection
3746 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3747 }
3748
3749 pub fn has_pending_selection(&self) -> bool {
3750 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3751 }
3752
3753 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3754 self.selection_mark_mode = false;
3755 self.selection_drag_state = SelectionDragState::None;
3756
3757 if self.clear_expanded_diff_hunks(cx) {
3758 cx.notify();
3759 return;
3760 }
3761 if self.dismiss_menus_and_popups(true, window, cx) {
3762 return;
3763 }
3764
3765 if self.mode.is_full()
3766 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3767 {
3768 return;
3769 }
3770
3771 cx.propagate();
3772 }
3773
3774 pub fn dismiss_menus_and_popups(
3775 &mut self,
3776 is_user_requested: bool,
3777 window: &mut Window,
3778 cx: &mut Context<Self>,
3779 ) -> bool {
3780 if self.take_rename(false, window, cx).is_some() {
3781 return true;
3782 }
3783
3784 if hide_hover(self, cx) {
3785 return true;
3786 }
3787
3788 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3789 return true;
3790 }
3791
3792 if self.hide_context_menu(window, cx).is_some() {
3793 return true;
3794 }
3795
3796 if self.mouse_context_menu.take().is_some() {
3797 return true;
3798 }
3799
3800 if is_user_requested && self.discard_inline_completion(true, cx) {
3801 return true;
3802 }
3803
3804 if self.snippet_stack.pop().is_some() {
3805 return true;
3806 }
3807
3808 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3809 self.dismiss_diagnostics(cx);
3810 return true;
3811 }
3812
3813 false
3814 }
3815
3816 fn linked_editing_ranges_for(
3817 &self,
3818 selection: Range<text::Anchor>,
3819 cx: &App,
3820 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3821 if self.linked_edit_ranges.is_empty() {
3822 return None;
3823 }
3824 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3825 selection.end.buffer_id.and_then(|end_buffer_id| {
3826 if selection.start.buffer_id != Some(end_buffer_id) {
3827 return None;
3828 }
3829 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3830 let snapshot = buffer.read(cx).snapshot();
3831 self.linked_edit_ranges
3832 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3833 .map(|ranges| (ranges, snapshot, buffer))
3834 })?;
3835 use text::ToOffset as TO;
3836 // find offset from the start of current range to current cursor position
3837 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3838
3839 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3840 let start_difference = start_offset - start_byte_offset;
3841 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3842 let end_difference = end_offset - start_byte_offset;
3843 // Current range has associated linked ranges.
3844 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3845 for range in linked_ranges.iter() {
3846 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3847 let end_offset = start_offset + end_difference;
3848 let start_offset = start_offset + start_difference;
3849 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3850 continue;
3851 }
3852 if self.selections.disjoint_anchor_ranges().any(|s| {
3853 if s.start.buffer_id != selection.start.buffer_id
3854 || s.end.buffer_id != selection.end.buffer_id
3855 {
3856 return false;
3857 }
3858 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3859 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3860 }) {
3861 continue;
3862 }
3863 let start = buffer_snapshot.anchor_after(start_offset);
3864 let end = buffer_snapshot.anchor_after(end_offset);
3865 linked_edits
3866 .entry(buffer.clone())
3867 .or_default()
3868 .push(start..end);
3869 }
3870 Some(linked_edits)
3871 }
3872
3873 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3874 let text: Arc<str> = text.into();
3875
3876 if self.read_only(cx) {
3877 return;
3878 }
3879
3880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3881
3882 let selections = self.selections.all_adjusted(cx);
3883 let mut bracket_inserted = false;
3884 let mut edits = Vec::new();
3885 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3886 let mut new_selections = Vec::with_capacity(selections.len());
3887 let mut new_autoclose_regions = Vec::new();
3888 let snapshot = self.buffer.read(cx).read(cx);
3889 let mut clear_linked_edit_ranges = false;
3890
3891 for (selection, autoclose_region) in
3892 self.selections_with_autoclose_regions(selections, &snapshot)
3893 {
3894 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3895 // Determine if the inserted text matches the opening or closing
3896 // bracket of any of this language's bracket pairs.
3897 let mut bracket_pair = None;
3898 let mut is_bracket_pair_start = false;
3899 let mut is_bracket_pair_end = false;
3900 if !text.is_empty() {
3901 let mut bracket_pair_matching_end = None;
3902 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3903 // and they are removing the character that triggered IME popup.
3904 for (pair, enabled) in scope.brackets() {
3905 if !pair.close && !pair.surround {
3906 continue;
3907 }
3908
3909 if enabled && pair.start.ends_with(text.as_ref()) {
3910 let prefix_len = pair.start.len() - text.len();
3911 let preceding_text_matches_prefix = prefix_len == 0
3912 || (selection.start.column >= (prefix_len as u32)
3913 && snapshot.contains_str_at(
3914 Point::new(
3915 selection.start.row,
3916 selection.start.column - (prefix_len as u32),
3917 ),
3918 &pair.start[..prefix_len],
3919 ));
3920 if preceding_text_matches_prefix {
3921 bracket_pair = Some(pair.clone());
3922 is_bracket_pair_start = true;
3923 break;
3924 }
3925 }
3926 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3927 {
3928 // take first bracket pair matching end, but don't break in case a later bracket
3929 // pair matches start
3930 bracket_pair_matching_end = Some(pair.clone());
3931 }
3932 }
3933 if let Some(end) = bracket_pair_matching_end
3934 && bracket_pair.is_none()
3935 {
3936 bracket_pair = Some(end);
3937 is_bracket_pair_end = true;
3938 }
3939 }
3940
3941 if let Some(bracket_pair) = bracket_pair {
3942 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3943 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3944 let auto_surround =
3945 self.use_auto_surround && snapshot_settings.use_auto_surround;
3946 if selection.is_empty() {
3947 if is_bracket_pair_start {
3948 // If the inserted text is a suffix of an opening bracket and the
3949 // selection is preceded by the rest of the opening bracket, then
3950 // insert the closing bracket.
3951 let following_text_allows_autoclose = snapshot
3952 .chars_at(selection.start)
3953 .next()
3954 .map_or(true, |c| scope.should_autoclose_before(c));
3955
3956 let preceding_text_allows_autoclose = selection.start.column == 0
3957 || snapshot.reversed_chars_at(selection.start).next().map_or(
3958 true,
3959 |c| {
3960 bracket_pair.start != bracket_pair.end
3961 || !snapshot
3962 .char_classifier_at(selection.start)
3963 .is_word(c)
3964 },
3965 );
3966
3967 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3968 && bracket_pair.start.len() == 1
3969 {
3970 let target = bracket_pair.start.chars().next().unwrap();
3971 let current_line_count = snapshot
3972 .reversed_chars_at(selection.start)
3973 .take_while(|&c| c != '\n')
3974 .filter(|&c| c == target)
3975 .count();
3976 current_line_count % 2 == 1
3977 } else {
3978 false
3979 };
3980
3981 if autoclose
3982 && bracket_pair.close
3983 && following_text_allows_autoclose
3984 && preceding_text_allows_autoclose
3985 && !is_closing_quote
3986 {
3987 let anchor = snapshot.anchor_before(selection.end);
3988 new_selections.push((selection.map(|_| anchor), text.len()));
3989 new_autoclose_regions.push((
3990 anchor,
3991 text.len(),
3992 selection.id,
3993 bracket_pair.clone(),
3994 ));
3995 edits.push((
3996 selection.range(),
3997 format!("{}{}", text, bracket_pair.end).into(),
3998 ));
3999 bracket_inserted = true;
4000 continue;
4001 }
4002 }
4003
4004 if let Some(region) = autoclose_region {
4005 // If the selection is followed by an auto-inserted closing bracket,
4006 // then don't insert that closing bracket again; just move the selection
4007 // past the closing bracket.
4008 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4009 && text.as_ref() == region.pair.end.as_str();
4010 if should_skip {
4011 let anchor = snapshot.anchor_after(selection.end);
4012 new_selections
4013 .push((selection.map(|_| anchor), region.pair.end.len()));
4014 continue;
4015 }
4016 }
4017
4018 let always_treat_brackets_as_autoclosed = snapshot
4019 .language_settings_at(selection.start, cx)
4020 .always_treat_brackets_as_autoclosed;
4021 if always_treat_brackets_as_autoclosed
4022 && is_bracket_pair_end
4023 && snapshot.contains_str_at(selection.end, text.as_ref())
4024 {
4025 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4026 // and the inserted text is a closing bracket and the selection is followed
4027 // by the closing bracket then move the selection past the closing bracket.
4028 let anchor = snapshot.anchor_after(selection.end);
4029 new_selections.push((selection.map(|_| anchor), text.len()));
4030 continue;
4031 }
4032 }
4033 // If an opening bracket is 1 character long and is typed while
4034 // text is selected, then surround that text with the bracket pair.
4035 else if auto_surround
4036 && bracket_pair.surround
4037 && is_bracket_pair_start
4038 && bracket_pair.start.chars().count() == 1
4039 {
4040 edits.push((selection.start..selection.start, text.clone()));
4041 edits.push((
4042 selection.end..selection.end,
4043 bracket_pair.end.as_str().into(),
4044 ));
4045 bracket_inserted = true;
4046 new_selections.push((
4047 Selection {
4048 id: selection.id,
4049 start: snapshot.anchor_after(selection.start),
4050 end: snapshot.anchor_before(selection.end),
4051 reversed: selection.reversed,
4052 goal: selection.goal,
4053 },
4054 0,
4055 ));
4056 continue;
4057 }
4058 }
4059 }
4060
4061 if self.auto_replace_emoji_shortcode
4062 && selection.is_empty()
4063 && text.as_ref().ends_with(':')
4064 {
4065 if let Some(possible_emoji_short_code) =
4066 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4067 {
4068 if !possible_emoji_short_code.is_empty() {
4069 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4070 let emoji_shortcode_start = Point::new(
4071 selection.start.row,
4072 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4073 );
4074
4075 // Remove shortcode from buffer
4076 edits.push((
4077 emoji_shortcode_start..selection.start,
4078 "".to_string().into(),
4079 ));
4080 new_selections.push((
4081 Selection {
4082 id: selection.id,
4083 start: snapshot.anchor_after(emoji_shortcode_start),
4084 end: snapshot.anchor_before(selection.start),
4085 reversed: selection.reversed,
4086 goal: selection.goal,
4087 },
4088 0,
4089 ));
4090
4091 // Insert emoji
4092 let selection_start_anchor = snapshot.anchor_after(selection.start);
4093 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4094 edits.push((selection.start..selection.end, emoji.to_string().into()));
4095
4096 continue;
4097 }
4098 }
4099 }
4100 }
4101
4102 // If not handling any auto-close operation, then just replace the selected
4103 // text with the given input and move the selection to the end of the
4104 // newly inserted text.
4105 let anchor = snapshot.anchor_after(selection.end);
4106 if !self.linked_edit_ranges.is_empty() {
4107 let start_anchor = snapshot.anchor_before(selection.start);
4108
4109 let is_word_char = text.chars().next().map_or(true, |char| {
4110 let classifier = snapshot
4111 .char_classifier_at(start_anchor.to_offset(&snapshot))
4112 .ignore_punctuation(true);
4113 classifier.is_word(char)
4114 });
4115
4116 if is_word_char {
4117 if let Some(ranges) = self
4118 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4119 {
4120 for (buffer, edits) in ranges {
4121 linked_edits
4122 .entry(buffer.clone())
4123 .or_default()
4124 .extend(edits.into_iter().map(|range| (range, text.clone())));
4125 }
4126 }
4127 } else {
4128 clear_linked_edit_ranges = true;
4129 }
4130 }
4131
4132 new_selections.push((selection.map(|_| anchor), 0));
4133 edits.push((selection.start..selection.end, text.clone()));
4134 }
4135
4136 drop(snapshot);
4137
4138 self.transact(window, cx, |this, window, cx| {
4139 if clear_linked_edit_ranges {
4140 this.linked_edit_ranges.clear();
4141 }
4142 let initial_buffer_versions =
4143 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4144
4145 this.buffer.update(cx, |buffer, cx| {
4146 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4147 });
4148 for (buffer, edits) in linked_edits {
4149 buffer.update(cx, |buffer, cx| {
4150 let snapshot = buffer.snapshot();
4151 let edits = edits
4152 .into_iter()
4153 .map(|(range, text)| {
4154 use text::ToPoint as TP;
4155 let end_point = TP::to_point(&range.end, &snapshot);
4156 let start_point = TP::to_point(&range.start, &snapshot);
4157 (start_point..end_point, text)
4158 })
4159 .sorted_by_key(|(range, _)| range.start);
4160 buffer.edit(edits, None, cx);
4161 })
4162 }
4163 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4164 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4165 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4166 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4167 .zip(new_selection_deltas)
4168 .map(|(selection, delta)| Selection {
4169 id: selection.id,
4170 start: selection.start + delta,
4171 end: selection.end + delta,
4172 reversed: selection.reversed,
4173 goal: SelectionGoal::None,
4174 })
4175 .collect::<Vec<_>>();
4176
4177 let mut i = 0;
4178 for (position, delta, selection_id, pair) in new_autoclose_regions {
4179 let position = position.to_offset(&map.buffer_snapshot) + delta;
4180 let start = map.buffer_snapshot.anchor_before(position);
4181 let end = map.buffer_snapshot.anchor_after(position);
4182 while let Some(existing_state) = this.autoclose_regions.get(i) {
4183 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4184 Ordering::Less => i += 1,
4185 Ordering::Greater => break,
4186 Ordering::Equal => {
4187 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4188 Ordering::Less => i += 1,
4189 Ordering::Equal => break,
4190 Ordering::Greater => break,
4191 }
4192 }
4193 }
4194 }
4195 this.autoclose_regions.insert(
4196 i,
4197 AutocloseRegion {
4198 selection_id,
4199 range: start..end,
4200 pair,
4201 },
4202 );
4203 }
4204
4205 let had_active_inline_completion = this.has_active_inline_completion();
4206 this.change_selections(
4207 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4208 window,
4209 cx,
4210 |s| s.select(new_selections),
4211 );
4212
4213 if !bracket_inserted {
4214 if let Some(on_type_format_task) =
4215 this.trigger_on_type_formatting(text.to_string(), window, cx)
4216 {
4217 on_type_format_task.detach_and_log_err(cx);
4218 }
4219 }
4220
4221 let editor_settings = EditorSettings::get_global(cx);
4222 if bracket_inserted
4223 && (editor_settings.auto_signature_help
4224 || editor_settings.show_signature_help_after_edits)
4225 {
4226 this.show_signature_help(&ShowSignatureHelp, window, cx);
4227 }
4228
4229 let trigger_in_words =
4230 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4231 if this.hard_wrap.is_some() {
4232 let latest: Range<Point> = this.selections.newest(cx).range();
4233 if latest.is_empty()
4234 && this
4235 .buffer()
4236 .read(cx)
4237 .snapshot(cx)
4238 .line_len(MultiBufferRow(latest.start.row))
4239 == latest.start.column
4240 {
4241 this.rewrap_impl(
4242 RewrapOptions {
4243 override_language_settings: true,
4244 preserve_existing_whitespace: true,
4245 },
4246 cx,
4247 )
4248 }
4249 }
4250 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4251 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4252 this.refresh_inline_completion(true, false, window, cx);
4253 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4254 });
4255 }
4256
4257 fn find_possible_emoji_shortcode_at_position(
4258 snapshot: &MultiBufferSnapshot,
4259 position: Point,
4260 ) -> Option<String> {
4261 let mut chars = Vec::new();
4262 let mut found_colon = false;
4263 for char in snapshot.reversed_chars_at(position).take(100) {
4264 // Found a possible emoji shortcode in the middle of the buffer
4265 if found_colon {
4266 if char.is_whitespace() {
4267 chars.reverse();
4268 return Some(chars.iter().collect());
4269 }
4270 // If the previous character is not a whitespace, we are in the middle of a word
4271 // and we only want to complete the shortcode if the word is made up of other emojis
4272 let mut containing_word = String::new();
4273 for ch in snapshot
4274 .reversed_chars_at(position)
4275 .skip(chars.len() + 1)
4276 .take(100)
4277 {
4278 if ch.is_whitespace() {
4279 break;
4280 }
4281 containing_word.push(ch);
4282 }
4283 let containing_word = containing_word.chars().rev().collect::<String>();
4284 if util::word_consists_of_emojis(containing_word.as_str()) {
4285 chars.reverse();
4286 return Some(chars.iter().collect());
4287 }
4288 }
4289
4290 if char.is_whitespace() || !char.is_ascii() {
4291 return None;
4292 }
4293 if char == ':' {
4294 found_colon = true;
4295 } else {
4296 chars.push(char);
4297 }
4298 }
4299 // Found a possible emoji shortcode at the beginning of the buffer
4300 chars.reverse();
4301 Some(chars.iter().collect())
4302 }
4303
4304 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4306 self.transact(window, cx, |this, window, cx| {
4307 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4308 let selections = this.selections.all::<usize>(cx);
4309 let multi_buffer = this.buffer.read(cx);
4310 let buffer = multi_buffer.snapshot(cx);
4311 selections
4312 .iter()
4313 .map(|selection| {
4314 let start_point = selection.start.to_point(&buffer);
4315 let mut existing_indent =
4316 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4317 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4318 let start = selection.start;
4319 let end = selection.end;
4320 let selection_is_empty = start == end;
4321 let language_scope = buffer.language_scope_at(start);
4322 let (
4323 comment_delimiter,
4324 doc_delimiter,
4325 insert_extra_newline,
4326 indent_on_newline,
4327 indent_on_extra_newline,
4328 ) = if let Some(language) = &language_scope {
4329 let mut insert_extra_newline =
4330 insert_extra_newline_brackets(&buffer, start..end, language)
4331 || insert_extra_newline_tree_sitter(&buffer, start..end);
4332
4333 // Comment extension on newline is allowed only for cursor selections
4334 let comment_delimiter = maybe!({
4335 if !selection_is_empty {
4336 return None;
4337 }
4338
4339 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4340 return None;
4341 }
4342
4343 let delimiters = language.line_comment_prefixes();
4344 let max_len_of_delimiter =
4345 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4346 let (snapshot, range) =
4347 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4348
4349 let num_of_whitespaces = snapshot
4350 .chars_for_range(range.clone())
4351 .take_while(|c| c.is_whitespace())
4352 .count();
4353 let comment_candidate = snapshot
4354 .chars_for_range(range)
4355 .skip(num_of_whitespaces)
4356 .take(max_len_of_delimiter)
4357 .collect::<String>();
4358 let (delimiter, trimmed_len) = delimiters
4359 .iter()
4360 .filter_map(|delimiter| {
4361 let prefix = delimiter.trim_end();
4362 if comment_candidate.starts_with(prefix) {
4363 Some((delimiter, prefix.len()))
4364 } else {
4365 None
4366 }
4367 })
4368 .max_by_key(|(_, len)| *len)?;
4369
4370 let cursor_is_placed_after_comment_marker =
4371 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4372 if cursor_is_placed_after_comment_marker {
4373 Some(delimiter.clone())
4374 } else {
4375 None
4376 }
4377 });
4378
4379 let mut indent_on_newline = IndentSize::spaces(0);
4380 let mut indent_on_extra_newline = IndentSize::spaces(0);
4381
4382 let doc_delimiter = maybe!({
4383 if !selection_is_empty {
4384 return None;
4385 }
4386
4387 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4388 return None;
4389 }
4390
4391 let DocumentationConfig {
4392 start: start_tag,
4393 end: end_tag,
4394 prefix: delimiter,
4395 tab_size: len,
4396 } = language.documentation()?;
4397
4398 let is_within_block_comment = buffer
4399 .language_scope_at(start_point)
4400 .is_some_and(|scope| scope.override_name() == Some("comment"));
4401 if !is_within_block_comment {
4402 return None;
4403 }
4404
4405 let (snapshot, range) =
4406 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4407
4408 let num_of_whitespaces = snapshot
4409 .chars_for_range(range.clone())
4410 .take_while(|c| c.is_whitespace())
4411 .count();
4412
4413 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4414 let column = start_point.column;
4415 let cursor_is_after_start_tag = {
4416 let start_tag_len = start_tag.len();
4417 let start_tag_line = snapshot
4418 .chars_for_range(range.clone())
4419 .skip(num_of_whitespaces)
4420 .take(start_tag_len)
4421 .collect::<String>();
4422 if start_tag_line.starts_with(start_tag.as_ref()) {
4423 num_of_whitespaces + start_tag_len <= column as usize
4424 } else {
4425 false
4426 }
4427 };
4428
4429 let cursor_is_after_delimiter = {
4430 let delimiter_trim = delimiter.trim_end();
4431 let delimiter_line = snapshot
4432 .chars_for_range(range.clone())
4433 .skip(num_of_whitespaces)
4434 .take(delimiter_trim.len())
4435 .collect::<String>();
4436 if delimiter_line.starts_with(delimiter_trim) {
4437 num_of_whitespaces + delimiter_trim.len() <= column as usize
4438 } else {
4439 false
4440 }
4441 };
4442
4443 let cursor_is_before_end_tag_if_exists = {
4444 let mut char_position = 0u32;
4445 let mut end_tag_offset = None;
4446
4447 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4448 if let Some(byte_pos) = chunk.find(&**end_tag) {
4449 let chars_before_match =
4450 chunk[..byte_pos].chars().count() as u32;
4451 end_tag_offset =
4452 Some(char_position + chars_before_match);
4453 break 'outer;
4454 }
4455 char_position += chunk.chars().count() as u32;
4456 }
4457
4458 if let Some(end_tag_offset) = end_tag_offset {
4459 let cursor_is_before_end_tag = column <= end_tag_offset;
4460 if cursor_is_after_start_tag {
4461 if cursor_is_before_end_tag {
4462 insert_extra_newline = true;
4463 }
4464 let cursor_is_at_start_of_end_tag =
4465 column == end_tag_offset;
4466 if cursor_is_at_start_of_end_tag {
4467 indent_on_extra_newline.len = (*len).into();
4468 }
4469 }
4470 cursor_is_before_end_tag
4471 } else {
4472 true
4473 }
4474 };
4475
4476 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4477 && cursor_is_before_end_tag_if_exists
4478 {
4479 if cursor_is_after_start_tag {
4480 indent_on_newline.len = (*len).into();
4481 }
4482 Some(delimiter.clone())
4483 } else {
4484 None
4485 }
4486 });
4487
4488 (
4489 comment_delimiter,
4490 doc_delimiter,
4491 insert_extra_newline,
4492 indent_on_newline,
4493 indent_on_extra_newline,
4494 )
4495 } else {
4496 (
4497 None,
4498 None,
4499 false,
4500 IndentSize::default(),
4501 IndentSize::default(),
4502 )
4503 };
4504
4505 let prevent_auto_indent = doc_delimiter.is_some();
4506 let delimiter = comment_delimiter.or(doc_delimiter);
4507
4508 let capacity_for_delimiter =
4509 delimiter.as_deref().map(str::len).unwrap_or_default();
4510 let mut new_text = String::with_capacity(
4511 1 + capacity_for_delimiter
4512 + existing_indent.len as usize
4513 + indent_on_newline.len as usize
4514 + indent_on_extra_newline.len as usize,
4515 );
4516 new_text.push('\n');
4517 new_text.extend(existing_indent.chars());
4518 new_text.extend(indent_on_newline.chars());
4519
4520 if let Some(delimiter) = &delimiter {
4521 new_text.push_str(delimiter);
4522 }
4523
4524 if insert_extra_newline {
4525 new_text.push('\n');
4526 new_text.extend(existing_indent.chars());
4527 new_text.extend(indent_on_extra_newline.chars());
4528 }
4529
4530 let anchor = buffer.anchor_after(end);
4531 let new_selection = selection.map(|_| anchor);
4532 (
4533 ((start..end, new_text), prevent_auto_indent),
4534 (insert_extra_newline, new_selection),
4535 )
4536 })
4537 .unzip()
4538 };
4539
4540 let mut auto_indent_edits = Vec::new();
4541 let mut edits = Vec::new();
4542 for (edit, prevent_auto_indent) in edits_with_flags {
4543 if prevent_auto_indent {
4544 edits.push(edit);
4545 } else {
4546 auto_indent_edits.push(edit);
4547 }
4548 }
4549 if !edits.is_empty() {
4550 this.edit(edits, cx);
4551 }
4552 if !auto_indent_edits.is_empty() {
4553 this.edit_with_autoindent(auto_indent_edits, cx);
4554 }
4555
4556 let buffer = this.buffer.read(cx).snapshot(cx);
4557 let new_selections = selection_info
4558 .into_iter()
4559 .map(|(extra_newline_inserted, new_selection)| {
4560 let mut cursor = new_selection.end.to_point(&buffer);
4561 if extra_newline_inserted {
4562 cursor.row -= 1;
4563 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4564 }
4565 new_selection.map(|_| cursor)
4566 })
4567 .collect();
4568
4569 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4570 this.refresh_inline_completion(true, false, window, cx);
4571 });
4572 }
4573
4574 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4576
4577 let buffer = self.buffer.read(cx);
4578 let snapshot = buffer.snapshot(cx);
4579
4580 let mut edits = Vec::new();
4581 let mut rows = Vec::new();
4582
4583 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4584 let cursor = selection.head();
4585 let row = cursor.row;
4586
4587 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4588
4589 let newline = "\n".to_string();
4590 edits.push((start_of_line..start_of_line, newline));
4591
4592 rows.push(row + rows_inserted as u32);
4593 }
4594
4595 self.transact(window, cx, |editor, window, cx| {
4596 editor.edit(edits, cx);
4597
4598 editor.change_selections(Default::default(), window, cx, |s| {
4599 let mut index = 0;
4600 s.move_cursors_with(|map, _, _| {
4601 let row = rows[index];
4602 index += 1;
4603
4604 let point = Point::new(row, 0);
4605 let boundary = map.next_line_boundary(point).1;
4606 let clipped = map.clip_point(boundary, Bias::Left);
4607
4608 (clipped, SelectionGoal::None)
4609 });
4610 });
4611
4612 let mut indent_edits = Vec::new();
4613 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4614 for row in rows {
4615 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4616 for (row, indent) in indents {
4617 if indent.len == 0 {
4618 continue;
4619 }
4620
4621 let text = match indent.kind {
4622 IndentKind::Space => " ".repeat(indent.len as usize),
4623 IndentKind::Tab => "\t".repeat(indent.len as usize),
4624 };
4625 let point = Point::new(row.0, 0);
4626 indent_edits.push((point..point, text));
4627 }
4628 }
4629 editor.edit(indent_edits, cx);
4630 });
4631 }
4632
4633 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4635
4636 let buffer = self.buffer.read(cx);
4637 let snapshot = buffer.snapshot(cx);
4638
4639 let mut edits = Vec::new();
4640 let mut rows = Vec::new();
4641 let mut rows_inserted = 0;
4642
4643 for selection in self.selections.all_adjusted(cx) {
4644 let cursor = selection.head();
4645 let row = cursor.row;
4646
4647 let point = Point::new(row + 1, 0);
4648 let start_of_line = snapshot.clip_point(point, Bias::Left);
4649
4650 let newline = "\n".to_string();
4651 edits.push((start_of_line..start_of_line, newline));
4652
4653 rows_inserted += 1;
4654 rows.push(row + rows_inserted);
4655 }
4656
4657 self.transact(window, cx, |editor, window, cx| {
4658 editor.edit(edits, cx);
4659
4660 editor.change_selections(Default::default(), window, cx, |s| {
4661 let mut index = 0;
4662 s.move_cursors_with(|map, _, _| {
4663 let row = rows[index];
4664 index += 1;
4665
4666 let point = Point::new(row, 0);
4667 let boundary = map.next_line_boundary(point).1;
4668 let clipped = map.clip_point(boundary, Bias::Left);
4669
4670 (clipped, SelectionGoal::None)
4671 });
4672 });
4673
4674 let mut indent_edits = Vec::new();
4675 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4676 for row in rows {
4677 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4678 for (row, indent) in indents {
4679 if indent.len == 0 {
4680 continue;
4681 }
4682
4683 let text = match indent.kind {
4684 IndentKind::Space => " ".repeat(indent.len as usize),
4685 IndentKind::Tab => "\t".repeat(indent.len as usize),
4686 };
4687 let point = Point::new(row.0, 0);
4688 indent_edits.push((point..point, text));
4689 }
4690 }
4691 editor.edit(indent_edits, cx);
4692 });
4693 }
4694
4695 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4696 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4697 original_indent_columns: Vec::new(),
4698 });
4699 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4700 }
4701
4702 fn insert_with_autoindent_mode(
4703 &mut self,
4704 text: &str,
4705 autoindent_mode: Option<AutoindentMode>,
4706 window: &mut Window,
4707 cx: &mut Context<Self>,
4708 ) {
4709 if self.read_only(cx) {
4710 return;
4711 }
4712
4713 let text: Arc<str> = text.into();
4714 self.transact(window, cx, |this, window, cx| {
4715 let old_selections = this.selections.all_adjusted(cx);
4716 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4717 let anchors = {
4718 let snapshot = buffer.read(cx);
4719 old_selections
4720 .iter()
4721 .map(|s| {
4722 let anchor = snapshot.anchor_after(s.head());
4723 s.map(|_| anchor)
4724 })
4725 .collect::<Vec<_>>()
4726 };
4727 buffer.edit(
4728 old_selections
4729 .iter()
4730 .map(|s| (s.start..s.end, text.clone())),
4731 autoindent_mode,
4732 cx,
4733 );
4734 anchors
4735 });
4736
4737 this.change_selections(Default::default(), window, cx, |s| {
4738 s.select_anchors(selection_anchors);
4739 });
4740
4741 cx.notify();
4742 });
4743 }
4744
4745 fn trigger_completion_on_input(
4746 &mut self,
4747 text: &str,
4748 trigger_in_words: bool,
4749 window: &mut Window,
4750 cx: &mut Context<Self>,
4751 ) {
4752 let completions_source = self
4753 .context_menu
4754 .borrow()
4755 .as_ref()
4756 .and_then(|menu| match menu {
4757 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4758 CodeContextMenu::CodeActions(_) => None,
4759 });
4760
4761 match completions_source {
4762 Some(CompletionsMenuSource::Words) => {
4763 self.show_word_completions(&ShowWordCompletions, window, cx)
4764 }
4765 Some(CompletionsMenuSource::Normal)
4766 | Some(CompletionsMenuSource::SnippetChoices)
4767 | None
4768 if self.is_completion_trigger(
4769 text,
4770 trigger_in_words,
4771 completions_source.is_some(),
4772 cx,
4773 ) =>
4774 {
4775 self.show_completions(
4776 &ShowCompletions {
4777 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4778 },
4779 window,
4780 cx,
4781 )
4782 }
4783 _ => {
4784 self.hide_context_menu(window, cx);
4785 }
4786 }
4787 }
4788
4789 fn is_completion_trigger(
4790 &self,
4791 text: &str,
4792 trigger_in_words: bool,
4793 menu_is_open: bool,
4794 cx: &mut Context<Self>,
4795 ) -> bool {
4796 let position = self.selections.newest_anchor().head();
4797 let multibuffer = self.buffer.read(cx);
4798 let Some(buffer) = position
4799 .buffer_id
4800 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4801 else {
4802 return false;
4803 };
4804
4805 if let Some(completion_provider) = &self.completion_provider {
4806 completion_provider.is_completion_trigger(
4807 &buffer,
4808 position.text_anchor,
4809 text,
4810 trigger_in_words,
4811 menu_is_open,
4812 cx,
4813 )
4814 } else {
4815 false
4816 }
4817 }
4818
4819 /// If any empty selections is touching the start of its innermost containing autoclose
4820 /// region, expand it to select the brackets.
4821 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4822 let selections = self.selections.all::<usize>(cx);
4823 let buffer = self.buffer.read(cx).read(cx);
4824 let new_selections = self
4825 .selections_with_autoclose_regions(selections, &buffer)
4826 .map(|(mut selection, region)| {
4827 if !selection.is_empty() {
4828 return selection;
4829 }
4830
4831 if let Some(region) = region {
4832 let mut range = region.range.to_offset(&buffer);
4833 if selection.start == range.start && range.start >= region.pair.start.len() {
4834 range.start -= region.pair.start.len();
4835 if buffer.contains_str_at(range.start, ®ion.pair.start)
4836 && buffer.contains_str_at(range.end, ®ion.pair.end)
4837 {
4838 range.end += region.pair.end.len();
4839 selection.start = range.start;
4840 selection.end = range.end;
4841
4842 return selection;
4843 }
4844 }
4845 }
4846
4847 let always_treat_brackets_as_autoclosed = buffer
4848 .language_settings_at(selection.start, cx)
4849 .always_treat_brackets_as_autoclosed;
4850
4851 if !always_treat_brackets_as_autoclosed {
4852 return selection;
4853 }
4854
4855 if let Some(scope) = buffer.language_scope_at(selection.start) {
4856 for (pair, enabled) in scope.brackets() {
4857 if !enabled || !pair.close {
4858 continue;
4859 }
4860
4861 if buffer.contains_str_at(selection.start, &pair.end) {
4862 let pair_start_len = pair.start.len();
4863 if buffer.contains_str_at(
4864 selection.start.saturating_sub(pair_start_len),
4865 &pair.start,
4866 ) {
4867 selection.start -= pair_start_len;
4868 selection.end += pair.end.len();
4869
4870 return selection;
4871 }
4872 }
4873 }
4874 }
4875
4876 selection
4877 })
4878 .collect();
4879
4880 drop(buffer);
4881 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
4882 selections.select(new_selections)
4883 });
4884 }
4885
4886 /// Iterate the given selections, and for each one, find the smallest surrounding
4887 /// autoclose region. This uses the ordering of the selections and the autoclose
4888 /// regions to avoid repeated comparisons.
4889 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4890 &'a self,
4891 selections: impl IntoIterator<Item = Selection<D>>,
4892 buffer: &'a MultiBufferSnapshot,
4893 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4894 let mut i = 0;
4895 let mut regions = self.autoclose_regions.as_slice();
4896 selections.into_iter().map(move |selection| {
4897 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4898
4899 let mut enclosing = None;
4900 while let Some(pair_state) = regions.get(i) {
4901 if pair_state.range.end.to_offset(buffer) < range.start {
4902 regions = ®ions[i + 1..];
4903 i = 0;
4904 } else if pair_state.range.start.to_offset(buffer) > range.end {
4905 break;
4906 } else {
4907 if pair_state.selection_id == selection.id {
4908 enclosing = Some(pair_state);
4909 }
4910 i += 1;
4911 }
4912 }
4913
4914 (selection, enclosing)
4915 })
4916 }
4917
4918 /// Remove any autoclose regions that no longer contain their selection.
4919 fn invalidate_autoclose_regions(
4920 &mut self,
4921 mut selections: &[Selection<Anchor>],
4922 buffer: &MultiBufferSnapshot,
4923 ) {
4924 self.autoclose_regions.retain(|state| {
4925 let mut i = 0;
4926 while let Some(selection) = selections.get(i) {
4927 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4928 selections = &selections[1..];
4929 continue;
4930 }
4931 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4932 break;
4933 }
4934 if selection.id == state.selection_id {
4935 return true;
4936 } else {
4937 i += 1;
4938 }
4939 }
4940 false
4941 });
4942 }
4943
4944 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4945 let offset = position.to_offset(buffer);
4946 let (word_range, kind) = buffer.surrounding_word(offset, true);
4947 if offset > word_range.start && kind == Some(CharKind::Word) {
4948 Some(
4949 buffer
4950 .text_for_range(word_range.start..offset)
4951 .collect::<String>(),
4952 )
4953 } else {
4954 None
4955 }
4956 }
4957
4958 pub fn toggle_inline_values(
4959 &mut self,
4960 _: &ToggleInlineValues,
4961 _: &mut Window,
4962 cx: &mut Context<Self>,
4963 ) {
4964 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4965
4966 self.refresh_inline_values(cx);
4967 }
4968
4969 pub fn toggle_inlay_hints(
4970 &mut self,
4971 _: &ToggleInlayHints,
4972 _: &mut Window,
4973 cx: &mut Context<Self>,
4974 ) {
4975 self.refresh_inlay_hints(
4976 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4977 cx,
4978 );
4979 }
4980
4981 pub fn inlay_hints_enabled(&self) -> bool {
4982 self.inlay_hint_cache.enabled
4983 }
4984
4985 pub fn inline_values_enabled(&self) -> bool {
4986 self.inline_value_cache.enabled
4987 }
4988
4989 #[cfg(any(test, feature = "test-support"))]
4990 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4991 self.display_map
4992 .read(cx)
4993 .current_inlays()
4994 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4995 .cloned()
4996 .collect()
4997 }
4998
4999 #[cfg(any(test, feature = "test-support"))]
5000 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5001 self.display_map
5002 .read(cx)
5003 .current_inlays()
5004 .cloned()
5005 .collect()
5006 }
5007
5008 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5009 if self.semantics_provider.is_none() || !self.mode.is_full() {
5010 return;
5011 }
5012
5013 let reason_description = reason.description();
5014 let ignore_debounce = matches!(
5015 reason,
5016 InlayHintRefreshReason::SettingsChange(_)
5017 | InlayHintRefreshReason::Toggle(_)
5018 | InlayHintRefreshReason::ExcerptsRemoved(_)
5019 | InlayHintRefreshReason::ModifiersChanged(_)
5020 );
5021 let (invalidate_cache, required_languages) = match reason {
5022 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5023 match self.inlay_hint_cache.modifiers_override(enabled) {
5024 Some(enabled) => {
5025 if enabled {
5026 (InvalidationStrategy::RefreshRequested, None)
5027 } else {
5028 self.splice_inlays(
5029 &self
5030 .visible_inlay_hints(cx)
5031 .iter()
5032 .map(|inlay| inlay.id)
5033 .collect::<Vec<InlayId>>(),
5034 Vec::new(),
5035 cx,
5036 );
5037 return;
5038 }
5039 }
5040 None => return,
5041 }
5042 }
5043 InlayHintRefreshReason::Toggle(enabled) => {
5044 if self.inlay_hint_cache.toggle(enabled) {
5045 if enabled {
5046 (InvalidationStrategy::RefreshRequested, None)
5047 } else {
5048 self.splice_inlays(
5049 &self
5050 .visible_inlay_hints(cx)
5051 .iter()
5052 .map(|inlay| inlay.id)
5053 .collect::<Vec<InlayId>>(),
5054 Vec::new(),
5055 cx,
5056 );
5057 return;
5058 }
5059 } else {
5060 return;
5061 }
5062 }
5063 InlayHintRefreshReason::SettingsChange(new_settings) => {
5064 match self.inlay_hint_cache.update_settings(
5065 &self.buffer,
5066 new_settings,
5067 self.visible_inlay_hints(cx),
5068 cx,
5069 ) {
5070 ControlFlow::Break(Some(InlaySplice {
5071 to_remove,
5072 to_insert,
5073 })) => {
5074 self.splice_inlays(&to_remove, to_insert, cx);
5075 return;
5076 }
5077 ControlFlow::Break(None) => return,
5078 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5079 }
5080 }
5081 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5082 if let Some(InlaySplice {
5083 to_remove,
5084 to_insert,
5085 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5086 {
5087 self.splice_inlays(&to_remove, to_insert, cx);
5088 }
5089 self.display_map.update(cx, |display_map, _| {
5090 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5091 });
5092 return;
5093 }
5094 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5095 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5096 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5097 }
5098 InlayHintRefreshReason::RefreshRequested => {
5099 (InvalidationStrategy::RefreshRequested, None)
5100 }
5101 };
5102
5103 if let Some(InlaySplice {
5104 to_remove,
5105 to_insert,
5106 }) = self.inlay_hint_cache.spawn_hint_refresh(
5107 reason_description,
5108 self.visible_excerpts(required_languages.as_ref(), cx),
5109 invalidate_cache,
5110 ignore_debounce,
5111 cx,
5112 ) {
5113 self.splice_inlays(&to_remove, to_insert, cx);
5114 }
5115 }
5116
5117 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5118 self.display_map
5119 .read(cx)
5120 .current_inlays()
5121 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5122 .cloned()
5123 .collect()
5124 }
5125
5126 pub fn visible_excerpts(
5127 &self,
5128 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5129 cx: &mut Context<Editor>,
5130 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5131 let Some(project) = self.project.as_ref() else {
5132 return HashMap::default();
5133 };
5134 let project = project.read(cx);
5135 let multi_buffer = self.buffer().read(cx);
5136 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5137 let multi_buffer_visible_start = self
5138 .scroll_manager
5139 .anchor()
5140 .anchor
5141 .to_point(&multi_buffer_snapshot);
5142 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5143 multi_buffer_visible_start
5144 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5145 Bias::Left,
5146 );
5147 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5148 multi_buffer_snapshot
5149 .range_to_buffer_ranges(multi_buffer_visible_range)
5150 .into_iter()
5151 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5152 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5153 let buffer_file = project::File::from_dyn(buffer.file())?;
5154 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5155 let worktree_entry = buffer_worktree
5156 .read(cx)
5157 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5158 if worktree_entry.is_ignored {
5159 return None;
5160 }
5161
5162 let language = buffer.language()?;
5163 if let Some(restrict_to_languages) = restrict_to_languages {
5164 if !restrict_to_languages.contains(language) {
5165 return None;
5166 }
5167 }
5168 Some((
5169 excerpt_id,
5170 (
5171 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5172 buffer.version().clone(),
5173 excerpt_visible_range,
5174 ),
5175 ))
5176 })
5177 .collect()
5178 }
5179
5180 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5181 TextLayoutDetails {
5182 text_system: window.text_system().clone(),
5183 editor_style: self.style.clone().unwrap(),
5184 rem_size: window.rem_size(),
5185 scroll_anchor: self.scroll_manager.anchor(),
5186 visible_rows: self.visible_line_count(),
5187 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5188 }
5189 }
5190
5191 pub fn splice_inlays(
5192 &self,
5193 to_remove: &[InlayId],
5194 to_insert: Vec<Inlay>,
5195 cx: &mut Context<Self>,
5196 ) {
5197 self.display_map.update(cx, |display_map, cx| {
5198 display_map.splice_inlays(to_remove, to_insert, cx)
5199 });
5200 cx.notify();
5201 }
5202
5203 fn trigger_on_type_formatting(
5204 &self,
5205 input: String,
5206 window: &mut Window,
5207 cx: &mut Context<Self>,
5208 ) -> Option<Task<Result<()>>> {
5209 if input.len() != 1 {
5210 return None;
5211 }
5212
5213 let project = self.project.as_ref()?;
5214 let position = self.selections.newest_anchor().head();
5215 let (buffer, buffer_position) = self
5216 .buffer
5217 .read(cx)
5218 .text_anchor_for_position(position, cx)?;
5219
5220 let settings = language_settings::language_settings(
5221 buffer
5222 .read(cx)
5223 .language_at(buffer_position)
5224 .map(|l| l.name()),
5225 buffer.read(cx).file(),
5226 cx,
5227 );
5228 if !settings.use_on_type_format {
5229 return None;
5230 }
5231
5232 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5233 // hence we do LSP request & edit on host side only — add formats to host's history.
5234 let push_to_lsp_host_history = true;
5235 // If this is not the host, append its history with new edits.
5236 let push_to_client_history = project.read(cx).is_via_collab();
5237
5238 let on_type_formatting = project.update(cx, |project, cx| {
5239 project.on_type_format(
5240 buffer.clone(),
5241 buffer_position,
5242 input,
5243 push_to_lsp_host_history,
5244 cx,
5245 )
5246 });
5247 Some(cx.spawn_in(window, async move |editor, cx| {
5248 if let Some(transaction) = on_type_formatting.await? {
5249 if push_to_client_history {
5250 buffer
5251 .update(cx, |buffer, _| {
5252 buffer.push_transaction(transaction, Instant::now());
5253 buffer.finalize_last_transaction();
5254 })
5255 .ok();
5256 }
5257 editor.update(cx, |editor, cx| {
5258 editor.refresh_document_highlights(cx);
5259 })?;
5260 }
5261 Ok(())
5262 }))
5263 }
5264
5265 pub fn show_word_completions(
5266 &mut self,
5267 _: &ShowWordCompletions,
5268 window: &mut Window,
5269 cx: &mut Context<Self>,
5270 ) {
5271 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5272 }
5273
5274 pub fn show_completions(
5275 &mut self,
5276 options: &ShowCompletions,
5277 window: &mut Window,
5278 cx: &mut Context<Self>,
5279 ) {
5280 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5281 }
5282
5283 fn open_or_update_completions_menu(
5284 &mut self,
5285 requested_source: Option<CompletionsMenuSource>,
5286 trigger: Option<&str>,
5287 window: &mut Window,
5288 cx: &mut Context<Self>,
5289 ) {
5290 if self.pending_rename.is_some() {
5291 return;
5292 }
5293
5294 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5295
5296 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5297 // inserted and selected. To handle that case, the start of the selection is used so that
5298 // the menu starts with all choices.
5299 let position = self
5300 .selections
5301 .newest_anchor()
5302 .start
5303 .bias_right(&multibuffer_snapshot);
5304 if position.diff_base_anchor.is_some() {
5305 return;
5306 }
5307 let (buffer, buffer_position) =
5308 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5309 output
5310 } else {
5311 return;
5312 };
5313 let buffer_snapshot = buffer.read(cx).snapshot();
5314
5315 let query: Option<Arc<String>> =
5316 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5317
5318 drop(multibuffer_snapshot);
5319
5320 let provider = match requested_source {
5321 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5322 Some(CompletionsMenuSource::Words) => None,
5323 Some(CompletionsMenuSource::SnippetChoices) => {
5324 log::error!("bug: SnippetChoices requested_source is not handled");
5325 None
5326 }
5327 };
5328
5329 let sort_completions = provider
5330 .as_ref()
5331 .map_or(false, |provider| provider.sort_completions());
5332
5333 let filter_completions = provider
5334 .as_ref()
5335 .map_or(true, |provider| provider.filter_completions());
5336
5337 let trigger_kind = match trigger {
5338 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5339 CompletionTriggerKind::TRIGGER_CHARACTER
5340 }
5341 _ => CompletionTriggerKind::INVOKED,
5342 };
5343 let completion_context = CompletionContext {
5344 trigger_character: trigger.and_then(|trigger| {
5345 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5346 Some(String::from(trigger))
5347 } else {
5348 None
5349 }
5350 }),
5351 trigger_kind,
5352 };
5353
5354 // Hide the current completions menu when a trigger char is typed. Without this, cached
5355 // completions from before the trigger char may be reused (#32774). Snippet choices could
5356 // involve trigger chars, so this is skipped in that case.
5357 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5358 {
5359 let menu_is_open = matches!(
5360 self.context_menu.borrow().as_ref(),
5361 Some(CodeContextMenu::Completions(_))
5362 );
5363 if menu_is_open {
5364 self.hide_context_menu(window, cx);
5365 }
5366 }
5367
5368 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5369 if filter_completions {
5370 menu.filter(query.clone(), provider.clone(), window, cx);
5371 }
5372 // When `is_incomplete` is false, no need to re-query completions when the current query
5373 // is a suffix of the initial query.
5374 if !menu.is_incomplete {
5375 // If the new query is a suffix of the old query (typing more characters) and
5376 // the previous result was complete, the existing completions can be filtered.
5377 //
5378 // Note that this is always true for snippet completions.
5379 let query_matches = match (&menu.initial_query, &query) {
5380 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5381 (None, _) => true,
5382 _ => false,
5383 };
5384 if query_matches {
5385 let position_matches = if menu.initial_position == position {
5386 true
5387 } else {
5388 let snapshot = self.buffer.read(cx).read(cx);
5389 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5390 };
5391 if position_matches {
5392 return;
5393 }
5394 }
5395 }
5396 };
5397
5398 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5399 buffer_snapshot.surrounding_word(buffer_position)
5400 {
5401 let word_to_exclude = buffer_snapshot
5402 .text_for_range(word_range.clone())
5403 .collect::<String>();
5404 (
5405 buffer_snapshot.anchor_before(word_range.start)
5406 ..buffer_snapshot.anchor_after(buffer_position),
5407 Some(word_to_exclude),
5408 )
5409 } else {
5410 (buffer_position..buffer_position, None)
5411 };
5412
5413 let language = buffer_snapshot
5414 .language_at(buffer_position)
5415 .map(|language| language.name());
5416
5417 let completion_settings =
5418 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5419
5420 let show_completion_documentation = buffer_snapshot
5421 .settings_at(buffer_position, cx)
5422 .show_completion_documentation;
5423
5424 // The document can be large, so stay in reasonable bounds when searching for words,
5425 // otherwise completion pop-up might be slow to appear.
5426 const WORD_LOOKUP_ROWS: u32 = 5_000;
5427 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5428 let min_word_search = buffer_snapshot.clip_point(
5429 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5430 Bias::Left,
5431 );
5432 let max_word_search = buffer_snapshot.clip_point(
5433 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5434 Bias::Right,
5435 );
5436 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5437 ..buffer_snapshot.point_to_offset(max_word_search);
5438
5439 let skip_digits = query
5440 .as_ref()
5441 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5442
5443 let (mut words, provider_responses) = match &provider {
5444 Some(provider) => {
5445 let provider_responses = provider.completions(
5446 position.excerpt_id,
5447 &buffer,
5448 buffer_position,
5449 completion_context,
5450 window,
5451 cx,
5452 );
5453
5454 let words = match completion_settings.words {
5455 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5456 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5457 .background_spawn(async move {
5458 buffer_snapshot.words_in_range(WordsQuery {
5459 fuzzy_contents: None,
5460 range: word_search_range,
5461 skip_digits,
5462 })
5463 }),
5464 };
5465
5466 (words, provider_responses)
5467 }
5468 None => (
5469 cx.background_spawn(async move {
5470 buffer_snapshot.words_in_range(WordsQuery {
5471 fuzzy_contents: None,
5472 range: word_search_range,
5473 skip_digits,
5474 })
5475 }),
5476 Task::ready(Ok(Vec::new())),
5477 ),
5478 };
5479
5480 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5481
5482 let id = post_inc(&mut self.next_completion_id);
5483 let task = cx.spawn_in(window, async move |editor, cx| {
5484 let Ok(()) = editor.update(cx, |this, _| {
5485 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5486 }) else {
5487 return;
5488 };
5489
5490 // TODO: Ideally completions from different sources would be selectively re-queried, so
5491 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5492 let mut completions = Vec::new();
5493 let mut is_incomplete = false;
5494 if let Some(provider_responses) = provider_responses.await.log_err() {
5495 if !provider_responses.is_empty() {
5496 for response in provider_responses {
5497 completions.extend(response.completions);
5498 is_incomplete = is_incomplete || response.is_incomplete;
5499 }
5500 if completion_settings.words == WordsCompletionMode::Fallback {
5501 words = Task::ready(BTreeMap::default());
5502 }
5503 }
5504 }
5505
5506 let mut words = words.await;
5507 if let Some(word_to_exclude) = &word_to_exclude {
5508 words.remove(word_to_exclude);
5509 }
5510 for lsp_completion in &completions {
5511 words.remove(&lsp_completion.new_text);
5512 }
5513 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5514 replace_range: word_replace_range.clone(),
5515 new_text: word.clone(),
5516 label: CodeLabel::plain(word, None),
5517 icon_path: None,
5518 documentation: None,
5519 source: CompletionSource::BufferWord {
5520 word_range,
5521 resolved: false,
5522 },
5523 insert_text_mode: Some(InsertTextMode::AS_IS),
5524 confirm: None,
5525 }));
5526
5527 let menu = if completions.is_empty() {
5528 None
5529 } else {
5530 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5531 let languages = editor
5532 .workspace
5533 .as_ref()
5534 .and_then(|(workspace, _)| workspace.upgrade())
5535 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5536 let menu = CompletionsMenu::new(
5537 id,
5538 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5539 sort_completions,
5540 show_completion_documentation,
5541 position,
5542 query.clone(),
5543 is_incomplete,
5544 buffer.clone(),
5545 completions.into(),
5546 snippet_sort_order,
5547 languages,
5548 language,
5549 cx,
5550 );
5551
5552 let query = if filter_completions { query } else { None };
5553 let matches_task = if let Some(query) = query {
5554 menu.do_async_filtering(query, cx)
5555 } else {
5556 Task::ready(menu.unfiltered_matches())
5557 };
5558 (menu, matches_task)
5559 }) else {
5560 return;
5561 };
5562
5563 let matches = matches_task.await;
5564
5565 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5566 // Newer menu already set, so exit.
5567 match editor.context_menu.borrow().as_ref() {
5568 Some(CodeContextMenu::Completions(prev_menu)) => {
5569 if prev_menu.id > id {
5570 return;
5571 }
5572 }
5573 _ => {}
5574 };
5575
5576 // Only valid to take prev_menu because it the new menu is immediately set
5577 // below, or the menu is hidden.
5578 match editor.context_menu.borrow_mut().take() {
5579 Some(CodeContextMenu::Completions(prev_menu)) => {
5580 let position_matches =
5581 if prev_menu.initial_position == menu.initial_position {
5582 true
5583 } else {
5584 let snapshot = editor.buffer.read(cx).read(cx);
5585 prev_menu.initial_position.to_offset(&snapshot)
5586 == menu.initial_position.to_offset(&snapshot)
5587 };
5588 if position_matches {
5589 // Preserve markdown cache before `set_filter_results` because it will
5590 // try to populate the documentation cache.
5591 menu.preserve_markdown_cache(prev_menu);
5592 }
5593 }
5594 _ => {}
5595 };
5596
5597 menu.set_filter_results(matches, provider, window, cx);
5598 }) else {
5599 return;
5600 };
5601
5602 menu.visible().then_some(menu)
5603 };
5604
5605 editor
5606 .update_in(cx, |editor, window, cx| {
5607 if editor.focus_handle.is_focused(window) {
5608 if let Some(menu) = menu {
5609 *editor.context_menu.borrow_mut() =
5610 Some(CodeContextMenu::Completions(menu));
5611
5612 crate::hover_popover::hide_hover(editor, cx);
5613 if editor.show_edit_predictions_in_menu() {
5614 editor.update_visible_inline_completion(window, cx);
5615 } else {
5616 editor.discard_inline_completion(false, cx);
5617 }
5618
5619 cx.notify();
5620 return;
5621 }
5622 }
5623
5624 if editor.completion_tasks.len() <= 1 {
5625 // If there are no more completion tasks and the last menu was empty, we should hide it.
5626 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5627 // If it was already hidden and we don't show inline completions in the menu, we should
5628 // also show the inline-completion when available.
5629 if was_hidden && editor.show_edit_predictions_in_menu() {
5630 editor.update_visible_inline_completion(window, cx);
5631 }
5632 }
5633 })
5634 .ok();
5635 });
5636
5637 self.completion_tasks.push((id, task));
5638 }
5639
5640 #[cfg(feature = "test-support")]
5641 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5642 let menu = self.context_menu.borrow();
5643 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5644 let completions = menu.completions.borrow();
5645 Some(completions.to_vec())
5646 } else {
5647 None
5648 }
5649 }
5650
5651 pub fn with_completions_menu_matching_id<R>(
5652 &self,
5653 id: CompletionId,
5654 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5655 ) -> R {
5656 let mut context_menu = self.context_menu.borrow_mut();
5657 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5658 return f(None);
5659 };
5660 if completions_menu.id != id {
5661 return f(None);
5662 }
5663 f(Some(completions_menu))
5664 }
5665
5666 pub fn confirm_completion(
5667 &mut self,
5668 action: &ConfirmCompletion,
5669 window: &mut Window,
5670 cx: &mut Context<Self>,
5671 ) -> Option<Task<Result<()>>> {
5672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5673 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5674 }
5675
5676 pub fn confirm_completion_insert(
5677 &mut self,
5678 _: &ConfirmCompletionInsert,
5679 window: &mut Window,
5680 cx: &mut Context<Self>,
5681 ) -> Option<Task<Result<()>>> {
5682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5683 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5684 }
5685
5686 pub fn confirm_completion_replace(
5687 &mut self,
5688 _: &ConfirmCompletionReplace,
5689 window: &mut Window,
5690 cx: &mut Context<Self>,
5691 ) -> Option<Task<Result<()>>> {
5692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5693 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5694 }
5695
5696 pub fn compose_completion(
5697 &mut self,
5698 action: &ComposeCompletion,
5699 window: &mut Window,
5700 cx: &mut Context<Self>,
5701 ) -> Option<Task<Result<()>>> {
5702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5703 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5704 }
5705
5706 fn do_completion(
5707 &mut self,
5708 item_ix: Option<usize>,
5709 intent: CompletionIntent,
5710 window: &mut Window,
5711 cx: &mut Context<Editor>,
5712 ) -> Option<Task<Result<()>>> {
5713 use language::ToOffset as _;
5714
5715 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5716 else {
5717 return None;
5718 };
5719
5720 let candidate_id = {
5721 let entries = completions_menu.entries.borrow();
5722 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5723 if self.show_edit_predictions_in_menu() {
5724 self.discard_inline_completion(true, cx);
5725 }
5726 mat.candidate_id
5727 };
5728
5729 let completion = completions_menu
5730 .completions
5731 .borrow()
5732 .get(candidate_id)?
5733 .clone();
5734 cx.stop_propagation();
5735
5736 let buffer_handle = completions_menu.buffer.clone();
5737
5738 let CompletionEdit {
5739 new_text,
5740 snippet,
5741 replace_range,
5742 } = process_completion_for_edit(
5743 &completion,
5744 intent,
5745 &buffer_handle,
5746 &completions_menu.initial_position.text_anchor,
5747 cx,
5748 );
5749
5750 let buffer = buffer_handle.read(cx);
5751 let snapshot = self.buffer.read(cx).snapshot(cx);
5752 let newest_anchor = self.selections.newest_anchor();
5753 let replace_range_multibuffer = {
5754 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5755 let multibuffer_anchor = snapshot
5756 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5757 .unwrap()
5758 ..snapshot
5759 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5760 .unwrap();
5761 multibuffer_anchor.start.to_offset(&snapshot)
5762 ..multibuffer_anchor.end.to_offset(&snapshot)
5763 };
5764 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5765 return None;
5766 }
5767
5768 let old_text = buffer
5769 .text_for_range(replace_range.clone())
5770 .collect::<String>();
5771 let lookbehind = newest_anchor
5772 .start
5773 .text_anchor
5774 .to_offset(buffer)
5775 .saturating_sub(replace_range.start);
5776 let lookahead = replace_range
5777 .end
5778 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5779 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5780 let suffix = &old_text[lookbehind.min(old_text.len())..];
5781
5782 let selections = self.selections.all::<usize>(cx);
5783 let mut ranges = Vec::new();
5784 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5785
5786 for selection in &selections {
5787 let range = if selection.id == newest_anchor.id {
5788 replace_range_multibuffer.clone()
5789 } else {
5790 let mut range = selection.range();
5791
5792 // if prefix is present, don't duplicate it
5793 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5794 range.start = range.start.saturating_sub(lookbehind);
5795
5796 // if suffix is also present, mimic the newest cursor and replace it
5797 if selection.id != newest_anchor.id
5798 && snapshot.contains_str_at(range.end, suffix)
5799 {
5800 range.end += lookahead;
5801 }
5802 }
5803 range
5804 };
5805
5806 ranges.push(range.clone());
5807
5808 if !self.linked_edit_ranges.is_empty() {
5809 let start_anchor = snapshot.anchor_before(range.start);
5810 let end_anchor = snapshot.anchor_after(range.end);
5811 if let Some(ranges) = self
5812 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5813 {
5814 for (buffer, edits) in ranges {
5815 linked_edits
5816 .entry(buffer.clone())
5817 .or_default()
5818 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5819 }
5820 }
5821 }
5822 }
5823
5824 let common_prefix_len = old_text
5825 .chars()
5826 .zip(new_text.chars())
5827 .take_while(|(a, b)| a == b)
5828 .map(|(a, _)| a.len_utf8())
5829 .sum::<usize>();
5830
5831 cx.emit(EditorEvent::InputHandled {
5832 utf16_range_to_replace: None,
5833 text: new_text[common_prefix_len..].into(),
5834 });
5835
5836 self.transact(window, cx, |this, window, cx| {
5837 if let Some(mut snippet) = snippet {
5838 snippet.text = new_text.to_string();
5839 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5840 } else {
5841 this.buffer.update(cx, |buffer, cx| {
5842 let auto_indent = match completion.insert_text_mode {
5843 Some(InsertTextMode::AS_IS) => None,
5844 _ => this.autoindent_mode.clone(),
5845 };
5846 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5847 buffer.edit(edits, auto_indent, cx);
5848 });
5849 }
5850 for (buffer, edits) in linked_edits {
5851 buffer.update(cx, |buffer, cx| {
5852 let snapshot = buffer.snapshot();
5853 let edits = edits
5854 .into_iter()
5855 .map(|(range, text)| {
5856 use text::ToPoint as TP;
5857 let end_point = TP::to_point(&range.end, &snapshot);
5858 let start_point = TP::to_point(&range.start, &snapshot);
5859 (start_point..end_point, text)
5860 })
5861 .sorted_by_key(|(range, _)| range.start);
5862 buffer.edit(edits, None, cx);
5863 })
5864 }
5865
5866 this.refresh_inline_completion(true, false, window, cx);
5867 });
5868
5869 let show_new_completions_on_confirm = completion
5870 .confirm
5871 .as_ref()
5872 .map_or(false, |confirm| confirm(intent, window, cx));
5873 if show_new_completions_on_confirm {
5874 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5875 }
5876
5877 let provider = self.completion_provider.as_ref()?;
5878 drop(completion);
5879 let apply_edits = provider.apply_additional_edits_for_completion(
5880 buffer_handle,
5881 completions_menu.completions.clone(),
5882 candidate_id,
5883 true,
5884 cx,
5885 );
5886
5887 let editor_settings = EditorSettings::get_global(cx);
5888 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5889 // After the code completion is finished, users often want to know what signatures are needed.
5890 // so we should automatically call signature_help
5891 self.show_signature_help(&ShowSignatureHelp, window, cx);
5892 }
5893
5894 Some(cx.foreground_executor().spawn(async move {
5895 apply_edits.await?;
5896 Ok(())
5897 }))
5898 }
5899
5900 pub fn toggle_code_actions(
5901 &mut self,
5902 action: &ToggleCodeActions,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) {
5906 let quick_launch = action.quick_launch;
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5909 if code_actions.deployed_from == action.deployed_from {
5910 // Toggle if we're selecting the same one
5911 *context_menu = None;
5912 cx.notify();
5913 return;
5914 } else {
5915 // Otherwise, clear it and start a new one
5916 *context_menu = None;
5917 cx.notify();
5918 }
5919 }
5920 drop(context_menu);
5921 let snapshot = self.snapshot(window, cx);
5922 let deployed_from = action.deployed_from.clone();
5923 let action = action.clone();
5924 self.completion_tasks.clear();
5925 self.discard_inline_completion(false, cx);
5926
5927 let multibuffer_point = match &action.deployed_from {
5928 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5929 DisplayPoint::new(*row, 0).to_point(&snapshot)
5930 }
5931 _ => self.selections.newest::<Point>(cx).head(),
5932 };
5933 let Some((buffer, buffer_row)) = snapshot
5934 .buffer_snapshot
5935 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5936 .and_then(|(buffer_snapshot, range)| {
5937 self.buffer()
5938 .read(cx)
5939 .buffer(buffer_snapshot.remote_id())
5940 .map(|buffer| (buffer, range.start.row))
5941 })
5942 else {
5943 return;
5944 };
5945 let buffer_id = buffer.read(cx).remote_id();
5946 let tasks = self
5947 .tasks
5948 .get(&(buffer_id, buffer_row))
5949 .map(|t| Arc::new(t.to_owned()));
5950
5951 if !self.focus_handle.is_focused(window) {
5952 return;
5953 }
5954 let project = self.project.clone();
5955
5956 let code_actions_task = match deployed_from {
5957 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5958 _ => self.code_actions(buffer_row, window, cx),
5959 };
5960
5961 let runnable_task = match deployed_from {
5962 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5963 _ => {
5964 let mut task_context_task = Task::ready(None);
5965 if let Some(tasks) = &tasks {
5966 if let Some(project) = project {
5967 task_context_task =
5968 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5969 }
5970 }
5971
5972 cx.spawn_in(window, {
5973 let buffer = buffer.clone();
5974 async move |editor, cx| {
5975 let task_context = task_context_task.await;
5976
5977 let resolved_tasks =
5978 tasks
5979 .zip(task_context.clone())
5980 .map(|(tasks, task_context)| ResolvedTasks {
5981 templates: tasks.resolve(&task_context).collect(),
5982 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5983 multibuffer_point.row,
5984 tasks.column,
5985 )),
5986 });
5987 let debug_scenarios = editor
5988 .update(cx, |editor, cx| {
5989 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5990 })?
5991 .await;
5992 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5993 }
5994 })
5995 }
5996 };
5997
5998 cx.spawn_in(window, async move |editor, cx| {
5999 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6000 let code_actions = code_actions_task.await;
6001 let spawn_straight_away = quick_launch
6002 && resolved_tasks
6003 .as_ref()
6004 .map_or(false, |tasks| tasks.templates.len() == 1)
6005 && code_actions
6006 .as_ref()
6007 .map_or(true, |actions| actions.is_empty())
6008 && debug_scenarios.is_empty();
6009
6010 editor.update_in(cx, |editor, window, cx| {
6011 crate::hover_popover::hide_hover(editor, cx);
6012 let actions = CodeActionContents::new(
6013 resolved_tasks,
6014 code_actions,
6015 debug_scenarios,
6016 task_context.unwrap_or_default(),
6017 );
6018
6019 // Don't show the menu if there are no actions available
6020 if actions.is_empty() {
6021 cx.notify();
6022 return Task::ready(Ok(()));
6023 }
6024
6025 *editor.context_menu.borrow_mut() =
6026 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6027 buffer,
6028 actions,
6029 selected_item: Default::default(),
6030 scroll_handle: UniformListScrollHandle::default(),
6031 deployed_from,
6032 }));
6033 cx.notify();
6034 if spawn_straight_away {
6035 if let Some(task) = editor.confirm_code_action(
6036 &ConfirmCodeAction { item_ix: Some(0) },
6037 window,
6038 cx,
6039 ) {
6040 return task;
6041 }
6042 }
6043
6044 Task::ready(Ok(()))
6045 })
6046 })
6047 .detach_and_log_err(cx);
6048 }
6049
6050 fn debug_scenarios(
6051 &mut self,
6052 resolved_tasks: &Option<ResolvedTasks>,
6053 buffer: &Entity<Buffer>,
6054 cx: &mut App,
6055 ) -> Task<Vec<task::DebugScenario>> {
6056 maybe!({
6057 let project = self.project.as_ref()?;
6058 let dap_store = project.read(cx).dap_store();
6059 let mut scenarios = vec![];
6060 let resolved_tasks = resolved_tasks.as_ref()?;
6061 let buffer = buffer.read(cx);
6062 let language = buffer.language()?;
6063 let file = buffer.file();
6064 let debug_adapter = language_settings(language.name().into(), file, cx)
6065 .debuggers
6066 .first()
6067 .map(SharedString::from)
6068 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6069
6070 dap_store.update(cx, |dap_store, cx| {
6071 for (_, task) in &resolved_tasks.templates {
6072 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6073 task.original_task().clone(),
6074 debug_adapter.clone().into(),
6075 task.display_label().to_owned().into(),
6076 cx,
6077 );
6078 scenarios.push(maybe_scenario);
6079 }
6080 });
6081 Some(cx.background_spawn(async move {
6082 let scenarios = futures::future::join_all(scenarios)
6083 .await
6084 .into_iter()
6085 .flatten()
6086 .collect::<Vec<_>>();
6087 scenarios
6088 }))
6089 })
6090 .unwrap_or_else(|| Task::ready(vec![]))
6091 }
6092
6093 fn code_actions(
6094 &mut self,
6095 buffer_row: u32,
6096 window: &mut Window,
6097 cx: &mut Context<Self>,
6098 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6099 let mut task = self.code_actions_task.take();
6100 cx.spawn_in(window, async move |editor, cx| {
6101 while let Some(prev_task) = task {
6102 prev_task.await.log_err();
6103 task = editor
6104 .update(cx, |this, _| this.code_actions_task.take())
6105 .ok()?;
6106 }
6107
6108 editor
6109 .update(cx, |editor, cx| {
6110 editor
6111 .available_code_actions
6112 .clone()
6113 .and_then(|(location, code_actions)| {
6114 let snapshot = location.buffer.read(cx).snapshot();
6115 let point_range = location.range.to_point(&snapshot);
6116 let point_range = point_range.start.row..=point_range.end.row;
6117 if point_range.contains(&buffer_row) {
6118 Some(code_actions)
6119 } else {
6120 None
6121 }
6122 })
6123 })
6124 .ok()
6125 .flatten()
6126 })
6127 }
6128
6129 pub fn confirm_code_action(
6130 &mut self,
6131 action: &ConfirmCodeAction,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) -> Option<Task<Result<()>>> {
6135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6136
6137 let actions_menu =
6138 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6139 menu
6140 } else {
6141 return None;
6142 };
6143
6144 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6145 let action = actions_menu.actions.get(action_ix)?;
6146 let title = action.label();
6147 let buffer = actions_menu.buffer;
6148 let workspace = self.workspace()?;
6149
6150 match action {
6151 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6152 workspace.update(cx, |workspace, cx| {
6153 workspace.schedule_resolved_task(
6154 task_source_kind,
6155 resolved_task,
6156 false,
6157 window,
6158 cx,
6159 );
6160
6161 Some(Task::ready(Ok(())))
6162 })
6163 }
6164 CodeActionsItem::CodeAction {
6165 excerpt_id,
6166 action,
6167 provider,
6168 } => {
6169 let apply_code_action =
6170 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6171 let workspace = workspace.downgrade();
6172 Some(cx.spawn_in(window, async move |editor, cx| {
6173 let project_transaction = apply_code_action.await?;
6174 Self::open_project_transaction(
6175 &editor,
6176 workspace,
6177 project_transaction,
6178 title,
6179 cx,
6180 )
6181 .await
6182 }))
6183 }
6184 CodeActionsItem::DebugScenario(scenario) => {
6185 let context = actions_menu.actions.context.clone();
6186
6187 workspace.update(cx, |workspace, cx| {
6188 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6189 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6190 });
6191 Some(Task::ready(Ok(())))
6192 }
6193 }
6194 }
6195
6196 pub async fn open_project_transaction(
6197 this: &WeakEntity<Editor>,
6198 workspace: WeakEntity<Workspace>,
6199 transaction: ProjectTransaction,
6200 title: String,
6201 cx: &mut AsyncWindowContext,
6202 ) -> Result<()> {
6203 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6204 cx.update(|_, cx| {
6205 entries.sort_unstable_by_key(|(buffer, _)| {
6206 buffer.read(cx).file().map(|f| f.path().clone())
6207 });
6208 })?;
6209
6210 // If the project transaction's edits are all contained within this editor, then
6211 // avoid opening a new editor to display them.
6212
6213 if let Some((buffer, transaction)) = entries.first() {
6214 if entries.len() == 1 {
6215 let excerpt = this.update(cx, |editor, cx| {
6216 editor
6217 .buffer()
6218 .read(cx)
6219 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6220 })?;
6221 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6222 if excerpted_buffer == *buffer {
6223 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6224 let excerpt_range = excerpt_range.to_offset(buffer);
6225 buffer
6226 .edited_ranges_for_transaction::<usize>(transaction)
6227 .all(|range| {
6228 excerpt_range.start <= range.start
6229 && excerpt_range.end >= range.end
6230 })
6231 })?;
6232
6233 if all_edits_within_excerpt {
6234 return Ok(());
6235 }
6236 }
6237 }
6238 }
6239 } else {
6240 return Ok(());
6241 }
6242
6243 let mut ranges_to_highlight = Vec::new();
6244 let excerpt_buffer = cx.new(|cx| {
6245 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6246 for (buffer_handle, transaction) in &entries {
6247 let edited_ranges = buffer_handle
6248 .read(cx)
6249 .edited_ranges_for_transaction::<Point>(transaction)
6250 .collect::<Vec<_>>();
6251 let (ranges, _) = multibuffer.set_excerpts_for_path(
6252 PathKey::for_buffer(buffer_handle, cx),
6253 buffer_handle.clone(),
6254 edited_ranges,
6255 DEFAULT_MULTIBUFFER_CONTEXT,
6256 cx,
6257 );
6258
6259 ranges_to_highlight.extend(ranges);
6260 }
6261 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6262 multibuffer
6263 })?;
6264
6265 workspace.update_in(cx, |workspace, window, cx| {
6266 let project = workspace.project().clone();
6267 let editor =
6268 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6269 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6270 editor.update(cx, |editor, cx| {
6271 editor.highlight_background::<Self>(
6272 &ranges_to_highlight,
6273 |theme| theme.colors().editor_highlighted_line_background,
6274 cx,
6275 );
6276 });
6277 })?;
6278
6279 Ok(())
6280 }
6281
6282 pub fn clear_code_action_providers(&mut self) {
6283 self.code_action_providers.clear();
6284 self.available_code_actions.take();
6285 }
6286
6287 pub fn add_code_action_provider(
6288 &mut self,
6289 provider: Rc<dyn CodeActionProvider>,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) {
6293 if self
6294 .code_action_providers
6295 .iter()
6296 .any(|existing_provider| existing_provider.id() == provider.id())
6297 {
6298 return;
6299 }
6300
6301 self.code_action_providers.push(provider);
6302 self.refresh_code_actions(window, cx);
6303 }
6304
6305 pub fn remove_code_action_provider(
6306 &mut self,
6307 id: Arc<str>,
6308 window: &mut Window,
6309 cx: &mut Context<Self>,
6310 ) {
6311 self.code_action_providers
6312 .retain(|provider| provider.id() != id);
6313 self.refresh_code_actions(window, cx);
6314 }
6315
6316 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6317 !self.code_action_providers.is_empty()
6318 && EditorSettings::get_global(cx).toolbar.code_actions
6319 }
6320
6321 pub fn has_available_code_actions(&self) -> bool {
6322 self.available_code_actions
6323 .as_ref()
6324 .is_some_and(|(_, actions)| !actions.is_empty())
6325 }
6326
6327 fn render_inline_code_actions(
6328 &self,
6329 icon_size: ui::IconSize,
6330 display_row: DisplayRow,
6331 is_active: bool,
6332 cx: &mut Context<Self>,
6333 ) -> AnyElement {
6334 let show_tooltip = !self.context_menu_visible();
6335 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6336 .icon_size(icon_size)
6337 .shape(ui::IconButtonShape::Square)
6338 .style(ButtonStyle::Transparent)
6339 .icon_color(ui::Color::Hidden)
6340 .toggle_state(is_active)
6341 .when(show_tooltip, |this| {
6342 this.tooltip({
6343 let focus_handle = self.focus_handle.clone();
6344 move |window, cx| {
6345 Tooltip::for_action_in(
6346 "Toggle Code Actions",
6347 &ToggleCodeActions {
6348 deployed_from: None,
6349 quick_launch: false,
6350 },
6351 &focus_handle,
6352 window,
6353 cx,
6354 )
6355 }
6356 })
6357 })
6358 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6359 window.focus(&editor.focus_handle(cx));
6360 editor.toggle_code_actions(
6361 &crate::actions::ToggleCodeActions {
6362 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6363 display_row,
6364 )),
6365 quick_launch: false,
6366 },
6367 window,
6368 cx,
6369 );
6370 }))
6371 .into_any_element()
6372 }
6373
6374 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6375 &self.context_menu
6376 }
6377
6378 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6379 let newest_selection = self.selections.newest_anchor().clone();
6380 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6381 let buffer = self.buffer.read(cx);
6382 if newest_selection.head().diff_base_anchor.is_some() {
6383 return None;
6384 }
6385 let (start_buffer, start) =
6386 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6387 let (end_buffer, end) =
6388 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6389 if start_buffer != end_buffer {
6390 return None;
6391 }
6392
6393 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6394 cx.background_executor()
6395 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6396 .await;
6397
6398 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6399 let providers = this.code_action_providers.clone();
6400 let tasks = this
6401 .code_action_providers
6402 .iter()
6403 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6404 .collect::<Vec<_>>();
6405 (providers, tasks)
6406 })?;
6407
6408 let mut actions = Vec::new();
6409 for (provider, provider_actions) in
6410 providers.into_iter().zip(future::join_all(tasks).await)
6411 {
6412 if let Some(provider_actions) = provider_actions.log_err() {
6413 actions.extend(provider_actions.into_iter().map(|action| {
6414 AvailableCodeAction {
6415 excerpt_id: newest_selection.start.excerpt_id,
6416 action,
6417 provider: provider.clone(),
6418 }
6419 }));
6420 }
6421 }
6422
6423 this.update(cx, |this, cx| {
6424 this.available_code_actions = if actions.is_empty() {
6425 None
6426 } else {
6427 Some((
6428 Location {
6429 buffer: start_buffer,
6430 range: start..end,
6431 },
6432 actions.into(),
6433 ))
6434 };
6435 cx.notify();
6436 })
6437 }));
6438 None
6439 }
6440
6441 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6442 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6443 self.show_git_blame_inline = false;
6444
6445 self.show_git_blame_inline_delay_task =
6446 Some(cx.spawn_in(window, async move |this, cx| {
6447 cx.background_executor().timer(delay).await;
6448
6449 this.update(cx, |this, cx| {
6450 this.show_git_blame_inline = true;
6451 cx.notify();
6452 })
6453 .log_err();
6454 }));
6455 }
6456 }
6457
6458 fn show_blame_popover(
6459 &mut self,
6460 blame_entry: &BlameEntry,
6461 position: gpui::Point<Pixels>,
6462 cx: &mut Context<Self>,
6463 ) {
6464 if let Some(state) = &mut self.inline_blame_popover {
6465 state.hide_task.take();
6466 } else {
6467 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6468 let blame_entry = blame_entry.clone();
6469 let show_task = cx.spawn(async move |editor, cx| {
6470 cx.background_executor()
6471 .timer(std::time::Duration::from_millis(delay))
6472 .await;
6473 editor
6474 .update(cx, |editor, cx| {
6475 editor.inline_blame_popover_show_task.take();
6476 let Some(blame) = editor.blame.as_ref() else {
6477 return;
6478 };
6479 let blame = blame.read(cx);
6480 let details = blame.details_for_entry(&blame_entry);
6481 let markdown = cx.new(|cx| {
6482 Markdown::new(
6483 details
6484 .as_ref()
6485 .map(|message| message.message.clone())
6486 .unwrap_or_default(),
6487 None,
6488 None,
6489 cx,
6490 )
6491 });
6492 editor.inline_blame_popover = Some(InlineBlamePopover {
6493 position,
6494 hide_task: None,
6495 popover_bounds: None,
6496 popover_state: InlineBlamePopoverState {
6497 scroll_handle: ScrollHandle::new(),
6498 commit_message: details,
6499 markdown,
6500 },
6501 });
6502 cx.notify();
6503 })
6504 .ok();
6505 });
6506 self.inline_blame_popover_show_task = Some(show_task);
6507 }
6508 }
6509
6510 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6511 self.inline_blame_popover_show_task.take();
6512 if let Some(state) = &mut self.inline_blame_popover {
6513 let hide_task = cx.spawn(async move |editor, cx| {
6514 cx.background_executor()
6515 .timer(std::time::Duration::from_millis(100))
6516 .await;
6517 editor
6518 .update(cx, |editor, cx| {
6519 editor.inline_blame_popover.take();
6520 cx.notify();
6521 })
6522 .ok();
6523 });
6524 state.hide_task = Some(hide_task);
6525 }
6526 }
6527
6528 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6529 if self.pending_rename.is_some() {
6530 return None;
6531 }
6532
6533 let provider = self.semantics_provider.clone()?;
6534 let buffer = self.buffer.read(cx);
6535 let newest_selection = self.selections.newest_anchor().clone();
6536 let cursor_position = newest_selection.head();
6537 let (cursor_buffer, cursor_buffer_position) =
6538 buffer.text_anchor_for_position(cursor_position, cx)?;
6539 let (tail_buffer, tail_buffer_position) =
6540 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6541 if cursor_buffer != tail_buffer {
6542 return None;
6543 }
6544
6545 let snapshot = cursor_buffer.read(cx).snapshot();
6546 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6547 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6548 if start_word_range != end_word_range {
6549 self.document_highlights_task.take();
6550 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6551 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6552 return None;
6553 }
6554
6555 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6556 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6557 cx.background_executor()
6558 .timer(Duration::from_millis(debounce))
6559 .await;
6560
6561 let highlights = if let Some(highlights) = cx
6562 .update(|cx| {
6563 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6564 })
6565 .ok()
6566 .flatten()
6567 {
6568 highlights.await.log_err()
6569 } else {
6570 None
6571 };
6572
6573 if let Some(highlights) = highlights {
6574 this.update(cx, |this, cx| {
6575 if this.pending_rename.is_some() {
6576 return;
6577 }
6578
6579 let buffer_id = cursor_position.buffer_id;
6580 let buffer = this.buffer.read(cx);
6581 if !buffer
6582 .text_anchor_for_position(cursor_position, cx)
6583 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6584 {
6585 return;
6586 }
6587
6588 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6589 let mut write_ranges = Vec::new();
6590 let mut read_ranges = Vec::new();
6591 for highlight in highlights {
6592 for (excerpt_id, excerpt_range) in
6593 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6594 {
6595 let start = highlight
6596 .range
6597 .start
6598 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6599 let end = highlight
6600 .range
6601 .end
6602 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6603 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6604 continue;
6605 }
6606
6607 let range = Anchor {
6608 buffer_id,
6609 excerpt_id,
6610 text_anchor: start,
6611 diff_base_anchor: None,
6612 }..Anchor {
6613 buffer_id,
6614 excerpt_id,
6615 text_anchor: end,
6616 diff_base_anchor: None,
6617 };
6618 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6619 write_ranges.push(range);
6620 } else {
6621 read_ranges.push(range);
6622 }
6623 }
6624 }
6625
6626 this.highlight_background::<DocumentHighlightRead>(
6627 &read_ranges,
6628 |theme| theme.colors().editor_document_highlight_read_background,
6629 cx,
6630 );
6631 this.highlight_background::<DocumentHighlightWrite>(
6632 &write_ranges,
6633 |theme| theme.colors().editor_document_highlight_write_background,
6634 cx,
6635 );
6636 cx.notify();
6637 })
6638 .log_err();
6639 }
6640 }));
6641 None
6642 }
6643
6644 fn prepare_highlight_query_from_selection(
6645 &mut self,
6646 cx: &mut Context<Editor>,
6647 ) -> Option<(String, Range<Anchor>)> {
6648 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6649 return None;
6650 }
6651 if !EditorSettings::get_global(cx).selection_highlight {
6652 return None;
6653 }
6654 if self.selections.count() != 1 || self.selections.line_mode {
6655 return None;
6656 }
6657 let selection = self.selections.newest::<Point>(cx);
6658 if selection.is_empty() || selection.start.row != selection.end.row {
6659 return None;
6660 }
6661 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6662 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6663 let query = multi_buffer_snapshot
6664 .text_for_range(selection_anchor_range.clone())
6665 .collect::<String>();
6666 if query.trim().is_empty() {
6667 return None;
6668 }
6669 Some((query, selection_anchor_range))
6670 }
6671
6672 fn update_selection_occurrence_highlights(
6673 &mut self,
6674 query_text: String,
6675 query_range: Range<Anchor>,
6676 multi_buffer_range_to_query: Range<Point>,
6677 use_debounce: bool,
6678 window: &mut Window,
6679 cx: &mut Context<Editor>,
6680 ) -> Task<()> {
6681 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6682 cx.spawn_in(window, async move |editor, cx| {
6683 if use_debounce {
6684 cx.background_executor()
6685 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6686 .await;
6687 }
6688 let match_task = cx.background_spawn(async move {
6689 let buffer_ranges = multi_buffer_snapshot
6690 .range_to_buffer_ranges(multi_buffer_range_to_query)
6691 .into_iter()
6692 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6693 let mut match_ranges = Vec::new();
6694 let Ok(regex) = project::search::SearchQuery::text(
6695 query_text.clone(),
6696 false,
6697 false,
6698 false,
6699 Default::default(),
6700 Default::default(),
6701 false,
6702 None,
6703 ) else {
6704 return Vec::default();
6705 };
6706 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6707 match_ranges.extend(
6708 regex
6709 .search(&buffer_snapshot, Some(search_range.clone()))
6710 .await
6711 .into_iter()
6712 .filter_map(|match_range| {
6713 let match_start = buffer_snapshot
6714 .anchor_after(search_range.start + match_range.start);
6715 let match_end = buffer_snapshot
6716 .anchor_before(search_range.start + match_range.end);
6717 let match_anchor_range = Anchor::range_in_buffer(
6718 excerpt_id,
6719 buffer_snapshot.remote_id(),
6720 match_start..match_end,
6721 );
6722 (match_anchor_range != query_range).then_some(match_anchor_range)
6723 }),
6724 );
6725 }
6726 match_ranges
6727 });
6728 let match_ranges = match_task.await;
6729 editor
6730 .update_in(cx, |editor, _, cx| {
6731 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6732 if !match_ranges.is_empty() {
6733 editor.highlight_background::<SelectedTextHighlight>(
6734 &match_ranges,
6735 |theme| theme.colors().editor_document_highlight_bracket_background,
6736 cx,
6737 )
6738 }
6739 })
6740 .log_err();
6741 })
6742 }
6743
6744 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6745 struct NewlineFold;
6746 let type_id = std::any::TypeId::of::<NewlineFold>();
6747 if !self.mode.is_single_line() {
6748 return;
6749 }
6750 let snapshot = self.snapshot(window, cx);
6751 if snapshot.buffer_snapshot.max_point().row == 0 {
6752 return;
6753 }
6754 let task = cx.background_spawn(async move {
6755 let new_newlines = snapshot
6756 .buffer_chars_at(0)
6757 .filter_map(|(c, i)| {
6758 if c == '\n' {
6759 Some(
6760 snapshot.buffer_snapshot.anchor_after(i)
6761 ..snapshot.buffer_snapshot.anchor_before(i + 1),
6762 )
6763 } else {
6764 None
6765 }
6766 })
6767 .collect::<Vec<_>>();
6768 let existing_newlines = snapshot
6769 .folds_in_range(0..snapshot.buffer_snapshot.len())
6770 .filter_map(|fold| {
6771 if fold.placeholder.type_tag == Some(type_id) {
6772 Some(fold.range.start..fold.range.end)
6773 } else {
6774 None
6775 }
6776 })
6777 .collect::<Vec<_>>();
6778
6779 (new_newlines, existing_newlines)
6780 });
6781 self.folding_newlines = cx.spawn(async move |this, cx| {
6782 let (new_newlines, existing_newlines) = task.await;
6783 if new_newlines == existing_newlines {
6784 return;
6785 }
6786 let placeholder = FoldPlaceholder {
6787 render: Arc::new(move |_, _, cx| {
6788 div()
6789 .bg(cx.theme().status().hint_background)
6790 .border_b_1()
6791 .size_full()
6792 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6793 .border_color(cx.theme().status().hint)
6794 .child("\\n")
6795 .into_any()
6796 }),
6797 constrain_width: false,
6798 merge_adjacent: false,
6799 type_tag: Some(type_id),
6800 };
6801 let creases = new_newlines
6802 .into_iter()
6803 .map(|range| Crease::simple(range, placeholder.clone()))
6804 .collect();
6805 this.update(cx, |this, cx| {
6806 this.display_map.update(cx, |display_map, cx| {
6807 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6808 display_map.fold(creases, cx);
6809 });
6810 })
6811 .ok();
6812 });
6813 }
6814
6815 fn refresh_selected_text_highlights(
6816 &mut self,
6817 on_buffer_edit: bool,
6818 window: &mut Window,
6819 cx: &mut Context<Editor>,
6820 ) {
6821 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6822 else {
6823 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6824 self.quick_selection_highlight_task.take();
6825 self.debounced_selection_highlight_task.take();
6826 return;
6827 };
6828 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6829 if on_buffer_edit
6830 || self
6831 .quick_selection_highlight_task
6832 .as_ref()
6833 .map_or(true, |(prev_anchor_range, _)| {
6834 prev_anchor_range != &query_range
6835 })
6836 {
6837 let multi_buffer_visible_start = self
6838 .scroll_manager
6839 .anchor()
6840 .anchor
6841 .to_point(&multi_buffer_snapshot);
6842 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6843 multi_buffer_visible_start
6844 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6845 Bias::Left,
6846 );
6847 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6848 self.quick_selection_highlight_task = Some((
6849 query_range.clone(),
6850 self.update_selection_occurrence_highlights(
6851 query_text.clone(),
6852 query_range.clone(),
6853 multi_buffer_visible_range,
6854 false,
6855 window,
6856 cx,
6857 ),
6858 ));
6859 }
6860 if on_buffer_edit
6861 || self
6862 .debounced_selection_highlight_task
6863 .as_ref()
6864 .map_or(true, |(prev_anchor_range, _)| {
6865 prev_anchor_range != &query_range
6866 })
6867 {
6868 let multi_buffer_start = multi_buffer_snapshot
6869 .anchor_before(0)
6870 .to_point(&multi_buffer_snapshot);
6871 let multi_buffer_end = multi_buffer_snapshot
6872 .anchor_after(multi_buffer_snapshot.len())
6873 .to_point(&multi_buffer_snapshot);
6874 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6875 self.debounced_selection_highlight_task = Some((
6876 query_range.clone(),
6877 self.update_selection_occurrence_highlights(
6878 query_text,
6879 query_range,
6880 multi_buffer_full_range,
6881 true,
6882 window,
6883 cx,
6884 ),
6885 ));
6886 }
6887 }
6888
6889 pub fn refresh_inline_completion(
6890 &mut self,
6891 debounce: bool,
6892 user_requested: bool,
6893 window: &mut Window,
6894 cx: &mut Context<Self>,
6895 ) -> Option<()> {
6896 let provider = self.edit_prediction_provider()?;
6897 let cursor = self.selections.newest_anchor().head();
6898 let (buffer, cursor_buffer_position) =
6899 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6900
6901 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6902 self.discard_inline_completion(false, cx);
6903 return None;
6904 }
6905
6906 if !user_requested
6907 && (!self.should_show_edit_predictions()
6908 || !self.is_focused(window)
6909 || buffer.read(cx).is_empty())
6910 {
6911 self.discard_inline_completion(false, cx);
6912 return None;
6913 }
6914
6915 self.update_visible_inline_completion(window, cx);
6916 provider.refresh(
6917 self.project.clone(),
6918 buffer,
6919 cursor_buffer_position,
6920 debounce,
6921 cx,
6922 );
6923 Some(())
6924 }
6925
6926 fn show_edit_predictions_in_menu(&self) -> bool {
6927 match self.edit_prediction_settings {
6928 EditPredictionSettings::Disabled => false,
6929 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6930 }
6931 }
6932
6933 pub fn edit_predictions_enabled(&self) -> bool {
6934 match self.edit_prediction_settings {
6935 EditPredictionSettings::Disabled => false,
6936 EditPredictionSettings::Enabled { .. } => true,
6937 }
6938 }
6939
6940 fn edit_prediction_requires_modifier(&self) -> bool {
6941 match self.edit_prediction_settings {
6942 EditPredictionSettings::Disabled => false,
6943 EditPredictionSettings::Enabled {
6944 preview_requires_modifier,
6945 ..
6946 } => preview_requires_modifier,
6947 }
6948 }
6949
6950 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6951 if self.edit_prediction_provider.is_none() {
6952 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6953 } else {
6954 let selection = self.selections.newest_anchor();
6955 let cursor = selection.head();
6956
6957 if let Some((buffer, cursor_buffer_position)) =
6958 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6959 {
6960 self.edit_prediction_settings =
6961 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6962 }
6963 }
6964 }
6965
6966 fn edit_prediction_settings_at_position(
6967 &self,
6968 buffer: &Entity<Buffer>,
6969 buffer_position: language::Anchor,
6970 cx: &App,
6971 ) -> EditPredictionSettings {
6972 if !self.mode.is_full()
6973 || !self.show_inline_completions_override.unwrap_or(true)
6974 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6975 {
6976 return EditPredictionSettings::Disabled;
6977 }
6978
6979 let buffer = buffer.read(cx);
6980
6981 let file = buffer.file();
6982
6983 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6984 return EditPredictionSettings::Disabled;
6985 };
6986
6987 let by_provider = matches!(
6988 self.menu_inline_completions_policy,
6989 MenuInlineCompletionsPolicy::ByProvider
6990 );
6991
6992 let show_in_menu = by_provider
6993 && self
6994 .edit_prediction_provider
6995 .as_ref()
6996 .map_or(false, |provider| {
6997 provider.provider.show_completions_in_menu()
6998 });
6999
7000 let preview_requires_modifier =
7001 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7002
7003 EditPredictionSettings::Enabled {
7004 show_in_menu,
7005 preview_requires_modifier,
7006 }
7007 }
7008
7009 fn should_show_edit_predictions(&self) -> bool {
7010 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7011 }
7012
7013 pub fn edit_prediction_preview_is_active(&self) -> bool {
7014 matches!(
7015 self.edit_prediction_preview,
7016 EditPredictionPreview::Active { .. }
7017 )
7018 }
7019
7020 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7021 let cursor = self.selections.newest_anchor().head();
7022 if let Some((buffer, cursor_position)) =
7023 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7024 {
7025 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7026 } else {
7027 false
7028 }
7029 }
7030
7031 pub fn supports_minimap(&self, cx: &App) -> bool {
7032 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7033 }
7034
7035 fn edit_predictions_enabled_in_buffer(
7036 &self,
7037 buffer: &Entity<Buffer>,
7038 buffer_position: language::Anchor,
7039 cx: &App,
7040 ) -> bool {
7041 maybe!({
7042 if self.read_only(cx) {
7043 return Some(false);
7044 }
7045 let provider = self.edit_prediction_provider()?;
7046 if !provider.is_enabled(&buffer, buffer_position, cx) {
7047 return Some(false);
7048 }
7049 let buffer = buffer.read(cx);
7050 let Some(file) = buffer.file() else {
7051 return Some(true);
7052 };
7053 let settings = all_language_settings(Some(file), cx);
7054 Some(settings.edit_predictions_enabled_for_file(file, cx))
7055 })
7056 .unwrap_or(false)
7057 }
7058
7059 fn cycle_inline_completion(
7060 &mut self,
7061 direction: Direction,
7062 window: &mut Window,
7063 cx: &mut Context<Self>,
7064 ) -> Option<()> {
7065 let provider = self.edit_prediction_provider()?;
7066 let cursor = self.selections.newest_anchor().head();
7067 let (buffer, cursor_buffer_position) =
7068 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7069 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7070 return None;
7071 }
7072
7073 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7074 self.update_visible_inline_completion(window, cx);
7075
7076 Some(())
7077 }
7078
7079 pub fn show_inline_completion(
7080 &mut self,
7081 _: &ShowEditPrediction,
7082 window: &mut Window,
7083 cx: &mut Context<Self>,
7084 ) {
7085 if !self.has_active_inline_completion() {
7086 self.refresh_inline_completion(false, true, window, cx);
7087 return;
7088 }
7089
7090 self.update_visible_inline_completion(window, cx);
7091 }
7092
7093 pub fn display_cursor_names(
7094 &mut self,
7095 _: &DisplayCursorNames,
7096 window: &mut Window,
7097 cx: &mut Context<Self>,
7098 ) {
7099 self.show_cursor_names(window, cx);
7100 }
7101
7102 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7103 self.show_cursor_names = true;
7104 cx.notify();
7105 cx.spawn_in(window, async move |this, cx| {
7106 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7107 this.update(cx, |this, cx| {
7108 this.show_cursor_names = false;
7109 cx.notify()
7110 })
7111 .ok()
7112 })
7113 .detach();
7114 }
7115
7116 pub fn next_edit_prediction(
7117 &mut self,
7118 _: &NextEditPrediction,
7119 window: &mut Window,
7120 cx: &mut Context<Self>,
7121 ) {
7122 if self.has_active_inline_completion() {
7123 self.cycle_inline_completion(Direction::Next, window, cx);
7124 } else {
7125 let is_copilot_disabled = self
7126 .refresh_inline_completion(false, true, window, cx)
7127 .is_none();
7128 if is_copilot_disabled {
7129 cx.propagate();
7130 }
7131 }
7132 }
7133
7134 pub fn previous_edit_prediction(
7135 &mut self,
7136 _: &PreviousEditPrediction,
7137 window: &mut Window,
7138 cx: &mut Context<Self>,
7139 ) {
7140 if self.has_active_inline_completion() {
7141 self.cycle_inline_completion(Direction::Prev, window, cx);
7142 } else {
7143 let is_copilot_disabled = self
7144 .refresh_inline_completion(false, true, window, cx)
7145 .is_none();
7146 if is_copilot_disabled {
7147 cx.propagate();
7148 }
7149 }
7150 }
7151
7152 pub fn accept_edit_prediction(
7153 &mut self,
7154 _: &AcceptEditPrediction,
7155 window: &mut Window,
7156 cx: &mut Context<Self>,
7157 ) {
7158 if self.show_edit_predictions_in_menu() {
7159 self.hide_context_menu(window, cx);
7160 }
7161
7162 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7163 return;
7164 };
7165
7166 self.report_inline_completion_event(
7167 active_inline_completion.completion_id.clone(),
7168 true,
7169 cx,
7170 );
7171
7172 match &active_inline_completion.completion {
7173 InlineCompletion::Move { target, .. } => {
7174 let target = *target;
7175
7176 if let Some(position_map) = &self.last_position_map {
7177 if position_map
7178 .visible_row_range
7179 .contains(&target.to_display_point(&position_map.snapshot).row())
7180 || !self.edit_prediction_requires_modifier()
7181 {
7182 self.unfold_ranges(&[target..target], true, false, cx);
7183 // Note that this is also done in vim's handler of the Tab action.
7184 self.change_selections(
7185 SelectionEffects::scroll(Autoscroll::newest()),
7186 window,
7187 cx,
7188 |selections| {
7189 selections.select_anchor_ranges([target..target]);
7190 },
7191 );
7192 self.clear_row_highlights::<EditPredictionPreview>();
7193
7194 self.edit_prediction_preview
7195 .set_previous_scroll_position(None);
7196 } else {
7197 self.edit_prediction_preview
7198 .set_previous_scroll_position(Some(
7199 position_map.snapshot.scroll_anchor,
7200 ));
7201
7202 self.highlight_rows::<EditPredictionPreview>(
7203 target..target,
7204 cx.theme().colors().editor_highlighted_line_background,
7205 RowHighlightOptions {
7206 autoscroll: true,
7207 ..Default::default()
7208 },
7209 cx,
7210 );
7211 self.request_autoscroll(Autoscroll::fit(), cx);
7212 }
7213 }
7214 }
7215 InlineCompletion::Edit { edits, .. } => {
7216 if let Some(provider) = self.edit_prediction_provider() {
7217 provider.accept(cx);
7218 }
7219
7220 // Store the transaction ID and selections before applying the edit
7221 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7222
7223 let snapshot = self.buffer.read(cx).snapshot(cx);
7224 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7225
7226 self.buffer.update(cx, |buffer, cx| {
7227 buffer.edit(edits.iter().cloned(), None, cx)
7228 });
7229
7230 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7231 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7232 });
7233
7234 let selections = self.selections.disjoint_anchors();
7235 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7236 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7237 if has_new_transaction {
7238 self.selection_history
7239 .insert_transaction(transaction_id_now, selections);
7240 }
7241 }
7242
7243 self.update_visible_inline_completion(window, cx);
7244 if self.active_inline_completion.is_none() {
7245 self.refresh_inline_completion(true, true, window, cx);
7246 }
7247
7248 cx.notify();
7249 }
7250 }
7251
7252 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7253 }
7254
7255 pub fn accept_partial_inline_completion(
7256 &mut self,
7257 _: &AcceptPartialEditPrediction,
7258 window: &mut Window,
7259 cx: &mut Context<Self>,
7260 ) {
7261 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7262 return;
7263 };
7264 if self.selections.count() != 1 {
7265 return;
7266 }
7267
7268 self.report_inline_completion_event(
7269 active_inline_completion.completion_id.clone(),
7270 true,
7271 cx,
7272 );
7273
7274 match &active_inline_completion.completion {
7275 InlineCompletion::Move { target, .. } => {
7276 let target = *target;
7277 self.change_selections(
7278 SelectionEffects::scroll(Autoscroll::newest()),
7279 window,
7280 cx,
7281 |selections| {
7282 selections.select_anchor_ranges([target..target]);
7283 },
7284 );
7285 }
7286 InlineCompletion::Edit { edits, .. } => {
7287 // Find an insertion that starts at the cursor position.
7288 let snapshot = self.buffer.read(cx).snapshot(cx);
7289 let cursor_offset = self.selections.newest::<usize>(cx).head();
7290 let insertion = edits.iter().find_map(|(range, text)| {
7291 let range = range.to_offset(&snapshot);
7292 if range.is_empty() && range.start == cursor_offset {
7293 Some(text)
7294 } else {
7295 None
7296 }
7297 });
7298
7299 if let Some(text) = insertion {
7300 let mut partial_completion = text
7301 .chars()
7302 .by_ref()
7303 .take_while(|c| c.is_alphabetic())
7304 .collect::<String>();
7305 if partial_completion.is_empty() {
7306 partial_completion = text
7307 .chars()
7308 .by_ref()
7309 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7310 .collect::<String>();
7311 }
7312
7313 cx.emit(EditorEvent::InputHandled {
7314 utf16_range_to_replace: None,
7315 text: partial_completion.clone().into(),
7316 });
7317
7318 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7319
7320 self.refresh_inline_completion(true, true, window, cx);
7321 cx.notify();
7322 } else {
7323 self.accept_edit_prediction(&Default::default(), window, cx);
7324 }
7325 }
7326 }
7327 }
7328
7329 fn discard_inline_completion(
7330 &mut self,
7331 should_report_inline_completion_event: bool,
7332 cx: &mut Context<Self>,
7333 ) -> bool {
7334 if should_report_inline_completion_event {
7335 let completion_id = self
7336 .active_inline_completion
7337 .as_ref()
7338 .and_then(|active_completion| active_completion.completion_id.clone());
7339
7340 self.report_inline_completion_event(completion_id, false, cx);
7341 }
7342
7343 if let Some(provider) = self.edit_prediction_provider() {
7344 provider.discard(cx);
7345 }
7346
7347 self.take_active_inline_completion(cx)
7348 }
7349
7350 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7351 let Some(provider) = self.edit_prediction_provider() else {
7352 return;
7353 };
7354
7355 let Some((_, buffer, _)) = self
7356 .buffer
7357 .read(cx)
7358 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7359 else {
7360 return;
7361 };
7362
7363 let extension = buffer
7364 .read(cx)
7365 .file()
7366 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7367
7368 let event_type = match accepted {
7369 true => "Edit Prediction Accepted",
7370 false => "Edit Prediction Discarded",
7371 };
7372 telemetry::event!(
7373 event_type,
7374 provider = provider.name(),
7375 prediction_id = id,
7376 suggestion_accepted = accepted,
7377 file_extension = extension,
7378 );
7379 }
7380
7381 pub fn has_active_inline_completion(&self) -> bool {
7382 self.active_inline_completion.is_some()
7383 }
7384
7385 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7386 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7387 return false;
7388 };
7389
7390 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7391 self.clear_highlights::<InlineCompletionHighlight>(cx);
7392 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7393 true
7394 }
7395
7396 /// Returns true when we're displaying the edit prediction popover below the cursor
7397 /// like we are not previewing and the LSP autocomplete menu is visible
7398 /// or we are in `when_holding_modifier` mode.
7399 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7400 if self.edit_prediction_preview_is_active()
7401 || !self.show_edit_predictions_in_menu()
7402 || !self.edit_predictions_enabled()
7403 {
7404 return false;
7405 }
7406
7407 if self.has_visible_completions_menu() {
7408 return true;
7409 }
7410
7411 has_completion && self.edit_prediction_requires_modifier()
7412 }
7413
7414 fn handle_modifiers_changed(
7415 &mut self,
7416 modifiers: Modifiers,
7417 position_map: &PositionMap,
7418 window: &mut Window,
7419 cx: &mut Context<Self>,
7420 ) {
7421 if self.show_edit_predictions_in_menu() {
7422 self.update_edit_prediction_preview(&modifiers, window, cx);
7423 }
7424
7425 self.update_selection_mode(&modifiers, position_map, window, cx);
7426
7427 let mouse_position = window.mouse_position();
7428 if !position_map.text_hitbox.is_hovered(window) {
7429 return;
7430 }
7431
7432 self.update_hovered_link(
7433 position_map.point_for_position(mouse_position),
7434 &position_map.snapshot,
7435 modifiers,
7436 window,
7437 cx,
7438 )
7439 }
7440
7441 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7442 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7443 if invert {
7444 match multi_cursor_setting {
7445 MultiCursorModifier::Alt => modifiers.alt,
7446 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7447 }
7448 } else {
7449 match multi_cursor_setting {
7450 MultiCursorModifier::Alt => modifiers.secondary(),
7451 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7452 }
7453 }
7454 }
7455
7456 fn columnar_selection_mode(
7457 modifiers: &Modifiers,
7458 cx: &mut Context<Self>,
7459 ) -> Option<ColumnarMode> {
7460 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7461 if Self::multi_cursor_modifier(false, modifiers, cx) {
7462 Some(ColumnarMode::FromMouse)
7463 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7464 Some(ColumnarMode::FromSelection)
7465 } else {
7466 None
7467 }
7468 } else {
7469 None
7470 }
7471 }
7472
7473 fn update_selection_mode(
7474 &mut self,
7475 modifiers: &Modifiers,
7476 position_map: &PositionMap,
7477 window: &mut Window,
7478 cx: &mut Context<Self>,
7479 ) {
7480 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7481 return;
7482 };
7483 if self.selections.pending.is_none() {
7484 return;
7485 }
7486
7487 let mouse_position = window.mouse_position();
7488 let point_for_position = position_map.point_for_position(mouse_position);
7489 let position = point_for_position.previous_valid;
7490
7491 self.select(
7492 SelectPhase::BeginColumnar {
7493 position,
7494 reset: false,
7495 mode,
7496 goal_column: point_for_position.exact_unclipped.column(),
7497 },
7498 window,
7499 cx,
7500 );
7501 }
7502
7503 fn update_edit_prediction_preview(
7504 &mut self,
7505 modifiers: &Modifiers,
7506 window: &mut Window,
7507 cx: &mut Context<Self>,
7508 ) {
7509 let mut modifiers_held = false;
7510 if let Some(accept_keystroke) = self
7511 .accept_edit_prediction_keybind(false, window, cx)
7512 .keystroke()
7513 {
7514 modifiers_held = modifiers_held
7515 || (&accept_keystroke.modifiers == modifiers
7516 && accept_keystroke.modifiers.modified());
7517 };
7518 if let Some(accept_partial_keystroke) = self
7519 .accept_edit_prediction_keybind(true, window, cx)
7520 .keystroke()
7521 {
7522 modifiers_held = modifiers_held
7523 || (&accept_partial_keystroke.modifiers == modifiers
7524 && accept_partial_keystroke.modifiers.modified());
7525 }
7526
7527 if modifiers_held {
7528 if matches!(
7529 self.edit_prediction_preview,
7530 EditPredictionPreview::Inactive { .. }
7531 ) {
7532 self.edit_prediction_preview = EditPredictionPreview::Active {
7533 previous_scroll_position: None,
7534 since: Instant::now(),
7535 };
7536
7537 self.update_visible_inline_completion(window, cx);
7538 cx.notify();
7539 }
7540 } else if let EditPredictionPreview::Active {
7541 previous_scroll_position,
7542 since,
7543 } = self.edit_prediction_preview
7544 {
7545 if let (Some(previous_scroll_position), Some(position_map)) =
7546 (previous_scroll_position, self.last_position_map.as_ref())
7547 {
7548 self.set_scroll_position(
7549 previous_scroll_position
7550 .scroll_position(&position_map.snapshot.display_snapshot),
7551 window,
7552 cx,
7553 );
7554 }
7555
7556 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7557 released_too_fast: since.elapsed() < Duration::from_millis(200),
7558 };
7559 self.clear_row_highlights::<EditPredictionPreview>();
7560 self.update_visible_inline_completion(window, cx);
7561 cx.notify();
7562 }
7563 }
7564
7565 fn update_visible_inline_completion(
7566 &mut self,
7567 _window: &mut Window,
7568 cx: &mut Context<Self>,
7569 ) -> Option<()> {
7570 let selection = self.selections.newest_anchor();
7571 let cursor = selection.head();
7572 let multibuffer = self.buffer.read(cx).snapshot(cx);
7573 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7574 let excerpt_id = cursor.excerpt_id;
7575
7576 let show_in_menu = self.show_edit_predictions_in_menu();
7577 let completions_menu_has_precedence = !show_in_menu
7578 && (self.context_menu.borrow().is_some()
7579 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7580
7581 if completions_menu_has_precedence
7582 || !offset_selection.is_empty()
7583 || self
7584 .active_inline_completion
7585 .as_ref()
7586 .map_or(false, |completion| {
7587 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7588 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7589 !invalidation_range.contains(&offset_selection.head())
7590 })
7591 {
7592 self.discard_inline_completion(false, cx);
7593 return None;
7594 }
7595
7596 self.take_active_inline_completion(cx);
7597 let Some(provider) = self.edit_prediction_provider() else {
7598 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7599 return None;
7600 };
7601
7602 let (buffer, cursor_buffer_position) =
7603 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7604
7605 self.edit_prediction_settings =
7606 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7607
7608 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7609
7610 if self.edit_prediction_indent_conflict {
7611 let cursor_point = cursor.to_point(&multibuffer);
7612
7613 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7614
7615 if let Some((_, indent)) = indents.iter().next() {
7616 if indent.len == cursor_point.column {
7617 self.edit_prediction_indent_conflict = false;
7618 }
7619 }
7620 }
7621
7622 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7623 let edits = inline_completion
7624 .edits
7625 .into_iter()
7626 .flat_map(|(range, new_text)| {
7627 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7628 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7629 Some((start..end, new_text))
7630 })
7631 .collect::<Vec<_>>();
7632 if edits.is_empty() {
7633 return None;
7634 }
7635
7636 let first_edit_start = edits.first().unwrap().0.start;
7637 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7638 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7639
7640 let last_edit_end = edits.last().unwrap().0.end;
7641 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7642 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7643
7644 let cursor_row = cursor.to_point(&multibuffer).row;
7645
7646 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7647
7648 let mut inlay_ids = Vec::new();
7649 let invalidation_row_range;
7650 let move_invalidation_row_range = if cursor_row < edit_start_row {
7651 Some(cursor_row..edit_end_row)
7652 } else if cursor_row > edit_end_row {
7653 Some(edit_start_row..cursor_row)
7654 } else {
7655 None
7656 };
7657 let is_move =
7658 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7659 let completion = if is_move {
7660 invalidation_row_range =
7661 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7662 let target = first_edit_start;
7663 InlineCompletion::Move { target, snapshot }
7664 } else {
7665 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7666 && !self.inline_completions_hidden_for_vim_mode;
7667
7668 if show_completions_in_buffer {
7669 if edits
7670 .iter()
7671 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7672 {
7673 let mut inlays = Vec::new();
7674 for (range, new_text) in &edits {
7675 let inlay = Inlay::inline_completion(
7676 post_inc(&mut self.next_inlay_id),
7677 range.start,
7678 new_text.as_str(),
7679 );
7680 inlay_ids.push(inlay.id);
7681 inlays.push(inlay);
7682 }
7683
7684 self.splice_inlays(&[], inlays, cx);
7685 } else {
7686 let background_color = cx.theme().status().deleted_background;
7687 self.highlight_text::<InlineCompletionHighlight>(
7688 edits.iter().map(|(range, _)| range.clone()).collect(),
7689 HighlightStyle {
7690 background_color: Some(background_color),
7691 ..Default::default()
7692 },
7693 cx,
7694 );
7695 }
7696 }
7697
7698 invalidation_row_range = edit_start_row..edit_end_row;
7699
7700 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7701 if provider.show_tab_accept_marker() {
7702 EditDisplayMode::TabAccept
7703 } else {
7704 EditDisplayMode::Inline
7705 }
7706 } else {
7707 EditDisplayMode::DiffPopover
7708 };
7709
7710 InlineCompletion::Edit {
7711 edits,
7712 edit_preview: inline_completion.edit_preview,
7713 display_mode,
7714 snapshot,
7715 }
7716 };
7717
7718 let invalidation_range = multibuffer
7719 .anchor_before(Point::new(invalidation_row_range.start, 0))
7720 ..multibuffer.anchor_after(Point::new(
7721 invalidation_row_range.end,
7722 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7723 ));
7724
7725 self.stale_inline_completion_in_menu = None;
7726 self.active_inline_completion = Some(InlineCompletionState {
7727 inlay_ids,
7728 completion,
7729 completion_id: inline_completion.id,
7730 invalidation_range,
7731 });
7732
7733 cx.notify();
7734
7735 Some(())
7736 }
7737
7738 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7739 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7740 }
7741
7742 fn clear_tasks(&mut self) {
7743 self.tasks.clear()
7744 }
7745
7746 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7747 if self.tasks.insert(key, value).is_some() {
7748 // This case should hopefully be rare, but just in case...
7749 log::error!(
7750 "multiple different run targets found on a single line, only the last target will be rendered"
7751 )
7752 }
7753 }
7754
7755 /// Get all display points of breakpoints that will be rendered within editor
7756 ///
7757 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7758 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7759 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7760 fn active_breakpoints(
7761 &self,
7762 range: Range<DisplayRow>,
7763 window: &mut Window,
7764 cx: &mut Context<Self>,
7765 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7766 let mut breakpoint_display_points = HashMap::default();
7767
7768 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7769 return breakpoint_display_points;
7770 };
7771
7772 let snapshot = self.snapshot(window, cx);
7773
7774 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7775 let Some(project) = self.project.as_ref() else {
7776 return breakpoint_display_points;
7777 };
7778
7779 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7780 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7781
7782 for (buffer_snapshot, range, excerpt_id) in
7783 multi_buffer_snapshot.range_to_buffer_ranges(range)
7784 {
7785 let Some(buffer) = project
7786 .read(cx)
7787 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7788 else {
7789 continue;
7790 };
7791 let breakpoints = breakpoint_store.read(cx).breakpoints(
7792 &buffer,
7793 Some(
7794 buffer_snapshot.anchor_before(range.start)
7795 ..buffer_snapshot.anchor_after(range.end),
7796 ),
7797 buffer_snapshot,
7798 cx,
7799 );
7800 for (breakpoint, state) in breakpoints {
7801 let multi_buffer_anchor =
7802 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7803 let position = multi_buffer_anchor
7804 .to_point(&multi_buffer_snapshot)
7805 .to_display_point(&snapshot);
7806
7807 breakpoint_display_points.insert(
7808 position.row(),
7809 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7810 );
7811 }
7812 }
7813
7814 breakpoint_display_points
7815 }
7816
7817 fn breakpoint_context_menu(
7818 &self,
7819 anchor: Anchor,
7820 window: &mut Window,
7821 cx: &mut Context<Self>,
7822 ) -> Entity<ui::ContextMenu> {
7823 let weak_editor = cx.weak_entity();
7824 let focus_handle = self.focus_handle(cx);
7825
7826 let row = self
7827 .buffer
7828 .read(cx)
7829 .snapshot(cx)
7830 .summary_for_anchor::<Point>(&anchor)
7831 .row;
7832
7833 let breakpoint = self
7834 .breakpoint_at_row(row, window, cx)
7835 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7836
7837 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7838 "Edit Log Breakpoint"
7839 } else {
7840 "Set Log Breakpoint"
7841 };
7842
7843 let condition_breakpoint_msg = if breakpoint
7844 .as_ref()
7845 .is_some_and(|bp| bp.1.condition.is_some())
7846 {
7847 "Edit Condition Breakpoint"
7848 } else {
7849 "Set Condition Breakpoint"
7850 };
7851
7852 let hit_condition_breakpoint_msg = if breakpoint
7853 .as_ref()
7854 .is_some_and(|bp| bp.1.hit_condition.is_some())
7855 {
7856 "Edit Hit Condition Breakpoint"
7857 } else {
7858 "Set Hit Condition Breakpoint"
7859 };
7860
7861 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7862 "Unset Breakpoint"
7863 } else {
7864 "Set Breakpoint"
7865 };
7866
7867 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7868
7869 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7870 BreakpointState::Enabled => Some("Disable"),
7871 BreakpointState::Disabled => Some("Enable"),
7872 });
7873
7874 let (anchor, breakpoint) =
7875 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7876
7877 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7878 menu.on_blur_subscription(Subscription::new(|| {}))
7879 .context(focus_handle)
7880 .when(run_to_cursor, |this| {
7881 let weak_editor = weak_editor.clone();
7882 this.entry("Run to cursor", None, move |window, cx| {
7883 weak_editor
7884 .update(cx, |editor, cx| {
7885 editor.change_selections(
7886 SelectionEffects::no_scroll(),
7887 window,
7888 cx,
7889 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
7890 );
7891 })
7892 .ok();
7893
7894 window.dispatch_action(Box::new(RunToCursor), cx);
7895 })
7896 .separator()
7897 })
7898 .when_some(toggle_state_msg, |this, msg| {
7899 this.entry(msg, None, {
7900 let weak_editor = weak_editor.clone();
7901 let breakpoint = breakpoint.clone();
7902 move |_window, cx| {
7903 weak_editor
7904 .update(cx, |this, cx| {
7905 this.edit_breakpoint_at_anchor(
7906 anchor,
7907 breakpoint.as_ref().clone(),
7908 BreakpointEditAction::InvertState,
7909 cx,
7910 );
7911 })
7912 .log_err();
7913 }
7914 })
7915 })
7916 .entry(set_breakpoint_msg, None, {
7917 let weak_editor = weak_editor.clone();
7918 let breakpoint = breakpoint.clone();
7919 move |_window, cx| {
7920 weak_editor
7921 .update(cx, |this, cx| {
7922 this.edit_breakpoint_at_anchor(
7923 anchor,
7924 breakpoint.as_ref().clone(),
7925 BreakpointEditAction::Toggle,
7926 cx,
7927 );
7928 })
7929 .log_err();
7930 }
7931 })
7932 .entry(log_breakpoint_msg, None, {
7933 let breakpoint = breakpoint.clone();
7934 let weak_editor = weak_editor.clone();
7935 move |window, cx| {
7936 weak_editor
7937 .update(cx, |this, cx| {
7938 this.add_edit_breakpoint_block(
7939 anchor,
7940 breakpoint.as_ref(),
7941 BreakpointPromptEditAction::Log,
7942 window,
7943 cx,
7944 );
7945 })
7946 .log_err();
7947 }
7948 })
7949 .entry(condition_breakpoint_msg, None, {
7950 let breakpoint = breakpoint.clone();
7951 let weak_editor = weak_editor.clone();
7952 move |window, cx| {
7953 weak_editor
7954 .update(cx, |this, cx| {
7955 this.add_edit_breakpoint_block(
7956 anchor,
7957 breakpoint.as_ref(),
7958 BreakpointPromptEditAction::Condition,
7959 window,
7960 cx,
7961 );
7962 })
7963 .log_err();
7964 }
7965 })
7966 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7967 weak_editor
7968 .update(cx, |this, cx| {
7969 this.add_edit_breakpoint_block(
7970 anchor,
7971 breakpoint.as_ref(),
7972 BreakpointPromptEditAction::HitCondition,
7973 window,
7974 cx,
7975 );
7976 })
7977 .log_err();
7978 })
7979 })
7980 }
7981
7982 fn render_breakpoint(
7983 &self,
7984 position: Anchor,
7985 row: DisplayRow,
7986 breakpoint: &Breakpoint,
7987 state: Option<BreakpointSessionState>,
7988 cx: &mut Context<Self>,
7989 ) -> IconButton {
7990 let is_rejected = state.is_some_and(|s| !s.verified);
7991 // Is it a breakpoint that shows up when hovering over gutter?
7992 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7993 (false, false),
7994 |PhantomBreakpointIndicator {
7995 is_active,
7996 display_row,
7997 collides_with_existing_breakpoint,
7998 }| {
7999 (
8000 is_active && display_row == row,
8001 collides_with_existing_breakpoint,
8002 )
8003 },
8004 );
8005
8006 let (color, icon) = {
8007 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8008 (false, false) => ui::IconName::DebugBreakpoint,
8009 (true, false) => ui::IconName::DebugLogBreakpoint,
8010 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8011 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8012 };
8013
8014 let color = if is_phantom {
8015 Color::Hint
8016 } else if is_rejected {
8017 Color::Disabled
8018 } else {
8019 Color::Debugger
8020 };
8021
8022 (color, icon)
8023 };
8024
8025 let breakpoint = Arc::from(breakpoint.clone());
8026
8027 let alt_as_text = gpui::Keystroke {
8028 modifiers: Modifiers::secondary_key(),
8029 ..Default::default()
8030 };
8031 let primary_action_text = if breakpoint.is_disabled() {
8032 "Enable breakpoint"
8033 } else if is_phantom && !collides_with_existing {
8034 "Set breakpoint"
8035 } else {
8036 "Unset breakpoint"
8037 };
8038 let focus_handle = self.focus_handle.clone();
8039
8040 let meta = if is_rejected {
8041 SharedString::from("No executable code is associated with this line.")
8042 } else if collides_with_existing && !breakpoint.is_disabled() {
8043 SharedString::from(format!(
8044 "{alt_as_text}-click to disable,\nright-click for more options."
8045 ))
8046 } else {
8047 SharedString::from("Right-click for more options.")
8048 };
8049 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8050 .icon_size(IconSize::XSmall)
8051 .size(ui::ButtonSize::None)
8052 .when(is_rejected, |this| {
8053 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8054 })
8055 .icon_color(color)
8056 .style(ButtonStyle::Transparent)
8057 .on_click(cx.listener({
8058 let breakpoint = breakpoint.clone();
8059
8060 move |editor, event: &ClickEvent, window, cx| {
8061 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8062 BreakpointEditAction::InvertState
8063 } else {
8064 BreakpointEditAction::Toggle
8065 };
8066
8067 window.focus(&editor.focus_handle(cx));
8068 editor.edit_breakpoint_at_anchor(
8069 position,
8070 breakpoint.as_ref().clone(),
8071 edit_action,
8072 cx,
8073 );
8074 }
8075 }))
8076 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8077 editor.set_breakpoint_context_menu(
8078 row,
8079 Some(position),
8080 event.down.position,
8081 window,
8082 cx,
8083 );
8084 }))
8085 .tooltip(move |window, cx| {
8086 Tooltip::with_meta_in(
8087 primary_action_text,
8088 Some(&ToggleBreakpoint),
8089 meta.clone(),
8090 &focus_handle,
8091 window,
8092 cx,
8093 )
8094 })
8095 }
8096
8097 fn build_tasks_context(
8098 project: &Entity<Project>,
8099 buffer: &Entity<Buffer>,
8100 buffer_row: u32,
8101 tasks: &Arc<RunnableTasks>,
8102 cx: &mut Context<Self>,
8103 ) -> Task<Option<task::TaskContext>> {
8104 let position = Point::new(buffer_row, tasks.column);
8105 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8106 let location = Location {
8107 buffer: buffer.clone(),
8108 range: range_start..range_start,
8109 };
8110 // Fill in the environmental variables from the tree-sitter captures
8111 let mut captured_task_variables = TaskVariables::default();
8112 for (capture_name, value) in tasks.extra_variables.clone() {
8113 captured_task_variables.insert(
8114 task::VariableName::Custom(capture_name.into()),
8115 value.clone(),
8116 );
8117 }
8118 project.update(cx, |project, cx| {
8119 project.task_store().update(cx, |task_store, cx| {
8120 task_store.task_context_for_location(captured_task_variables, location, cx)
8121 })
8122 })
8123 }
8124
8125 pub fn spawn_nearest_task(
8126 &mut self,
8127 action: &SpawnNearestTask,
8128 window: &mut Window,
8129 cx: &mut Context<Self>,
8130 ) {
8131 let Some((workspace, _)) = self.workspace.clone() else {
8132 return;
8133 };
8134 let Some(project) = self.project.clone() else {
8135 return;
8136 };
8137
8138 // Try to find a closest, enclosing node using tree-sitter that has a
8139 // task
8140 let Some((buffer, buffer_row, tasks)) = self
8141 .find_enclosing_node_task(cx)
8142 // Or find the task that's closest in row-distance.
8143 .or_else(|| self.find_closest_task(cx))
8144 else {
8145 return;
8146 };
8147
8148 let reveal_strategy = action.reveal;
8149 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8150 cx.spawn_in(window, async move |_, cx| {
8151 let context = task_context.await?;
8152 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8153
8154 let resolved = &mut resolved_task.resolved;
8155 resolved.reveal = reveal_strategy;
8156
8157 workspace
8158 .update_in(cx, |workspace, window, cx| {
8159 workspace.schedule_resolved_task(
8160 task_source_kind,
8161 resolved_task,
8162 false,
8163 window,
8164 cx,
8165 );
8166 })
8167 .ok()
8168 })
8169 .detach();
8170 }
8171
8172 fn find_closest_task(
8173 &mut self,
8174 cx: &mut Context<Self>,
8175 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8176 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8177
8178 let ((buffer_id, row), tasks) = self
8179 .tasks
8180 .iter()
8181 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8182
8183 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8184 let tasks = Arc::new(tasks.to_owned());
8185 Some((buffer, *row, tasks))
8186 }
8187
8188 fn find_enclosing_node_task(
8189 &mut self,
8190 cx: &mut Context<Self>,
8191 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8192 let snapshot = self.buffer.read(cx).snapshot(cx);
8193 let offset = self.selections.newest::<usize>(cx).head();
8194 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8195 let buffer_id = excerpt.buffer().remote_id();
8196
8197 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8198 let mut cursor = layer.node().walk();
8199
8200 while cursor.goto_first_child_for_byte(offset).is_some() {
8201 if cursor.node().end_byte() == offset {
8202 cursor.goto_next_sibling();
8203 }
8204 }
8205
8206 // Ascend to the smallest ancestor that contains the range and has a task.
8207 loop {
8208 let node = cursor.node();
8209 let node_range = node.byte_range();
8210 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8211
8212 // Check if this node contains our offset
8213 if node_range.start <= offset && node_range.end >= offset {
8214 // If it contains offset, check for task
8215 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8216 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8217 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8218 }
8219 }
8220
8221 if !cursor.goto_parent() {
8222 break;
8223 }
8224 }
8225 None
8226 }
8227
8228 fn render_run_indicator(
8229 &self,
8230 _style: &EditorStyle,
8231 is_active: bool,
8232 row: DisplayRow,
8233 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8234 cx: &mut Context<Self>,
8235 ) -> IconButton {
8236 let color = Color::Muted;
8237 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8238
8239 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8240 .shape(ui::IconButtonShape::Square)
8241 .icon_size(IconSize::XSmall)
8242 .icon_color(color)
8243 .toggle_state(is_active)
8244 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8245 let quick_launch = e.down.button == MouseButton::Left;
8246 window.focus(&editor.focus_handle(cx));
8247 editor.toggle_code_actions(
8248 &ToggleCodeActions {
8249 deployed_from: Some(CodeActionSource::RunMenu(row)),
8250 quick_launch,
8251 },
8252 window,
8253 cx,
8254 );
8255 }))
8256 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8257 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8258 }))
8259 }
8260
8261 pub fn context_menu_visible(&self) -> bool {
8262 !self.edit_prediction_preview_is_active()
8263 && self
8264 .context_menu
8265 .borrow()
8266 .as_ref()
8267 .map_or(false, |menu| menu.visible())
8268 }
8269
8270 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8271 self.context_menu
8272 .borrow()
8273 .as_ref()
8274 .map(|menu| menu.origin())
8275 }
8276
8277 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8278 self.context_menu_options = Some(options);
8279 }
8280
8281 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8282 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8283
8284 fn render_edit_prediction_popover(
8285 &mut self,
8286 text_bounds: &Bounds<Pixels>,
8287 content_origin: gpui::Point<Pixels>,
8288 right_margin: Pixels,
8289 editor_snapshot: &EditorSnapshot,
8290 visible_row_range: Range<DisplayRow>,
8291 scroll_top: f32,
8292 scroll_bottom: f32,
8293 line_layouts: &[LineWithInvisibles],
8294 line_height: Pixels,
8295 scroll_pixel_position: gpui::Point<Pixels>,
8296 newest_selection_head: Option<DisplayPoint>,
8297 editor_width: Pixels,
8298 style: &EditorStyle,
8299 window: &mut Window,
8300 cx: &mut App,
8301 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8302 if self.mode().is_minimap() {
8303 return None;
8304 }
8305 let active_inline_completion = self.active_inline_completion.as_ref()?;
8306
8307 if self.edit_prediction_visible_in_cursor_popover(true) {
8308 return None;
8309 }
8310
8311 match &active_inline_completion.completion {
8312 InlineCompletion::Move { target, .. } => {
8313 let target_display_point = target.to_display_point(editor_snapshot);
8314
8315 if self.edit_prediction_requires_modifier() {
8316 if !self.edit_prediction_preview_is_active() {
8317 return None;
8318 }
8319
8320 self.render_edit_prediction_modifier_jump_popover(
8321 text_bounds,
8322 content_origin,
8323 visible_row_range,
8324 line_layouts,
8325 line_height,
8326 scroll_pixel_position,
8327 newest_selection_head,
8328 target_display_point,
8329 window,
8330 cx,
8331 )
8332 } else {
8333 self.render_edit_prediction_eager_jump_popover(
8334 text_bounds,
8335 content_origin,
8336 editor_snapshot,
8337 visible_row_range,
8338 scroll_top,
8339 scroll_bottom,
8340 line_height,
8341 scroll_pixel_position,
8342 target_display_point,
8343 editor_width,
8344 window,
8345 cx,
8346 )
8347 }
8348 }
8349 InlineCompletion::Edit {
8350 display_mode: EditDisplayMode::Inline,
8351 ..
8352 } => None,
8353 InlineCompletion::Edit {
8354 display_mode: EditDisplayMode::TabAccept,
8355 edits,
8356 ..
8357 } => {
8358 let range = &edits.first()?.0;
8359 let target_display_point = range.end.to_display_point(editor_snapshot);
8360
8361 self.render_edit_prediction_end_of_line_popover(
8362 "Accept",
8363 editor_snapshot,
8364 visible_row_range,
8365 target_display_point,
8366 line_height,
8367 scroll_pixel_position,
8368 content_origin,
8369 editor_width,
8370 window,
8371 cx,
8372 )
8373 }
8374 InlineCompletion::Edit {
8375 edits,
8376 edit_preview,
8377 display_mode: EditDisplayMode::DiffPopover,
8378 snapshot,
8379 } => self.render_edit_prediction_diff_popover(
8380 text_bounds,
8381 content_origin,
8382 right_margin,
8383 editor_snapshot,
8384 visible_row_range,
8385 line_layouts,
8386 line_height,
8387 scroll_pixel_position,
8388 newest_selection_head,
8389 editor_width,
8390 style,
8391 edits,
8392 edit_preview,
8393 snapshot,
8394 window,
8395 cx,
8396 ),
8397 }
8398 }
8399
8400 fn render_edit_prediction_modifier_jump_popover(
8401 &mut self,
8402 text_bounds: &Bounds<Pixels>,
8403 content_origin: gpui::Point<Pixels>,
8404 visible_row_range: Range<DisplayRow>,
8405 line_layouts: &[LineWithInvisibles],
8406 line_height: Pixels,
8407 scroll_pixel_position: gpui::Point<Pixels>,
8408 newest_selection_head: Option<DisplayPoint>,
8409 target_display_point: DisplayPoint,
8410 window: &mut Window,
8411 cx: &mut App,
8412 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8413 let scrolled_content_origin =
8414 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8415
8416 const SCROLL_PADDING_Y: Pixels = px(12.);
8417
8418 if target_display_point.row() < visible_row_range.start {
8419 return self.render_edit_prediction_scroll_popover(
8420 |_| SCROLL_PADDING_Y,
8421 IconName::ArrowUp,
8422 visible_row_range,
8423 line_layouts,
8424 newest_selection_head,
8425 scrolled_content_origin,
8426 window,
8427 cx,
8428 );
8429 } else if target_display_point.row() >= visible_row_range.end {
8430 return self.render_edit_prediction_scroll_popover(
8431 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8432 IconName::ArrowDown,
8433 visible_row_range,
8434 line_layouts,
8435 newest_selection_head,
8436 scrolled_content_origin,
8437 window,
8438 cx,
8439 );
8440 }
8441
8442 const POLE_WIDTH: Pixels = px(2.);
8443
8444 let line_layout =
8445 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8446 let target_column = target_display_point.column() as usize;
8447
8448 let target_x = line_layout.x_for_index(target_column);
8449 let target_y =
8450 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8451
8452 let flag_on_right = target_x < text_bounds.size.width / 2.;
8453
8454 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8455 border_color.l += 0.001;
8456
8457 let mut element = v_flex()
8458 .items_end()
8459 .when(flag_on_right, |el| el.items_start())
8460 .child(if flag_on_right {
8461 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8462 .rounded_bl(px(0.))
8463 .rounded_tl(px(0.))
8464 .border_l_2()
8465 .border_color(border_color)
8466 } else {
8467 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8468 .rounded_br(px(0.))
8469 .rounded_tr(px(0.))
8470 .border_r_2()
8471 .border_color(border_color)
8472 })
8473 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8474 .into_any();
8475
8476 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8477
8478 let mut origin = scrolled_content_origin + point(target_x, target_y)
8479 - point(
8480 if flag_on_right {
8481 POLE_WIDTH
8482 } else {
8483 size.width - POLE_WIDTH
8484 },
8485 size.height - line_height,
8486 );
8487
8488 origin.x = origin.x.max(content_origin.x);
8489
8490 element.prepaint_at(origin, window, cx);
8491
8492 Some((element, origin))
8493 }
8494
8495 fn render_edit_prediction_scroll_popover(
8496 &mut self,
8497 to_y: impl Fn(Size<Pixels>) -> Pixels,
8498 scroll_icon: IconName,
8499 visible_row_range: Range<DisplayRow>,
8500 line_layouts: &[LineWithInvisibles],
8501 newest_selection_head: Option<DisplayPoint>,
8502 scrolled_content_origin: gpui::Point<Pixels>,
8503 window: &mut Window,
8504 cx: &mut App,
8505 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8506 let mut element = self
8507 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8508 .into_any();
8509
8510 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8511
8512 let cursor = newest_selection_head?;
8513 let cursor_row_layout =
8514 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8515 let cursor_column = cursor.column() as usize;
8516
8517 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8518
8519 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8520
8521 element.prepaint_at(origin, window, cx);
8522 Some((element, origin))
8523 }
8524
8525 fn render_edit_prediction_eager_jump_popover(
8526 &mut self,
8527 text_bounds: &Bounds<Pixels>,
8528 content_origin: gpui::Point<Pixels>,
8529 editor_snapshot: &EditorSnapshot,
8530 visible_row_range: Range<DisplayRow>,
8531 scroll_top: f32,
8532 scroll_bottom: f32,
8533 line_height: Pixels,
8534 scroll_pixel_position: gpui::Point<Pixels>,
8535 target_display_point: DisplayPoint,
8536 editor_width: Pixels,
8537 window: &mut Window,
8538 cx: &mut App,
8539 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8540 if target_display_point.row().as_f32() < scroll_top {
8541 let mut element = self
8542 .render_edit_prediction_line_popover(
8543 "Jump to Edit",
8544 Some(IconName::ArrowUp),
8545 window,
8546 cx,
8547 )?
8548 .into_any();
8549
8550 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8551 let offset = point(
8552 (text_bounds.size.width - size.width) / 2.,
8553 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8554 );
8555
8556 let origin = text_bounds.origin + offset;
8557 element.prepaint_at(origin, window, cx);
8558 Some((element, origin))
8559 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8560 let mut element = self
8561 .render_edit_prediction_line_popover(
8562 "Jump to Edit",
8563 Some(IconName::ArrowDown),
8564 window,
8565 cx,
8566 )?
8567 .into_any();
8568
8569 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8570 let offset = point(
8571 (text_bounds.size.width - size.width) / 2.,
8572 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8573 );
8574
8575 let origin = text_bounds.origin + offset;
8576 element.prepaint_at(origin, window, cx);
8577 Some((element, origin))
8578 } else {
8579 self.render_edit_prediction_end_of_line_popover(
8580 "Jump to Edit",
8581 editor_snapshot,
8582 visible_row_range,
8583 target_display_point,
8584 line_height,
8585 scroll_pixel_position,
8586 content_origin,
8587 editor_width,
8588 window,
8589 cx,
8590 )
8591 }
8592 }
8593
8594 fn render_edit_prediction_end_of_line_popover(
8595 self: &mut Editor,
8596 label: &'static str,
8597 editor_snapshot: &EditorSnapshot,
8598 visible_row_range: Range<DisplayRow>,
8599 target_display_point: DisplayPoint,
8600 line_height: Pixels,
8601 scroll_pixel_position: gpui::Point<Pixels>,
8602 content_origin: gpui::Point<Pixels>,
8603 editor_width: Pixels,
8604 window: &mut Window,
8605 cx: &mut App,
8606 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8607 let target_line_end = DisplayPoint::new(
8608 target_display_point.row(),
8609 editor_snapshot.line_len(target_display_point.row()),
8610 );
8611
8612 let mut element = self
8613 .render_edit_prediction_line_popover(label, None, window, cx)?
8614 .into_any();
8615
8616 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8617
8618 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8619
8620 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8621 let mut origin = start_point
8622 + line_origin
8623 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8624 origin.x = origin.x.max(content_origin.x);
8625
8626 let max_x = content_origin.x + editor_width - size.width;
8627
8628 if origin.x > max_x {
8629 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8630
8631 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8632 origin.y += offset;
8633 IconName::ArrowUp
8634 } else {
8635 origin.y -= offset;
8636 IconName::ArrowDown
8637 };
8638
8639 element = self
8640 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8641 .into_any();
8642
8643 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8644
8645 origin.x = content_origin.x + editor_width - size.width - px(2.);
8646 }
8647
8648 element.prepaint_at(origin, window, cx);
8649 Some((element, origin))
8650 }
8651
8652 fn render_edit_prediction_diff_popover(
8653 self: &Editor,
8654 text_bounds: &Bounds<Pixels>,
8655 content_origin: gpui::Point<Pixels>,
8656 right_margin: Pixels,
8657 editor_snapshot: &EditorSnapshot,
8658 visible_row_range: Range<DisplayRow>,
8659 line_layouts: &[LineWithInvisibles],
8660 line_height: Pixels,
8661 scroll_pixel_position: gpui::Point<Pixels>,
8662 newest_selection_head: Option<DisplayPoint>,
8663 editor_width: Pixels,
8664 style: &EditorStyle,
8665 edits: &Vec<(Range<Anchor>, String)>,
8666 edit_preview: &Option<language::EditPreview>,
8667 snapshot: &language::BufferSnapshot,
8668 window: &mut Window,
8669 cx: &mut App,
8670 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8671 let edit_start = edits
8672 .first()
8673 .unwrap()
8674 .0
8675 .start
8676 .to_display_point(editor_snapshot);
8677 let edit_end = edits
8678 .last()
8679 .unwrap()
8680 .0
8681 .end
8682 .to_display_point(editor_snapshot);
8683
8684 let is_visible = visible_row_range.contains(&edit_start.row())
8685 || visible_row_range.contains(&edit_end.row());
8686 if !is_visible {
8687 return None;
8688 }
8689
8690 let highlighted_edits =
8691 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8692
8693 let styled_text = highlighted_edits.to_styled_text(&style.text);
8694 let line_count = highlighted_edits.text.lines().count();
8695
8696 const BORDER_WIDTH: Pixels = px(1.);
8697
8698 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8699 let has_keybind = keybind.is_some();
8700
8701 let mut element = h_flex()
8702 .items_start()
8703 .child(
8704 h_flex()
8705 .bg(cx.theme().colors().editor_background)
8706 .border(BORDER_WIDTH)
8707 .shadow_sm()
8708 .border_color(cx.theme().colors().border)
8709 .rounded_l_lg()
8710 .when(line_count > 1, |el| el.rounded_br_lg())
8711 .pr_1()
8712 .child(styled_text),
8713 )
8714 .child(
8715 h_flex()
8716 .h(line_height + BORDER_WIDTH * 2.)
8717 .px_1p5()
8718 .gap_1()
8719 // Workaround: For some reason, there's a gap if we don't do this
8720 .ml(-BORDER_WIDTH)
8721 .shadow(vec![gpui::BoxShadow {
8722 color: gpui::black().opacity(0.05),
8723 offset: point(px(1.), px(1.)),
8724 blur_radius: px(2.),
8725 spread_radius: px(0.),
8726 }])
8727 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8728 .border(BORDER_WIDTH)
8729 .border_color(cx.theme().colors().border)
8730 .rounded_r_lg()
8731 .id("edit_prediction_diff_popover_keybind")
8732 .when(!has_keybind, |el| {
8733 let status_colors = cx.theme().status();
8734
8735 el.bg(status_colors.error_background)
8736 .border_color(status_colors.error.opacity(0.6))
8737 .child(Icon::new(IconName::Info).color(Color::Error))
8738 .cursor_default()
8739 .hoverable_tooltip(move |_window, cx| {
8740 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8741 })
8742 })
8743 .children(keybind),
8744 )
8745 .into_any();
8746
8747 let longest_row =
8748 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8749 let longest_line_width = if visible_row_range.contains(&longest_row) {
8750 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8751 } else {
8752 layout_line(
8753 longest_row,
8754 editor_snapshot,
8755 style,
8756 editor_width,
8757 |_| false,
8758 window,
8759 cx,
8760 )
8761 .width
8762 };
8763
8764 let viewport_bounds =
8765 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8766 right: -right_margin,
8767 ..Default::default()
8768 });
8769
8770 let x_after_longest =
8771 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8772 - scroll_pixel_position.x;
8773
8774 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8775
8776 // Fully visible if it can be displayed within the window (allow overlapping other
8777 // panes). However, this is only allowed if the popover starts within text_bounds.
8778 let can_position_to_the_right = x_after_longest < text_bounds.right()
8779 && x_after_longest + element_bounds.width < viewport_bounds.right();
8780
8781 let mut origin = if can_position_to_the_right {
8782 point(
8783 x_after_longest,
8784 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8785 - scroll_pixel_position.y,
8786 )
8787 } else {
8788 let cursor_row = newest_selection_head.map(|head| head.row());
8789 let above_edit = edit_start
8790 .row()
8791 .0
8792 .checked_sub(line_count as u32)
8793 .map(DisplayRow);
8794 let below_edit = Some(edit_end.row() + 1);
8795 let above_cursor =
8796 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8797 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8798
8799 // Place the edit popover adjacent to the edit if there is a location
8800 // available that is onscreen and does not obscure the cursor. Otherwise,
8801 // place it adjacent to the cursor.
8802 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8803 .into_iter()
8804 .flatten()
8805 .find(|&start_row| {
8806 let end_row = start_row + line_count as u32;
8807 visible_row_range.contains(&start_row)
8808 && visible_row_range.contains(&end_row)
8809 && cursor_row.map_or(true, |cursor_row| {
8810 !((start_row..end_row).contains(&cursor_row))
8811 })
8812 })?;
8813
8814 content_origin
8815 + point(
8816 -scroll_pixel_position.x,
8817 row_target.as_f32() * line_height - scroll_pixel_position.y,
8818 )
8819 };
8820
8821 origin.x -= BORDER_WIDTH;
8822
8823 window.defer_draw(element, origin, 1);
8824
8825 // Do not return an element, since it will already be drawn due to defer_draw.
8826 None
8827 }
8828
8829 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8830 px(30.)
8831 }
8832
8833 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8834 if self.read_only(cx) {
8835 cx.theme().players().read_only()
8836 } else {
8837 self.style.as_ref().unwrap().local_player
8838 }
8839 }
8840
8841 fn render_edit_prediction_accept_keybind(
8842 &self,
8843 window: &mut Window,
8844 cx: &App,
8845 ) -> Option<AnyElement> {
8846 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8847 let accept_keystroke = accept_binding.keystroke()?;
8848
8849 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8850
8851 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8852 Color::Accent
8853 } else {
8854 Color::Muted
8855 };
8856
8857 h_flex()
8858 .px_0p5()
8859 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8860 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8861 .text_size(TextSize::XSmall.rems(cx))
8862 .child(h_flex().children(ui::render_modifiers(
8863 &accept_keystroke.modifiers,
8864 PlatformStyle::platform(),
8865 Some(modifiers_color),
8866 Some(IconSize::XSmall.rems().into()),
8867 true,
8868 )))
8869 .when(is_platform_style_mac, |parent| {
8870 parent.child(accept_keystroke.key.clone())
8871 })
8872 .when(!is_platform_style_mac, |parent| {
8873 parent.child(
8874 Key::new(
8875 util::capitalize(&accept_keystroke.key),
8876 Some(Color::Default),
8877 )
8878 .size(Some(IconSize::XSmall.rems().into())),
8879 )
8880 })
8881 .into_any()
8882 .into()
8883 }
8884
8885 fn render_edit_prediction_line_popover(
8886 &self,
8887 label: impl Into<SharedString>,
8888 icon: Option<IconName>,
8889 window: &mut Window,
8890 cx: &App,
8891 ) -> Option<Stateful<Div>> {
8892 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8893
8894 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8895 let has_keybind = keybind.is_some();
8896
8897 let result = h_flex()
8898 .id("ep-line-popover")
8899 .py_0p5()
8900 .pl_1()
8901 .pr(padding_right)
8902 .gap_1()
8903 .rounded_md()
8904 .border_1()
8905 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8906 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8907 .shadow_sm()
8908 .when(!has_keybind, |el| {
8909 let status_colors = cx.theme().status();
8910
8911 el.bg(status_colors.error_background)
8912 .border_color(status_colors.error.opacity(0.6))
8913 .pl_2()
8914 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8915 .cursor_default()
8916 .hoverable_tooltip(move |_window, cx| {
8917 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8918 })
8919 })
8920 .children(keybind)
8921 .child(
8922 Label::new(label)
8923 .size(LabelSize::Small)
8924 .when(!has_keybind, |el| {
8925 el.color(cx.theme().status().error.into()).strikethrough()
8926 }),
8927 )
8928 .when(!has_keybind, |el| {
8929 el.child(
8930 h_flex().ml_1().child(
8931 Icon::new(IconName::Info)
8932 .size(IconSize::Small)
8933 .color(cx.theme().status().error.into()),
8934 ),
8935 )
8936 })
8937 .when_some(icon, |element, icon| {
8938 element.child(
8939 div()
8940 .mt(px(1.5))
8941 .child(Icon::new(icon).size(IconSize::Small)),
8942 )
8943 });
8944
8945 Some(result)
8946 }
8947
8948 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8949 let accent_color = cx.theme().colors().text_accent;
8950 let editor_bg_color = cx.theme().colors().editor_background;
8951 editor_bg_color.blend(accent_color.opacity(0.1))
8952 }
8953
8954 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8955 let accent_color = cx.theme().colors().text_accent;
8956 let editor_bg_color = cx.theme().colors().editor_background;
8957 editor_bg_color.blend(accent_color.opacity(0.6))
8958 }
8959
8960 fn render_edit_prediction_cursor_popover(
8961 &self,
8962 min_width: Pixels,
8963 max_width: Pixels,
8964 cursor_point: Point,
8965 style: &EditorStyle,
8966 accept_keystroke: Option<&gpui::Keystroke>,
8967 _window: &Window,
8968 cx: &mut Context<Editor>,
8969 ) -> Option<AnyElement> {
8970 let provider = self.edit_prediction_provider.as_ref()?;
8971
8972 if provider.provider.needs_terms_acceptance(cx) {
8973 return Some(
8974 h_flex()
8975 .min_w(min_width)
8976 .flex_1()
8977 .px_2()
8978 .py_1()
8979 .gap_3()
8980 .elevation_2(cx)
8981 .hover(|style| style.bg(cx.theme().colors().element_hover))
8982 .id("accept-terms")
8983 .cursor_pointer()
8984 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8985 .on_click(cx.listener(|this, _event, window, cx| {
8986 cx.stop_propagation();
8987 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8988 window.dispatch_action(
8989 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8990 cx,
8991 );
8992 }))
8993 .child(
8994 h_flex()
8995 .flex_1()
8996 .gap_2()
8997 .child(Icon::new(IconName::ZedPredict))
8998 .child(Label::new("Accept Terms of Service"))
8999 .child(div().w_full())
9000 .child(
9001 Icon::new(IconName::ArrowUpRight)
9002 .color(Color::Muted)
9003 .size(IconSize::Small),
9004 )
9005 .into_any_element(),
9006 )
9007 .into_any(),
9008 );
9009 }
9010
9011 let is_refreshing = provider.provider.is_refreshing(cx);
9012
9013 fn pending_completion_container() -> Div {
9014 h_flex()
9015 .h_full()
9016 .flex_1()
9017 .gap_2()
9018 .child(Icon::new(IconName::ZedPredict))
9019 }
9020
9021 let completion = match &self.active_inline_completion {
9022 Some(prediction) => {
9023 if !self.has_visible_completions_menu() {
9024 const RADIUS: Pixels = px(6.);
9025 const BORDER_WIDTH: Pixels = px(1.);
9026
9027 return Some(
9028 h_flex()
9029 .elevation_2(cx)
9030 .border(BORDER_WIDTH)
9031 .border_color(cx.theme().colors().border)
9032 .when(accept_keystroke.is_none(), |el| {
9033 el.border_color(cx.theme().status().error)
9034 })
9035 .rounded(RADIUS)
9036 .rounded_tl(px(0.))
9037 .overflow_hidden()
9038 .child(div().px_1p5().child(match &prediction.completion {
9039 InlineCompletion::Move { target, snapshot } => {
9040 use text::ToPoint as _;
9041 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
9042 {
9043 Icon::new(IconName::ZedPredictDown)
9044 } else {
9045 Icon::new(IconName::ZedPredictUp)
9046 }
9047 }
9048 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
9049 }))
9050 .child(
9051 h_flex()
9052 .gap_1()
9053 .py_1()
9054 .px_2()
9055 .rounded_r(RADIUS - BORDER_WIDTH)
9056 .border_l_1()
9057 .border_color(cx.theme().colors().border)
9058 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9059 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9060 el.child(
9061 Label::new("Hold")
9062 .size(LabelSize::Small)
9063 .when(accept_keystroke.is_none(), |el| {
9064 el.strikethrough()
9065 })
9066 .line_height_style(LineHeightStyle::UiLabel),
9067 )
9068 })
9069 .id("edit_prediction_cursor_popover_keybind")
9070 .when(accept_keystroke.is_none(), |el| {
9071 let status_colors = cx.theme().status();
9072
9073 el.bg(status_colors.error_background)
9074 .border_color(status_colors.error.opacity(0.6))
9075 .child(Icon::new(IconName::Info).color(Color::Error))
9076 .cursor_default()
9077 .hoverable_tooltip(move |_window, cx| {
9078 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9079 .into()
9080 })
9081 })
9082 .when_some(
9083 accept_keystroke.as_ref(),
9084 |el, accept_keystroke| {
9085 el.child(h_flex().children(ui::render_modifiers(
9086 &accept_keystroke.modifiers,
9087 PlatformStyle::platform(),
9088 Some(Color::Default),
9089 Some(IconSize::XSmall.rems().into()),
9090 false,
9091 )))
9092 },
9093 ),
9094 )
9095 .into_any(),
9096 );
9097 }
9098
9099 self.render_edit_prediction_cursor_popover_preview(
9100 prediction,
9101 cursor_point,
9102 style,
9103 cx,
9104 )?
9105 }
9106
9107 None if is_refreshing => match &self.stale_inline_completion_in_menu {
9108 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9109 stale_completion,
9110 cursor_point,
9111 style,
9112 cx,
9113 )?,
9114
9115 None => {
9116 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
9117 }
9118 },
9119
9120 None => pending_completion_container().child(Label::new("No Prediction")),
9121 };
9122
9123 let completion = if is_refreshing {
9124 completion
9125 .with_animation(
9126 "loading-completion",
9127 Animation::new(Duration::from_secs(2))
9128 .repeat()
9129 .with_easing(pulsating_between(0.4, 0.8)),
9130 |label, delta| label.opacity(delta),
9131 )
9132 .into_any_element()
9133 } else {
9134 completion.into_any_element()
9135 };
9136
9137 let has_completion = self.active_inline_completion.is_some();
9138
9139 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9140 Some(
9141 h_flex()
9142 .min_w(min_width)
9143 .max_w(max_width)
9144 .flex_1()
9145 .elevation_2(cx)
9146 .border_color(cx.theme().colors().border)
9147 .child(
9148 div()
9149 .flex_1()
9150 .py_1()
9151 .px_2()
9152 .overflow_hidden()
9153 .child(completion),
9154 )
9155 .when_some(accept_keystroke, |el, accept_keystroke| {
9156 if !accept_keystroke.modifiers.modified() {
9157 return el;
9158 }
9159
9160 el.child(
9161 h_flex()
9162 .h_full()
9163 .border_l_1()
9164 .rounded_r_lg()
9165 .border_color(cx.theme().colors().border)
9166 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9167 .gap_1()
9168 .py_1()
9169 .px_2()
9170 .child(
9171 h_flex()
9172 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9173 .when(is_platform_style_mac, |parent| parent.gap_1())
9174 .child(h_flex().children(ui::render_modifiers(
9175 &accept_keystroke.modifiers,
9176 PlatformStyle::platform(),
9177 Some(if !has_completion {
9178 Color::Muted
9179 } else {
9180 Color::Default
9181 }),
9182 None,
9183 false,
9184 ))),
9185 )
9186 .child(Label::new("Preview").into_any_element())
9187 .opacity(if has_completion { 1.0 } else { 0.4 }),
9188 )
9189 })
9190 .into_any(),
9191 )
9192 }
9193
9194 fn render_edit_prediction_cursor_popover_preview(
9195 &self,
9196 completion: &InlineCompletionState,
9197 cursor_point: Point,
9198 style: &EditorStyle,
9199 cx: &mut Context<Editor>,
9200 ) -> Option<Div> {
9201 use text::ToPoint as _;
9202
9203 fn render_relative_row_jump(
9204 prefix: impl Into<String>,
9205 current_row: u32,
9206 target_row: u32,
9207 ) -> Div {
9208 let (row_diff, arrow) = if target_row < current_row {
9209 (current_row - target_row, IconName::ArrowUp)
9210 } else {
9211 (target_row - current_row, IconName::ArrowDown)
9212 };
9213
9214 h_flex()
9215 .child(
9216 Label::new(format!("{}{}", prefix.into(), row_diff))
9217 .color(Color::Muted)
9218 .size(LabelSize::Small),
9219 )
9220 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9221 }
9222
9223 match &completion.completion {
9224 InlineCompletion::Move {
9225 target, snapshot, ..
9226 } => Some(
9227 h_flex()
9228 .px_2()
9229 .gap_2()
9230 .flex_1()
9231 .child(
9232 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9233 Icon::new(IconName::ZedPredictDown)
9234 } else {
9235 Icon::new(IconName::ZedPredictUp)
9236 },
9237 )
9238 .child(Label::new("Jump to Edit")),
9239 ),
9240
9241 InlineCompletion::Edit {
9242 edits,
9243 edit_preview,
9244 snapshot,
9245 display_mode: _,
9246 } => {
9247 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9248
9249 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9250 &snapshot,
9251 &edits,
9252 edit_preview.as_ref()?,
9253 true,
9254 cx,
9255 )
9256 .first_line_preview();
9257
9258 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9259 .with_default_highlights(&style.text, highlighted_edits.highlights);
9260
9261 let preview = h_flex()
9262 .gap_1()
9263 .min_w_16()
9264 .child(styled_text)
9265 .when(has_more_lines, |parent| parent.child("…"));
9266
9267 let left = if first_edit_row != cursor_point.row {
9268 render_relative_row_jump("", cursor_point.row, first_edit_row)
9269 .into_any_element()
9270 } else {
9271 Icon::new(IconName::ZedPredict).into_any_element()
9272 };
9273
9274 Some(
9275 h_flex()
9276 .h_full()
9277 .flex_1()
9278 .gap_2()
9279 .pr_1()
9280 .overflow_x_hidden()
9281 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9282 .child(left)
9283 .child(preview),
9284 )
9285 }
9286 }
9287 }
9288
9289 pub fn render_context_menu(
9290 &self,
9291 style: &EditorStyle,
9292 max_height_in_lines: u32,
9293 window: &mut Window,
9294 cx: &mut Context<Editor>,
9295 ) -> Option<AnyElement> {
9296 let menu = self.context_menu.borrow();
9297 let menu = menu.as_ref()?;
9298 if !menu.visible() {
9299 return None;
9300 };
9301 Some(menu.render(style, max_height_in_lines, window, cx))
9302 }
9303
9304 fn render_context_menu_aside(
9305 &mut self,
9306 max_size: Size<Pixels>,
9307 window: &mut Window,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<AnyElement> {
9310 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9311 if menu.visible() {
9312 menu.render_aside(max_size, window, cx)
9313 } else {
9314 None
9315 }
9316 })
9317 }
9318
9319 fn hide_context_menu(
9320 &mut self,
9321 window: &mut Window,
9322 cx: &mut Context<Self>,
9323 ) -> Option<CodeContextMenu> {
9324 cx.notify();
9325 self.completion_tasks.clear();
9326 let context_menu = self.context_menu.borrow_mut().take();
9327 self.stale_inline_completion_in_menu.take();
9328 self.update_visible_inline_completion(window, cx);
9329 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9330 if let Some(completion_provider) = &self.completion_provider {
9331 completion_provider.selection_changed(None, window, cx);
9332 }
9333 }
9334 context_menu
9335 }
9336
9337 fn show_snippet_choices(
9338 &mut self,
9339 choices: &Vec<String>,
9340 selection: Range<Anchor>,
9341 cx: &mut Context<Self>,
9342 ) {
9343 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9344 (Some(a), Some(b)) if a == b => a,
9345 _ => {
9346 log::error!("expected anchor range to have matching buffer IDs");
9347 return;
9348 }
9349 };
9350 let multi_buffer = self.buffer().read(cx);
9351 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9352 return;
9353 };
9354
9355 let id = post_inc(&mut self.next_completion_id);
9356 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9357 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9358 CompletionsMenu::new_snippet_choices(
9359 id,
9360 true,
9361 choices,
9362 selection,
9363 buffer,
9364 snippet_sort_order,
9365 ),
9366 ));
9367 }
9368
9369 pub fn insert_snippet(
9370 &mut self,
9371 insertion_ranges: &[Range<usize>],
9372 snippet: Snippet,
9373 window: &mut Window,
9374 cx: &mut Context<Self>,
9375 ) -> Result<()> {
9376 struct Tabstop<T> {
9377 is_end_tabstop: bool,
9378 ranges: Vec<Range<T>>,
9379 choices: Option<Vec<String>>,
9380 }
9381
9382 let tabstops = self.buffer.update(cx, |buffer, cx| {
9383 let snippet_text: Arc<str> = snippet.text.clone().into();
9384 let edits = insertion_ranges
9385 .iter()
9386 .cloned()
9387 .map(|range| (range, snippet_text.clone()));
9388 let autoindent_mode = AutoindentMode::Block {
9389 original_indent_columns: Vec::new(),
9390 };
9391 buffer.edit(edits, Some(autoindent_mode), cx);
9392
9393 let snapshot = &*buffer.read(cx);
9394 let snippet = &snippet;
9395 snippet
9396 .tabstops
9397 .iter()
9398 .map(|tabstop| {
9399 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9400 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9401 });
9402 let mut tabstop_ranges = tabstop
9403 .ranges
9404 .iter()
9405 .flat_map(|tabstop_range| {
9406 let mut delta = 0_isize;
9407 insertion_ranges.iter().map(move |insertion_range| {
9408 let insertion_start = insertion_range.start as isize + delta;
9409 delta +=
9410 snippet.text.len() as isize - insertion_range.len() as isize;
9411
9412 let start = ((insertion_start + tabstop_range.start) as usize)
9413 .min(snapshot.len());
9414 let end = ((insertion_start + tabstop_range.end) as usize)
9415 .min(snapshot.len());
9416 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9417 })
9418 })
9419 .collect::<Vec<_>>();
9420 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9421
9422 Tabstop {
9423 is_end_tabstop,
9424 ranges: tabstop_ranges,
9425 choices: tabstop.choices.clone(),
9426 }
9427 })
9428 .collect::<Vec<_>>()
9429 });
9430 if let Some(tabstop) = tabstops.first() {
9431 self.change_selections(Default::default(), window, cx, |s| {
9432 // Reverse order so that the first range is the newest created selection.
9433 // Completions will use it and autoscroll will prioritize it.
9434 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9435 });
9436
9437 if let Some(choices) = &tabstop.choices {
9438 if let Some(selection) = tabstop.ranges.first() {
9439 self.show_snippet_choices(choices, selection.clone(), cx)
9440 }
9441 }
9442
9443 // If we're already at the last tabstop and it's at the end of the snippet,
9444 // we're done, we don't need to keep the state around.
9445 if !tabstop.is_end_tabstop {
9446 let choices = tabstops
9447 .iter()
9448 .map(|tabstop| tabstop.choices.clone())
9449 .collect();
9450
9451 let ranges = tabstops
9452 .into_iter()
9453 .map(|tabstop| tabstop.ranges)
9454 .collect::<Vec<_>>();
9455
9456 self.snippet_stack.push(SnippetState {
9457 active_index: 0,
9458 ranges,
9459 choices,
9460 });
9461 }
9462
9463 // Check whether the just-entered snippet ends with an auto-closable bracket.
9464 if self.autoclose_regions.is_empty() {
9465 let snapshot = self.buffer.read(cx).snapshot(cx);
9466 for selection in &mut self.selections.all::<Point>(cx) {
9467 let selection_head = selection.head();
9468 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9469 continue;
9470 };
9471
9472 let mut bracket_pair = None;
9473 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9474 let prev_chars = snapshot
9475 .reversed_chars_at(selection_head)
9476 .collect::<String>();
9477 for (pair, enabled) in scope.brackets() {
9478 if enabled
9479 && pair.close
9480 && prev_chars.starts_with(pair.start.as_str())
9481 && next_chars.starts_with(pair.end.as_str())
9482 {
9483 bracket_pair = Some(pair.clone());
9484 break;
9485 }
9486 }
9487 if let Some(pair) = bracket_pair {
9488 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9489 let autoclose_enabled =
9490 self.use_autoclose && snapshot_settings.use_autoclose;
9491 if autoclose_enabled {
9492 let start = snapshot.anchor_after(selection_head);
9493 let end = snapshot.anchor_after(selection_head);
9494 self.autoclose_regions.push(AutocloseRegion {
9495 selection_id: selection.id,
9496 range: start..end,
9497 pair,
9498 });
9499 }
9500 }
9501 }
9502 }
9503 }
9504 Ok(())
9505 }
9506
9507 pub fn move_to_next_snippet_tabstop(
9508 &mut self,
9509 window: &mut Window,
9510 cx: &mut Context<Self>,
9511 ) -> bool {
9512 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9513 }
9514
9515 pub fn move_to_prev_snippet_tabstop(
9516 &mut self,
9517 window: &mut Window,
9518 cx: &mut Context<Self>,
9519 ) -> bool {
9520 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9521 }
9522
9523 pub fn move_to_snippet_tabstop(
9524 &mut self,
9525 bias: Bias,
9526 window: &mut Window,
9527 cx: &mut Context<Self>,
9528 ) -> bool {
9529 if let Some(mut snippet) = self.snippet_stack.pop() {
9530 match bias {
9531 Bias::Left => {
9532 if snippet.active_index > 0 {
9533 snippet.active_index -= 1;
9534 } else {
9535 self.snippet_stack.push(snippet);
9536 return false;
9537 }
9538 }
9539 Bias::Right => {
9540 if snippet.active_index + 1 < snippet.ranges.len() {
9541 snippet.active_index += 1;
9542 } else {
9543 self.snippet_stack.push(snippet);
9544 return false;
9545 }
9546 }
9547 }
9548 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9549 self.change_selections(Default::default(), window, cx, |s| {
9550 // Reverse order so that the first range is the newest created selection.
9551 // Completions will use it and autoscroll will prioritize it.
9552 s.select_ranges(current_ranges.iter().rev().cloned())
9553 });
9554
9555 if let Some(choices) = &snippet.choices[snippet.active_index] {
9556 if let Some(selection) = current_ranges.first() {
9557 self.show_snippet_choices(&choices, selection.clone(), cx);
9558 }
9559 }
9560
9561 // If snippet state is not at the last tabstop, push it back on the stack
9562 if snippet.active_index + 1 < snippet.ranges.len() {
9563 self.snippet_stack.push(snippet);
9564 }
9565 return true;
9566 }
9567 }
9568
9569 false
9570 }
9571
9572 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9573 self.transact(window, cx, |this, window, cx| {
9574 this.select_all(&SelectAll, window, cx);
9575 this.insert("", window, cx);
9576 });
9577 }
9578
9579 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9580 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9581 self.transact(window, cx, |this, window, cx| {
9582 this.select_autoclose_pair(window, cx);
9583 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9584 if !this.linked_edit_ranges.is_empty() {
9585 let selections = this.selections.all::<MultiBufferPoint>(cx);
9586 let snapshot = this.buffer.read(cx).snapshot(cx);
9587
9588 for selection in selections.iter() {
9589 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9590 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9591 if selection_start.buffer_id != selection_end.buffer_id {
9592 continue;
9593 }
9594 if let Some(ranges) =
9595 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9596 {
9597 for (buffer, entries) in ranges {
9598 linked_ranges.entry(buffer).or_default().extend(entries);
9599 }
9600 }
9601 }
9602 }
9603
9604 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9605 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9606 for selection in &mut selections {
9607 if selection.is_empty() {
9608 let old_head = selection.head();
9609 let mut new_head =
9610 movement::left(&display_map, old_head.to_display_point(&display_map))
9611 .to_point(&display_map);
9612 if let Some((buffer, line_buffer_range)) = display_map
9613 .buffer_snapshot
9614 .buffer_line_for_row(MultiBufferRow(old_head.row))
9615 {
9616 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9617 let indent_len = match indent_size.kind {
9618 IndentKind::Space => {
9619 buffer.settings_at(line_buffer_range.start, cx).tab_size
9620 }
9621 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9622 };
9623 if old_head.column <= indent_size.len && old_head.column > 0 {
9624 let indent_len = indent_len.get();
9625 new_head = cmp::min(
9626 new_head,
9627 MultiBufferPoint::new(
9628 old_head.row,
9629 ((old_head.column - 1) / indent_len) * indent_len,
9630 ),
9631 );
9632 }
9633 }
9634
9635 selection.set_head(new_head, SelectionGoal::None);
9636 }
9637 }
9638
9639 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9640 this.insert("", window, cx);
9641 let empty_str: Arc<str> = Arc::from("");
9642 for (buffer, edits) in linked_ranges {
9643 let snapshot = buffer.read(cx).snapshot();
9644 use text::ToPoint as TP;
9645
9646 let edits = edits
9647 .into_iter()
9648 .map(|range| {
9649 let end_point = TP::to_point(&range.end, &snapshot);
9650 let mut start_point = TP::to_point(&range.start, &snapshot);
9651
9652 if end_point == start_point {
9653 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9654 .saturating_sub(1);
9655 start_point =
9656 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9657 };
9658
9659 (start_point..end_point, empty_str.clone())
9660 })
9661 .sorted_by_key(|(range, _)| range.start)
9662 .collect::<Vec<_>>();
9663 buffer.update(cx, |this, cx| {
9664 this.edit(edits, None, cx);
9665 })
9666 }
9667 this.refresh_inline_completion(true, false, window, cx);
9668 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9669 });
9670 }
9671
9672 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9674 self.transact(window, cx, |this, window, cx| {
9675 this.change_selections(Default::default(), window, cx, |s| {
9676 s.move_with(|map, selection| {
9677 if selection.is_empty() {
9678 let cursor = movement::right(map, selection.head());
9679 selection.end = cursor;
9680 selection.reversed = true;
9681 selection.goal = SelectionGoal::None;
9682 }
9683 })
9684 });
9685 this.insert("", window, cx);
9686 this.refresh_inline_completion(true, false, window, cx);
9687 });
9688 }
9689
9690 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9691 if self.mode.is_single_line() {
9692 cx.propagate();
9693 return;
9694 }
9695
9696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9697 if self.move_to_prev_snippet_tabstop(window, cx) {
9698 return;
9699 }
9700 self.outdent(&Outdent, window, cx);
9701 }
9702
9703 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9704 if self.mode.is_single_line() {
9705 cx.propagate();
9706 return;
9707 }
9708
9709 if self.move_to_next_snippet_tabstop(window, cx) {
9710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9711 return;
9712 }
9713 if self.read_only(cx) {
9714 return;
9715 }
9716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9717 let mut selections = self.selections.all_adjusted(cx);
9718 let buffer = self.buffer.read(cx);
9719 let snapshot = buffer.snapshot(cx);
9720 let rows_iter = selections.iter().map(|s| s.head().row);
9721 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9722
9723 let has_some_cursor_in_whitespace = selections
9724 .iter()
9725 .filter(|selection| selection.is_empty())
9726 .any(|selection| {
9727 let cursor = selection.head();
9728 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9729 cursor.column < current_indent.len
9730 });
9731
9732 let mut edits = Vec::new();
9733 let mut prev_edited_row = 0;
9734 let mut row_delta = 0;
9735 for selection in &mut selections {
9736 if selection.start.row != prev_edited_row {
9737 row_delta = 0;
9738 }
9739 prev_edited_row = selection.end.row;
9740
9741 // If the selection is non-empty, then increase the indentation of the selected lines.
9742 if !selection.is_empty() {
9743 row_delta =
9744 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9745 continue;
9746 }
9747
9748 let cursor = selection.head();
9749 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9750 if let Some(suggested_indent) =
9751 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9752 {
9753 // Don't do anything if already at suggested indent
9754 // and there is any other cursor which is not
9755 if has_some_cursor_in_whitespace
9756 && cursor.column == current_indent.len
9757 && current_indent.len == suggested_indent.len
9758 {
9759 continue;
9760 }
9761
9762 // Adjust line and move cursor to suggested indent
9763 // if cursor is not at suggested indent
9764 if cursor.column < suggested_indent.len
9765 && cursor.column <= current_indent.len
9766 && current_indent.len <= suggested_indent.len
9767 {
9768 selection.start = Point::new(cursor.row, suggested_indent.len);
9769 selection.end = selection.start;
9770 if row_delta == 0 {
9771 edits.extend(Buffer::edit_for_indent_size_adjustment(
9772 cursor.row,
9773 current_indent,
9774 suggested_indent,
9775 ));
9776 row_delta = suggested_indent.len - current_indent.len;
9777 }
9778 continue;
9779 }
9780
9781 // If current indent is more than suggested indent
9782 // only move cursor to current indent and skip indent
9783 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9784 selection.start = Point::new(cursor.row, current_indent.len);
9785 selection.end = selection.start;
9786 continue;
9787 }
9788 }
9789
9790 // Otherwise, insert a hard or soft tab.
9791 let settings = buffer.language_settings_at(cursor, cx);
9792 let tab_size = if settings.hard_tabs {
9793 IndentSize::tab()
9794 } else {
9795 let tab_size = settings.tab_size.get();
9796 let indent_remainder = snapshot
9797 .text_for_range(Point::new(cursor.row, 0)..cursor)
9798 .flat_map(str::chars)
9799 .fold(row_delta % tab_size, |counter: u32, c| {
9800 if c == '\t' {
9801 0
9802 } else {
9803 (counter + 1) % tab_size
9804 }
9805 });
9806
9807 let chars_to_next_tab_stop = tab_size - indent_remainder;
9808 IndentSize::spaces(chars_to_next_tab_stop)
9809 };
9810 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9811 selection.end = selection.start;
9812 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9813 row_delta += tab_size.len;
9814 }
9815
9816 self.transact(window, cx, |this, window, cx| {
9817 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9818 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9819 this.refresh_inline_completion(true, false, window, cx);
9820 });
9821 }
9822
9823 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9824 if self.read_only(cx) {
9825 return;
9826 }
9827 if self.mode.is_single_line() {
9828 cx.propagate();
9829 return;
9830 }
9831
9832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9833 let mut selections = self.selections.all::<Point>(cx);
9834 let mut prev_edited_row = 0;
9835 let mut row_delta = 0;
9836 let mut edits = Vec::new();
9837 let buffer = self.buffer.read(cx);
9838 let snapshot = buffer.snapshot(cx);
9839 for selection in &mut selections {
9840 if selection.start.row != prev_edited_row {
9841 row_delta = 0;
9842 }
9843 prev_edited_row = selection.end.row;
9844
9845 row_delta =
9846 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9847 }
9848
9849 self.transact(window, cx, |this, window, cx| {
9850 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9851 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9852 });
9853 }
9854
9855 fn indent_selection(
9856 buffer: &MultiBuffer,
9857 snapshot: &MultiBufferSnapshot,
9858 selection: &mut Selection<Point>,
9859 edits: &mut Vec<(Range<Point>, String)>,
9860 delta_for_start_row: u32,
9861 cx: &App,
9862 ) -> u32 {
9863 let settings = buffer.language_settings_at(selection.start, cx);
9864 let tab_size = settings.tab_size.get();
9865 let indent_kind = if settings.hard_tabs {
9866 IndentKind::Tab
9867 } else {
9868 IndentKind::Space
9869 };
9870 let mut start_row = selection.start.row;
9871 let mut end_row = selection.end.row + 1;
9872
9873 // If a selection ends at the beginning of a line, don't indent
9874 // that last line.
9875 if selection.end.column == 0 && selection.end.row > selection.start.row {
9876 end_row -= 1;
9877 }
9878
9879 // Avoid re-indenting a row that has already been indented by a
9880 // previous selection, but still update this selection's column
9881 // to reflect that indentation.
9882 if delta_for_start_row > 0 {
9883 start_row += 1;
9884 selection.start.column += delta_for_start_row;
9885 if selection.end.row == selection.start.row {
9886 selection.end.column += delta_for_start_row;
9887 }
9888 }
9889
9890 let mut delta_for_end_row = 0;
9891 let has_multiple_rows = start_row + 1 != end_row;
9892 for row in start_row..end_row {
9893 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9894 let indent_delta = match (current_indent.kind, indent_kind) {
9895 (IndentKind::Space, IndentKind::Space) => {
9896 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9897 IndentSize::spaces(columns_to_next_tab_stop)
9898 }
9899 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9900 (_, IndentKind::Tab) => IndentSize::tab(),
9901 };
9902
9903 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9904 0
9905 } else {
9906 selection.start.column
9907 };
9908 let row_start = Point::new(row, start);
9909 edits.push((
9910 row_start..row_start,
9911 indent_delta.chars().collect::<String>(),
9912 ));
9913
9914 // Update this selection's endpoints to reflect the indentation.
9915 if row == selection.start.row {
9916 selection.start.column += indent_delta.len;
9917 }
9918 if row == selection.end.row {
9919 selection.end.column += indent_delta.len;
9920 delta_for_end_row = indent_delta.len;
9921 }
9922 }
9923
9924 if selection.start.row == selection.end.row {
9925 delta_for_start_row + delta_for_end_row
9926 } else {
9927 delta_for_end_row
9928 }
9929 }
9930
9931 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9932 if self.read_only(cx) {
9933 return;
9934 }
9935 if self.mode.is_single_line() {
9936 cx.propagate();
9937 return;
9938 }
9939
9940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9942 let selections = self.selections.all::<Point>(cx);
9943 let mut deletion_ranges = Vec::new();
9944 let mut last_outdent = None;
9945 {
9946 let buffer = self.buffer.read(cx);
9947 let snapshot = buffer.snapshot(cx);
9948 for selection in &selections {
9949 let settings = buffer.language_settings_at(selection.start, cx);
9950 let tab_size = settings.tab_size.get();
9951 let mut rows = selection.spanned_rows(false, &display_map);
9952
9953 // Avoid re-outdenting a row that has already been outdented by a
9954 // previous selection.
9955 if let Some(last_row) = last_outdent {
9956 if last_row == rows.start {
9957 rows.start = rows.start.next_row();
9958 }
9959 }
9960 let has_multiple_rows = rows.len() > 1;
9961 for row in rows.iter_rows() {
9962 let indent_size = snapshot.indent_size_for_line(row);
9963 if indent_size.len > 0 {
9964 let deletion_len = match indent_size.kind {
9965 IndentKind::Space => {
9966 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9967 if columns_to_prev_tab_stop == 0 {
9968 tab_size
9969 } else {
9970 columns_to_prev_tab_stop
9971 }
9972 }
9973 IndentKind::Tab => 1,
9974 };
9975 let start = if has_multiple_rows
9976 || deletion_len > selection.start.column
9977 || indent_size.len < selection.start.column
9978 {
9979 0
9980 } else {
9981 selection.start.column - deletion_len
9982 };
9983 deletion_ranges.push(
9984 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9985 );
9986 last_outdent = Some(row);
9987 }
9988 }
9989 }
9990 }
9991
9992 self.transact(window, cx, |this, window, cx| {
9993 this.buffer.update(cx, |buffer, cx| {
9994 let empty_str: Arc<str> = Arc::default();
9995 buffer.edit(
9996 deletion_ranges
9997 .into_iter()
9998 .map(|range| (range, empty_str.clone())),
9999 None,
10000 cx,
10001 );
10002 });
10003 let selections = this.selections.all::<usize>(cx);
10004 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10005 });
10006 }
10007
10008 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10009 if self.read_only(cx) {
10010 return;
10011 }
10012 if self.mode.is_single_line() {
10013 cx.propagate();
10014 return;
10015 }
10016
10017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10018 let selections = self
10019 .selections
10020 .all::<usize>(cx)
10021 .into_iter()
10022 .map(|s| s.range());
10023
10024 self.transact(window, cx, |this, window, cx| {
10025 this.buffer.update(cx, |buffer, cx| {
10026 buffer.autoindent_ranges(selections, cx);
10027 });
10028 let selections = this.selections.all::<usize>(cx);
10029 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10030 });
10031 }
10032
10033 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10036 let selections = self.selections.all::<Point>(cx);
10037
10038 let mut new_cursors = Vec::new();
10039 let mut edit_ranges = Vec::new();
10040 let mut selections = selections.iter().peekable();
10041 while let Some(selection) = selections.next() {
10042 let mut rows = selection.spanned_rows(false, &display_map);
10043 let goal_display_column = selection.head().to_display_point(&display_map).column();
10044
10045 // Accumulate contiguous regions of rows that we want to delete.
10046 while let Some(next_selection) = selections.peek() {
10047 let next_rows = next_selection.spanned_rows(false, &display_map);
10048 if next_rows.start <= rows.end {
10049 rows.end = next_rows.end;
10050 selections.next().unwrap();
10051 } else {
10052 break;
10053 }
10054 }
10055
10056 let buffer = &display_map.buffer_snapshot;
10057 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10058 let edit_end;
10059 let cursor_buffer_row;
10060 if buffer.max_point().row >= rows.end.0 {
10061 // If there's a line after the range, delete the \n from the end of the row range
10062 // and position the cursor on the next line.
10063 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10064 cursor_buffer_row = rows.end;
10065 } else {
10066 // If there isn't a line after the range, delete the \n from the line before the
10067 // start of the row range and position the cursor there.
10068 edit_start = edit_start.saturating_sub(1);
10069 edit_end = buffer.len();
10070 cursor_buffer_row = rows.start.previous_row();
10071 }
10072
10073 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10074 *cursor.column_mut() =
10075 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10076
10077 new_cursors.push((
10078 selection.id,
10079 buffer.anchor_after(cursor.to_point(&display_map)),
10080 ));
10081 edit_ranges.push(edit_start..edit_end);
10082 }
10083
10084 self.transact(window, cx, |this, window, cx| {
10085 let buffer = this.buffer.update(cx, |buffer, cx| {
10086 let empty_str: Arc<str> = Arc::default();
10087 buffer.edit(
10088 edit_ranges
10089 .into_iter()
10090 .map(|range| (range, empty_str.clone())),
10091 None,
10092 cx,
10093 );
10094 buffer.snapshot(cx)
10095 });
10096 let new_selections = new_cursors
10097 .into_iter()
10098 .map(|(id, cursor)| {
10099 let cursor = cursor.to_point(&buffer);
10100 Selection {
10101 id,
10102 start: cursor,
10103 end: cursor,
10104 reversed: false,
10105 goal: SelectionGoal::None,
10106 }
10107 })
10108 .collect();
10109
10110 this.change_selections(Default::default(), window, cx, |s| {
10111 s.select(new_selections);
10112 });
10113 });
10114 }
10115
10116 pub fn join_lines_impl(
10117 &mut self,
10118 insert_whitespace: bool,
10119 window: &mut Window,
10120 cx: &mut Context<Self>,
10121 ) {
10122 if self.read_only(cx) {
10123 return;
10124 }
10125 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10126 for selection in self.selections.all::<Point>(cx) {
10127 let start = MultiBufferRow(selection.start.row);
10128 // Treat single line selections as if they include the next line. Otherwise this action
10129 // would do nothing for single line selections individual cursors.
10130 let end = if selection.start.row == selection.end.row {
10131 MultiBufferRow(selection.start.row + 1)
10132 } else {
10133 MultiBufferRow(selection.end.row)
10134 };
10135
10136 if let Some(last_row_range) = row_ranges.last_mut() {
10137 if start <= last_row_range.end {
10138 last_row_range.end = end;
10139 continue;
10140 }
10141 }
10142 row_ranges.push(start..end);
10143 }
10144
10145 let snapshot = self.buffer.read(cx).snapshot(cx);
10146 let mut cursor_positions = Vec::new();
10147 for row_range in &row_ranges {
10148 let anchor = snapshot.anchor_before(Point::new(
10149 row_range.end.previous_row().0,
10150 snapshot.line_len(row_range.end.previous_row()),
10151 ));
10152 cursor_positions.push(anchor..anchor);
10153 }
10154
10155 self.transact(window, cx, |this, window, cx| {
10156 for row_range in row_ranges.into_iter().rev() {
10157 for row in row_range.iter_rows().rev() {
10158 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10159 let next_line_row = row.next_row();
10160 let indent = snapshot.indent_size_for_line(next_line_row);
10161 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10162
10163 let replace =
10164 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10165 " "
10166 } else {
10167 ""
10168 };
10169
10170 this.buffer.update(cx, |buffer, cx| {
10171 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10172 });
10173 }
10174 }
10175
10176 this.change_selections(Default::default(), window, cx, |s| {
10177 s.select_anchor_ranges(cursor_positions)
10178 });
10179 });
10180 }
10181
10182 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10184 self.join_lines_impl(true, window, cx);
10185 }
10186
10187 pub fn sort_lines_case_sensitive(
10188 &mut self,
10189 _: &SortLinesCaseSensitive,
10190 window: &mut Window,
10191 cx: &mut Context<Self>,
10192 ) {
10193 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10194 }
10195
10196 pub fn sort_lines_case_insensitive(
10197 &mut self,
10198 _: &SortLinesCaseInsensitive,
10199 window: &mut Window,
10200 cx: &mut Context<Self>,
10201 ) {
10202 self.manipulate_immutable_lines(window, cx, |lines| {
10203 lines.sort_by_key(|line| line.to_lowercase())
10204 })
10205 }
10206
10207 pub fn unique_lines_case_insensitive(
10208 &mut self,
10209 _: &UniqueLinesCaseInsensitive,
10210 window: &mut Window,
10211 cx: &mut Context<Self>,
10212 ) {
10213 self.manipulate_immutable_lines(window, cx, |lines| {
10214 let mut seen = HashSet::default();
10215 lines.retain(|line| seen.insert(line.to_lowercase()));
10216 })
10217 }
10218
10219 pub fn unique_lines_case_sensitive(
10220 &mut self,
10221 _: &UniqueLinesCaseSensitive,
10222 window: &mut Window,
10223 cx: &mut Context<Self>,
10224 ) {
10225 self.manipulate_immutable_lines(window, cx, |lines| {
10226 let mut seen = HashSet::default();
10227 lines.retain(|line| seen.insert(*line));
10228 })
10229 }
10230
10231 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10232 let Some(project) = self.project.clone() else {
10233 return;
10234 };
10235 self.reload(project, window, cx)
10236 .detach_and_notify_err(window, cx);
10237 }
10238
10239 pub fn restore_file(
10240 &mut self,
10241 _: &::git::RestoreFile,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10246 let mut buffer_ids = HashSet::default();
10247 let snapshot = self.buffer().read(cx).snapshot(cx);
10248 for selection in self.selections.all::<usize>(cx) {
10249 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10250 }
10251
10252 let buffer = self.buffer().read(cx);
10253 let ranges = buffer_ids
10254 .into_iter()
10255 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10256 .collect::<Vec<_>>();
10257
10258 self.restore_hunks_in_ranges(ranges, window, cx);
10259 }
10260
10261 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10262 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10263 let selections = self
10264 .selections
10265 .all(cx)
10266 .into_iter()
10267 .map(|s| s.range())
10268 .collect();
10269 self.restore_hunks_in_ranges(selections, window, cx);
10270 }
10271
10272 pub fn restore_hunks_in_ranges(
10273 &mut self,
10274 ranges: Vec<Range<Point>>,
10275 window: &mut Window,
10276 cx: &mut Context<Editor>,
10277 ) {
10278 let mut revert_changes = HashMap::default();
10279 let chunk_by = self
10280 .snapshot(window, cx)
10281 .hunks_for_ranges(ranges)
10282 .into_iter()
10283 .chunk_by(|hunk| hunk.buffer_id);
10284 for (buffer_id, hunks) in &chunk_by {
10285 let hunks = hunks.collect::<Vec<_>>();
10286 for hunk in &hunks {
10287 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10288 }
10289 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10290 }
10291 drop(chunk_by);
10292 if !revert_changes.is_empty() {
10293 self.transact(window, cx, |editor, window, cx| {
10294 editor.restore(revert_changes, window, cx);
10295 });
10296 }
10297 }
10298
10299 pub fn open_active_item_in_terminal(
10300 &mut self,
10301 _: &OpenInTerminal,
10302 window: &mut Window,
10303 cx: &mut Context<Self>,
10304 ) {
10305 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10306 let project_path = buffer.read(cx).project_path(cx)?;
10307 let project = self.project.as_ref()?.read(cx);
10308 let entry = project.entry_for_path(&project_path, cx)?;
10309 let parent = match &entry.canonical_path {
10310 Some(canonical_path) => canonical_path.to_path_buf(),
10311 None => project.absolute_path(&project_path, cx)?,
10312 }
10313 .parent()?
10314 .to_path_buf();
10315 Some(parent)
10316 }) {
10317 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10318 }
10319 }
10320
10321 fn set_breakpoint_context_menu(
10322 &mut self,
10323 display_row: DisplayRow,
10324 position: Option<Anchor>,
10325 clicked_point: gpui::Point<Pixels>,
10326 window: &mut Window,
10327 cx: &mut Context<Self>,
10328 ) {
10329 let source = self
10330 .buffer
10331 .read(cx)
10332 .snapshot(cx)
10333 .anchor_before(Point::new(display_row.0, 0u32));
10334
10335 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10336
10337 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10338 self,
10339 source,
10340 clicked_point,
10341 context_menu,
10342 window,
10343 cx,
10344 );
10345 }
10346
10347 fn add_edit_breakpoint_block(
10348 &mut self,
10349 anchor: Anchor,
10350 breakpoint: &Breakpoint,
10351 edit_action: BreakpointPromptEditAction,
10352 window: &mut Window,
10353 cx: &mut Context<Self>,
10354 ) {
10355 let weak_editor = cx.weak_entity();
10356 let bp_prompt = cx.new(|cx| {
10357 BreakpointPromptEditor::new(
10358 weak_editor,
10359 anchor,
10360 breakpoint.clone(),
10361 edit_action,
10362 window,
10363 cx,
10364 )
10365 });
10366
10367 let height = bp_prompt.update(cx, |this, cx| {
10368 this.prompt
10369 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10370 });
10371 let cloned_prompt = bp_prompt.clone();
10372 let blocks = vec![BlockProperties {
10373 style: BlockStyle::Sticky,
10374 placement: BlockPlacement::Above(anchor),
10375 height: Some(height),
10376 render: Arc::new(move |cx| {
10377 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10378 cloned_prompt.clone().into_any_element()
10379 }),
10380 priority: 0,
10381 render_in_minimap: true,
10382 }];
10383
10384 let focus_handle = bp_prompt.focus_handle(cx);
10385 window.focus(&focus_handle);
10386
10387 let block_ids = self.insert_blocks(blocks, None, cx);
10388 bp_prompt.update(cx, |prompt, _| {
10389 prompt.add_block_ids(block_ids);
10390 });
10391 }
10392
10393 pub(crate) fn breakpoint_at_row(
10394 &self,
10395 row: u32,
10396 window: &mut Window,
10397 cx: &mut Context<Self>,
10398 ) -> Option<(Anchor, Breakpoint)> {
10399 let snapshot = self.snapshot(window, cx);
10400 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10401
10402 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10403 }
10404
10405 pub(crate) fn breakpoint_at_anchor(
10406 &self,
10407 breakpoint_position: Anchor,
10408 snapshot: &EditorSnapshot,
10409 cx: &mut Context<Self>,
10410 ) -> Option<(Anchor, Breakpoint)> {
10411 let project = self.project.clone()?;
10412
10413 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10414 snapshot
10415 .buffer_snapshot
10416 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10417 })?;
10418
10419 let enclosing_excerpt = breakpoint_position.excerpt_id;
10420 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10421 let buffer_snapshot = buffer.read(cx).snapshot();
10422
10423 let row = buffer_snapshot
10424 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10425 .row;
10426
10427 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10428 let anchor_end = snapshot
10429 .buffer_snapshot
10430 .anchor_after(Point::new(row, line_len));
10431
10432 let bp = self
10433 .breakpoint_store
10434 .as_ref()?
10435 .read_with(cx, |breakpoint_store, cx| {
10436 breakpoint_store
10437 .breakpoints(
10438 &buffer,
10439 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10440 &buffer_snapshot,
10441 cx,
10442 )
10443 .next()
10444 .and_then(|(bp, _)| {
10445 let breakpoint_row = buffer_snapshot
10446 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10447 .row;
10448
10449 if breakpoint_row == row {
10450 snapshot
10451 .buffer_snapshot
10452 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10453 .map(|position| (position, bp.bp.clone()))
10454 } else {
10455 None
10456 }
10457 })
10458 });
10459 bp
10460 }
10461
10462 pub fn edit_log_breakpoint(
10463 &mut self,
10464 _: &EditLogBreakpoint,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10469 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10470 message: None,
10471 state: BreakpointState::Enabled,
10472 condition: None,
10473 hit_condition: None,
10474 });
10475
10476 self.add_edit_breakpoint_block(
10477 anchor,
10478 &breakpoint,
10479 BreakpointPromptEditAction::Log,
10480 window,
10481 cx,
10482 );
10483 }
10484 }
10485
10486 fn breakpoints_at_cursors(
10487 &self,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10491 let snapshot = self.snapshot(window, cx);
10492 let cursors = self
10493 .selections
10494 .disjoint_anchors()
10495 .into_iter()
10496 .map(|selection| {
10497 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10498
10499 let breakpoint_position = self
10500 .breakpoint_at_row(cursor_position.row, window, cx)
10501 .map(|bp| bp.0)
10502 .unwrap_or_else(|| {
10503 snapshot
10504 .display_snapshot
10505 .buffer_snapshot
10506 .anchor_after(Point::new(cursor_position.row, 0))
10507 });
10508
10509 let breakpoint = self
10510 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10511 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10512
10513 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10514 })
10515 // 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.
10516 .collect::<HashMap<Anchor, _>>();
10517
10518 cursors.into_iter().collect()
10519 }
10520
10521 pub fn enable_breakpoint(
10522 &mut self,
10523 _: &crate::actions::EnableBreakpoint,
10524 window: &mut Window,
10525 cx: &mut Context<Self>,
10526 ) {
10527 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10528 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10529 continue;
10530 };
10531 self.edit_breakpoint_at_anchor(
10532 anchor,
10533 breakpoint,
10534 BreakpointEditAction::InvertState,
10535 cx,
10536 );
10537 }
10538 }
10539
10540 pub fn disable_breakpoint(
10541 &mut self,
10542 _: &crate::actions::DisableBreakpoint,
10543 window: &mut Window,
10544 cx: &mut Context<Self>,
10545 ) {
10546 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10547 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10548 continue;
10549 };
10550 self.edit_breakpoint_at_anchor(
10551 anchor,
10552 breakpoint,
10553 BreakpointEditAction::InvertState,
10554 cx,
10555 );
10556 }
10557 }
10558
10559 pub fn toggle_breakpoint(
10560 &mut self,
10561 _: &crate::actions::ToggleBreakpoint,
10562 window: &mut Window,
10563 cx: &mut Context<Self>,
10564 ) {
10565 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10566 if let Some(breakpoint) = breakpoint {
10567 self.edit_breakpoint_at_anchor(
10568 anchor,
10569 breakpoint,
10570 BreakpointEditAction::Toggle,
10571 cx,
10572 );
10573 } else {
10574 self.edit_breakpoint_at_anchor(
10575 anchor,
10576 Breakpoint::new_standard(),
10577 BreakpointEditAction::Toggle,
10578 cx,
10579 );
10580 }
10581 }
10582 }
10583
10584 pub fn edit_breakpoint_at_anchor(
10585 &mut self,
10586 breakpoint_position: Anchor,
10587 breakpoint: Breakpoint,
10588 edit_action: BreakpointEditAction,
10589 cx: &mut Context<Self>,
10590 ) {
10591 let Some(breakpoint_store) = &self.breakpoint_store else {
10592 return;
10593 };
10594
10595 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10596 if breakpoint_position == Anchor::min() {
10597 self.buffer()
10598 .read(cx)
10599 .excerpt_buffer_ids()
10600 .into_iter()
10601 .next()
10602 } else {
10603 None
10604 }
10605 }) else {
10606 return;
10607 };
10608
10609 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10610 return;
10611 };
10612
10613 breakpoint_store.update(cx, |breakpoint_store, cx| {
10614 breakpoint_store.toggle_breakpoint(
10615 buffer,
10616 BreakpointWithPosition {
10617 position: breakpoint_position.text_anchor,
10618 bp: breakpoint,
10619 },
10620 edit_action,
10621 cx,
10622 );
10623 });
10624
10625 cx.notify();
10626 }
10627
10628 #[cfg(any(test, feature = "test-support"))]
10629 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10630 self.breakpoint_store.clone()
10631 }
10632
10633 pub fn prepare_restore_change(
10634 &self,
10635 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10636 hunk: &MultiBufferDiffHunk,
10637 cx: &mut App,
10638 ) -> Option<()> {
10639 if hunk.is_created_file() {
10640 return None;
10641 }
10642 let buffer = self.buffer.read(cx);
10643 let diff = buffer.diff_for(hunk.buffer_id)?;
10644 let buffer = buffer.buffer(hunk.buffer_id)?;
10645 let buffer = buffer.read(cx);
10646 let original_text = diff
10647 .read(cx)
10648 .base_text()
10649 .as_rope()
10650 .slice(hunk.diff_base_byte_range.clone());
10651 let buffer_snapshot = buffer.snapshot();
10652 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10653 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10654 probe
10655 .0
10656 .start
10657 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10658 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10659 }) {
10660 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10661 Some(())
10662 } else {
10663 None
10664 }
10665 }
10666
10667 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10668 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
10669 }
10670
10671 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10672 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10673 }
10674
10675 fn manipulate_lines<M>(
10676 &mut self,
10677 window: &mut Window,
10678 cx: &mut Context<Self>,
10679 mut manipulate: M,
10680 ) where
10681 M: FnMut(&str) -> LineManipulationResult,
10682 {
10683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10684
10685 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10686 let buffer = self.buffer.read(cx).snapshot(cx);
10687
10688 let mut edits = Vec::new();
10689
10690 let selections = self.selections.all::<Point>(cx);
10691 let mut selections = selections.iter().peekable();
10692 let mut contiguous_row_selections = Vec::new();
10693 let mut new_selections = Vec::new();
10694 let mut added_lines = 0;
10695 let mut removed_lines = 0;
10696
10697 while let Some(selection) = selections.next() {
10698 let (start_row, end_row) = consume_contiguous_rows(
10699 &mut contiguous_row_selections,
10700 selection,
10701 &display_map,
10702 &mut selections,
10703 );
10704
10705 let start_point = Point::new(start_row.0, 0);
10706 let end_point = Point::new(
10707 end_row.previous_row().0,
10708 buffer.line_len(end_row.previous_row()),
10709 );
10710 let text = buffer
10711 .text_for_range(start_point..end_point)
10712 .collect::<String>();
10713
10714 let LineManipulationResult {
10715 new_text,
10716 line_count_before,
10717 line_count_after,
10718 } = manipulate(&text);
10719
10720 edits.push((start_point..end_point, new_text));
10721
10722 // Selections must change based on added and removed line count
10723 let start_row =
10724 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10725 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
10726 new_selections.push(Selection {
10727 id: selection.id,
10728 start: start_row,
10729 end: end_row,
10730 goal: SelectionGoal::None,
10731 reversed: selection.reversed,
10732 });
10733
10734 if line_count_after > line_count_before {
10735 added_lines += line_count_after - line_count_before;
10736 } else if line_count_before > line_count_after {
10737 removed_lines += line_count_before - line_count_after;
10738 }
10739 }
10740
10741 self.transact(window, cx, |this, window, cx| {
10742 let buffer = this.buffer.update(cx, |buffer, cx| {
10743 buffer.edit(edits, None, cx);
10744 buffer.snapshot(cx)
10745 });
10746
10747 // Recalculate offsets on newly edited buffer
10748 let new_selections = new_selections
10749 .iter()
10750 .map(|s| {
10751 let start_point = Point::new(s.start.0, 0);
10752 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10753 Selection {
10754 id: s.id,
10755 start: buffer.point_to_offset(start_point),
10756 end: buffer.point_to_offset(end_point),
10757 goal: s.goal,
10758 reversed: s.reversed,
10759 }
10760 })
10761 .collect();
10762
10763 this.change_selections(Default::default(), window, cx, |s| {
10764 s.select(new_selections);
10765 });
10766
10767 this.request_autoscroll(Autoscroll::fit(), cx);
10768 });
10769 }
10770
10771 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10772 self.manipulate_text(window, cx, |text| {
10773 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10774 if has_upper_case_characters {
10775 text.to_lowercase()
10776 } else {
10777 text.to_uppercase()
10778 }
10779 })
10780 }
10781
10782 fn manipulate_immutable_lines<Fn>(
10783 &mut self,
10784 window: &mut Window,
10785 cx: &mut Context<Self>,
10786 mut callback: Fn,
10787 ) where
10788 Fn: FnMut(&mut Vec<&str>),
10789 {
10790 self.manipulate_lines(window, cx, |text| {
10791 let mut lines: Vec<&str> = text.split('\n').collect();
10792 let line_count_before = lines.len();
10793
10794 callback(&mut lines);
10795
10796 LineManipulationResult {
10797 new_text: lines.join("\n"),
10798 line_count_before,
10799 line_count_after: lines.len(),
10800 }
10801 });
10802 }
10803
10804 fn manipulate_mutable_lines<Fn>(
10805 &mut self,
10806 window: &mut Window,
10807 cx: &mut Context<Self>,
10808 mut callback: Fn,
10809 ) where
10810 Fn: FnMut(&mut Vec<Cow<'_, str>>),
10811 {
10812 self.manipulate_lines(window, cx, |text| {
10813 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
10814 let line_count_before = lines.len();
10815
10816 callback(&mut lines);
10817
10818 LineManipulationResult {
10819 new_text: lines.join("\n"),
10820 line_count_before,
10821 line_count_after: lines.len(),
10822 }
10823 });
10824 }
10825
10826 pub fn convert_indentation_to_spaces(
10827 &mut self,
10828 _: &ConvertIndentationToSpaces,
10829 window: &mut Window,
10830 cx: &mut Context<Self>,
10831 ) {
10832 let settings = self.buffer.read(cx).language_settings(cx);
10833 let tab_size = settings.tab_size.get() as usize;
10834
10835 self.manipulate_mutable_lines(window, cx, |lines| {
10836 // Allocates a reasonably sized scratch buffer once for the whole loop
10837 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10838 // Avoids recomputing spaces that could be inserted many times
10839 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10840 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10841 .collect();
10842
10843 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10844 let mut chars = line.as_ref().chars();
10845 let mut col = 0;
10846 let mut changed = false;
10847
10848 while let Some(ch) = chars.next() {
10849 match ch {
10850 ' ' => {
10851 reindented_line.push(' ');
10852 col += 1;
10853 }
10854 '\t' => {
10855 // \t are converted to spaces depending on the current column
10856 let spaces_len = tab_size - (col % tab_size);
10857 reindented_line.extend(&space_cache[spaces_len - 1]);
10858 col += spaces_len;
10859 changed = true;
10860 }
10861 _ => {
10862 // If we dont append before break, the character is consumed
10863 reindented_line.push(ch);
10864 break;
10865 }
10866 }
10867 }
10868
10869 if !changed {
10870 reindented_line.clear();
10871 continue;
10872 }
10873 // Append the rest of the line and replace old reference with new one
10874 reindented_line.extend(chars);
10875 *line = Cow::Owned(reindented_line.clone());
10876 reindented_line.clear();
10877 }
10878 });
10879 }
10880
10881 pub fn convert_indentation_to_tabs(
10882 &mut self,
10883 _: &ConvertIndentationToTabs,
10884 window: &mut Window,
10885 cx: &mut Context<Self>,
10886 ) {
10887 let settings = self.buffer.read(cx).language_settings(cx);
10888 let tab_size = settings.tab_size.get() as usize;
10889
10890 self.manipulate_mutable_lines(window, cx, |lines| {
10891 // Allocates a reasonably sized buffer once for the whole loop
10892 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
10893 // Avoids recomputing spaces that could be inserted many times
10894 let space_cache: Vec<Vec<char>> = (1..=tab_size)
10895 .map(|n| IndentSize::spaces(n as u32).chars().collect())
10896 .collect();
10897
10898 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
10899 let mut chars = line.chars();
10900 let mut spaces_count = 0;
10901 let mut first_non_indent_char = None;
10902 let mut changed = false;
10903
10904 while let Some(ch) = chars.next() {
10905 match ch {
10906 ' ' => {
10907 // Keep track of spaces. Append \t when we reach tab_size
10908 spaces_count += 1;
10909 changed = true;
10910 if spaces_count == tab_size {
10911 reindented_line.push('\t');
10912 spaces_count = 0;
10913 }
10914 }
10915 '\t' => {
10916 reindented_line.push('\t');
10917 spaces_count = 0;
10918 }
10919 _ => {
10920 // Dont append it yet, we might have remaining spaces
10921 first_non_indent_char = Some(ch);
10922 break;
10923 }
10924 }
10925 }
10926
10927 if !changed {
10928 reindented_line.clear();
10929 continue;
10930 }
10931 // Remaining spaces that didn't make a full tab stop
10932 if spaces_count > 0 {
10933 reindented_line.extend(&space_cache[spaces_count - 1]);
10934 }
10935 // If we consume an extra character that was not indentation, add it back
10936 if let Some(extra_char) = first_non_indent_char {
10937 reindented_line.push(extra_char);
10938 }
10939 // Append the rest of the line and replace old reference with new one
10940 reindented_line.extend(chars);
10941 *line = Cow::Owned(reindented_line.clone());
10942 reindented_line.clear();
10943 }
10944 });
10945 }
10946
10947 pub fn convert_to_upper_case(
10948 &mut self,
10949 _: &ConvertToUpperCase,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 self.manipulate_text(window, cx, |text| text.to_uppercase())
10954 }
10955
10956 pub fn convert_to_lower_case(
10957 &mut self,
10958 _: &ConvertToLowerCase,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 self.manipulate_text(window, cx, |text| text.to_lowercase())
10963 }
10964
10965 pub fn convert_to_title_case(
10966 &mut self,
10967 _: &ConvertToTitleCase,
10968 window: &mut Window,
10969 cx: &mut Context<Self>,
10970 ) {
10971 self.manipulate_text(window, cx, |text| {
10972 text.split('\n')
10973 .map(|line| line.to_case(Case::Title))
10974 .join("\n")
10975 })
10976 }
10977
10978 pub fn convert_to_snake_case(
10979 &mut self,
10980 _: &ConvertToSnakeCase,
10981 window: &mut Window,
10982 cx: &mut Context<Self>,
10983 ) {
10984 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10985 }
10986
10987 pub fn convert_to_kebab_case(
10988 &mut self,
10989 _: &ConvertToKebabCase,
10990 window: &mut Window,
10991 cx: &mut Context<Self>,
10992 ) {
10993 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10994 }
10995
10996 pub fn convert_to_upper_camel_case(
10997 &mut self,
10998 _: &ConvertToUpperCamelCase,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) {
11002 self.manipulate_text(window, cx, |text| {
11003 text.split('\n')
11004 .map(|line| line.to_case(Case::UpperCamel))
11005 .join("\n")
11006 })
11007 }
11008
11009 pub fn convert_to_lower_camel_case(
11010 &mut self,
11011 _: &ConvertToLowerCamelCase,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11016 }
11017
11018 pub fn convert_to_opposite_case(
11019 &mut self,
11020 _: &ConvertToOppositeCase,
11021 window: &mut Window,
11022 cx: &mut Context<Self>,
11023 ) {
11024 self.manipulate_text(window, cx, |text| {
11025 text.chars()
11026 .fold(String::with_capacity(text.len()), |mut t, c| {
11027 if c.is_uppercase() {
11028 t.extend(c.to_lowercase());
11029 } else {
11030 t.extend(c.to_uppercase());
11031 }
11032 t
11033 })
11034 })
11035 }
11036
11037 pub fn convert_to_rot13(
11038 &mut self,
11039 _: &ConvertToRot13,
11040 window: &mut Window,
11041 cx: &mut Context<Self>,
11042 ) {
11043 self.manipulate_text(window, cx, |text| {
11044 text.chars()
11045 .map(|c| match c {
11046 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11047 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11048 _ => c,
11049 })
11050 .collect()
11051 })
11052 }
11053
11054 pub fn convert_to_rot47(
11055 &mut self,
11056 _: &ConvertToRot47,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_text(window, cx, |text| {
11061 text.chars()
11062 .map(|c| {
11063 let code_point = c as u32;
11064 if code_point >= 33 && code_point <= 126 {
11065 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11066 }
11067 c
11068 })
11069 .collect()
11070 })
11071 }
11072
11073 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11074 where
11075 Fn: FnMut(&str) -> String,
11076 {
11077 let buffer = self.buffer.read(cx).snapshot(cx);
11078
11079 let mut new_selections = Vec::new();
11080 let mut edits = Vec::new();
11081 let mut selection_adjustment = 0i32;
11082
11083 for selection in self.selections.all::<usize>(cx) {
11084 let selection_is_empty = selection.is_empty();
11085
11086 let (start, end) = if selection_is_empty {
11087 let (word_range, _) = buffer.surrounding_word(selection.start, false);
11088 (word_range.start, word_range.end)
11089 } else {
11090 (selection.start, selection.end)
11091 };
11092
11093 let text = buffer.text_for_range(start..end).collect::<String>();
11094 let old_length = text.len() as i32;
11095 let text = callback(&text);
11096
11097 new_selections.push(Selection {
11098 start: (start as i32 - selection_adjustment) as usize,
11099 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11100 goal: SelectionGoal::None,
11101 ..selection
11102 });
11103
11104 selection_adjustment += old_length - text.len() as i32;
11105
11106 edits.push((start..end, text));
11107 }
11108
11109 self.transact(window, cx, |this, window, cx| {
11110 this.buffer.update(cx, |buffer, cx| {
11111 buffer.edit(edits, None, cx);
11112 });
11113
11114 this.change_selections(Default::default(), window, cx, |s| {
11115 s.select(new_selections);
11116 });
11117
11118 this.request_autoscroll(Autoscroll::fit(), cx);
11119 });
11120 }
11121
11122 pub fn move_selection_on_drop(
11123 &mut self,
11124 selection: &Selection<Anchor>,
11125 target: DisplayPoint,
11126 is_cut: bool,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11131 let buffer = &display_map.buffer_snapshot;
11132 let mut edits = Vec::new();
11133 let insert_point = display_map
11134 .clip_point(target, Bias::Left)
11135 .to_point(&display_map);
11136 let text = buffer
11137 .text_for_range(selection.start..selection.end)
11138 .collect::<String>();
11139 if is_cut {
11140 edits.push(((selection.start..selection.end), String::new()));
11141 }
11142 let insert_anchor = buffer.anchor_before(insert_point);
11143 edits.push(((insert_anchor..insert_anchor), text));
11144 let last_edit_start = insert_anchor.bias_left(buffer);
11145 let last_edit_end = insert_anchor.bias_right(buffer);
11146 self.transact(window, cx, |this, window, cx| {
11147 this.buffer.update(cx, |buffer, cx| {
11148 buffer.edit(edits, None, cx);
11149 });
11150 this.change_selections(Default::default(), window, cx, |s| {
11151 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11152 });
11153 });
11154 }
11155
11156 pub fn clear_selection_drag_state(&mut self) {
11157 self.selection_drag_state = SelectionDragState::None;
11158 }
11159
11160 pub fn duplicate(
11161 &mut self,
11162 upwards: bool,
11163 whole_lines: bool,
11164 window: &mut Window,
11165 cx: &mut Context<Self>,
11166 ) {
11167 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11168
11169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11170 let buffer = &display_map.buffer_snapshot;
11171 let selections = self.selections.all::<Point>(cx);
11172
11173 let mut edits = Vec::new();
11174 let mut selections_iter = selections.iter().peekable();
11175 while let Some(selection) = selections_iter.next() {
11176 let mut rows = selection.spanned_rows(false, &display_map);
11177 // duplicate line-wise
11178 if whole_lines || selection.start == selection.end {
11179 // Avoid duplicating the same lines twice.
11180 while let Some(next_selection) = selections_iter.peek() {
11181 let next_rows = next_selection.spanned_rows(false, &display_map);
11182 if next_rows.start < rows.end {
11183 rows.end = next_rows.end;
11184 selections_iter.next().unwrap();
11185 } else {
11186 break;
11187 }
11188 }
11189
11190 // Copy the text from the selected row region and splice it either at the start
11191 // or end of the region.
11192 let start = Point::new(rows.start.0, 0);
11193 let end = Point::new(
11194 rows.end.previous_row().0,
11195 buffer.line_len(rows.end.previous_row()),
11196 );
11197 let text = buffer
11198 .text_for_range(start..end)
11199 .chain(Some("\n"))
11200 .collect::<String>();
11201 let insert_location = if upwards {
11202 Point::new(rows.end.0, 0)
11203 } else {
11204 start
11205 };
11206 edits.push((insert_location..insert_location, text));
11207 } else {
11208 // duplicate character-wise
11209 let start = selection.start;
11210 let end = selection.end;
11211 let text = buffer.text_for_range(start..end).collect::<String>();
11212 edits.push((selection.end..selection.end, text));
11213 }
11214 }
11215
11216 self.transact(window, cx, |this, _, cx| {
11217 this.buffer.update(cx, |buffer, cx| {
11218 buffer.edit(edits, None, cx);
11219 });
11220
11221 this.request_autoscroll(Autoscroll::fit(), cx);
11222 });
11223 }
11224
11225 pub fn duplicate_line_up(
11226 &mut self,
11227 _: &DuplicateLineUp,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.duplicate(true, true, window, cx);
11232 }
11233
11234 pub fn duplicate_line_down(
11235 &mut self,
11236 _: &DuplicateLineDown,
11237 window: &mut Window,
11238 cx: &mut Context<Self>,
11239 ) {
11240 self.duplicate(false, true, window, cx);
11241 }
11242
11243 pub fn duplicate_selection(
11244 &mut self,
11245 _: &DuplicateSelection,
11246 window: &mut Window,
11247 cx: &mut Context<Self>,
11248 ) {
11249 self.duplicate(false, false, window, cx);
11250 }
11251
11252 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11254 if self.mode.is_single_line() {
11255 cx.propagate();
11256 return;
11257 }
11258
11259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11260 let buffer = self.buffer.read(cx).snapshot(cx);
11261
11262 let mut edits = Vec::new();
11263 let mut unfold_ranges = Vec::new();
11264 let mut refold_creases = Vec::new();
11265
11266 let selections = self.selections.all::<Point>(cx);
11267 let mut selections = selections.iter().peekable();
11268 let mut contiguous_row_selections = Vec::new();
11269 let mut new_selections = Vec::new();
11270
11271 while let Some(selection) = selections.next() {
11272 // Find all the selections that span a contiguous row range
11273 let (start_row, end_row) = consume_contiguous_rows(
11274 &mut contiguous_row_selections,
11275 selection,
11276 &display_map,
11277 &mut selections,
11278 );
11279
11280 // Move the text spanned by the row range to be before the line preceding the row range
11281 if start_row.0 > 0 {
11282 let range_to_move = Point::new(
11283 start_row.previous_row().0,
11284 buffer.line_len(start_row.previous_row()),
11285 )
11286 ..Point::new(
11287 end_row.previous_row().0,
11288 buffer.line_len(end_row.previous_row()),
11289 );
11290 let insertion_point = display_map
11291 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11292 .0;
11293
11294 // Don't move lines across excerpts
11295 if buffer
11296 .excerpt_containing(insertion_point..range_to_move.end)
11297 .is_some()
11298 {
11299 let text = buffer
11300 .text_for_range(range_to_move.clone())
11301 .flat_map(|s| s.chars())
11302 .skip(1)
11303 .chain(['\n'])
11304 .collect::<String>();
11305
11306 edits.push((
11307 buffer.anchor_after(range_to_move.start)
11308 ..buffer.anchor_before(range_to_move.end),
11309 String::new(),
11310 ));
11311 let insertion_anchor = buffer.anchor_after(insertion_point);
11312 edits.push((insertion_anchor..insertion_anchor, text));
11313
11314 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11315
11316 // Move selections up
11317 new_selections.extend(contiguous_row_selections.drain(..).map(
11318 |mut selection| {
11319 selection.start.row -= row_delta;
11320 selection.end.row -= row_delta;
11321 selection
11322 },
11323 ));
11324
11325 // Move folds up
11326 unfold_ranges.push(range_to_move.clone());
11327 for fold in display_map.folds_in_range(
11328 buffer.anchor_before(range_to_move.start)
11329 ..buffer.anchor_after(range_to_move.end),
11330 ) {
11331 let mut start = fold.range.start.to_point(&buffer);
11332 let mut end = fold.range.end.to_point(&buffer);
11333 start.row -= row_delta;
11334 end.row -= row_delta;
11335 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11336 }
11337 }
11338 }
11339
11340 // If we didn't move line(s), preserve the existing selections
11341 new_selections.append(&mut contiguous_row_selections);
11342 }
11343
11344 self.transact(window, cx, |this, window, cx| {
11345 this.unfold_ranges(&unfold_ranges, true, true, cx);
11346 this.buffer.update(cx, |buffer, cx| {
11347 for (range, text) in edits {
11348 buffer.edit([(range, text)], None, cx);
11349 }
11350 });
11351 this.fold_creases(refold_creases, true, window, cx);
11352 this.change_selections(Default::default(), window, cx, |s| {
11353 s.select(new_selections);
11354 })
11355 });
11356 }
11357
11358 pub fn move_line_down(
11359 &mut self,
11360 _: &MoveLineDown,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11365 if self.mode.is_single_line() {
11366 cx.propagate();
11367 return;
11368 }
11369
11370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11371 let buffer = self.buffer.read(cx).snapshot(cx);
11372
11373 let mut edits = Vec::new();
11374 let mut unfold_ranges = Vec::new();
11375 let mut refold_creases = Vec::new();
11376
11377 let selections = self.selections.all::<Point>(cx);
11378 let mut selections = selections.iter().peekable();
11379 let mut contiguous_row_selections = Vec::new();
11380 let mut new_selections = Vec::new();
11381
11382 while let Some(selection) = selections.next() {
11383 // Find all the selections that span a contiguous row range
11384 let (start_row, end_row) = consume_contiguous_rows(
11385 &mut contiguous_row_selections,
11386 selection,
11387 &display_map,
11388 &mut selections,
11389 );
11390
11391 // Move the text spanned by the row range to be after the last line of the row range
11392 if end_row.0 <= buffer.max_point().row {
11393 let range_to_move =
11394 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11395 let insertion_point = display_map
11396 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11397 .0;
11398
11399 // Don't move lines across excerpt boundaries
11400 if buffer
11401 .excerpt_containing(range_to_move.start..insertion_point)
11402 .is_some()
11403 {
11404 let mut text = String::from("\n");
11405 text.extend(buffer.text_for_range(range_to_move.clone()));
11406 text.pop(); // Drop trailing newline
11407 edits.push((
11408 buffer.anchor_after(range_to_move.start)
11409 ..buffer.anchor_before(range_to_move.end),
11410 String::new(),
11411 ));
11412 let insertion_anchor = buffer.anchor_after(insertion_point);
11413 edits.push((insertion_anchor..insertion_anchor, text));
11414
11415 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11416
11417 // Move selections down
11418 new_selections.extend(contiguous_row_selections.drain(..).map(
11419 |mut selection| {
11420 selection.start.row += row_delta;
11421 selection.end.row += row_delta;
11422 selection
11423 },
11424 ));
11425
11426 // Move folds down
11427 unfold_ranges.push(range_to_move.clone());
11428 for fold in display_map.folds_in_range(
11429 buffer.anchor_before(range_to_move.start)
11430 ..buffer.anchor_after(range_to_move.end),
11431 ) {
11432 let mut start = fold.range.start.to_point(&buffer);
11433 let mut end = fold.range.end.to_point(&buffer);
11434 start.row += row_delta;
11435 end.row += row_delta;
11436 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11437 }
11438 }
11439 }
11440
11441 // If we didn't move line(s), preserve the existing selections
11442 new_selections.append(&mut contiguous_row_selections);
11443 }
11444
11445 self.transact(window, cx, |this, window, cx| {
11446 this.unfold_ranges(&unfold_ranges, true, true, cx);
11447 this.buffer.update(cx, |buffer, cx| {
11448 for (range, text) in edits {
11449 buffer.edit([(range, text)], None, cx);
11450 }
11451 });
11452 this.fold_creases(refold_creases, true, window, cx);
11453 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11454 });
11455 }
11456
11457 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11459 let text_layout_details = &self.text_layout_details(window);
11460 self.transact(window, cx, |this, window, cx| {
11461 let edits = this.change_selections(Default::default(), window, cx, |s| {
11462 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11463 s.move_with(|display_map, selection| {
11464 if !selection.is_empty() {
11465 return;
11466 }
11467
11468 let mut head = selection.head();
11469 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11470 if head.column() == display_map.line_len(head.row()) {
11471 transpose_offset = display_map
11472 .buffer_snapshot
11473 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11474 }
11475
11476 if transpose_offset == 0 {
11477 return;
11478 }
11479
11480 *head.column_mut() += 1;
11481 head = display_map.clip_point(head, Bias::Right);
11482 let goal = SelectionGoal::HorizontalPosition(
11483 display_map
11484 .x_for_display_point(head, text_layout_details)
11485 .into(),
11486 );
11487 selection.collapse_to(head, goal);
11488
11489 let transpose_start = display_map
11490 .buffer_snapshot
11491 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11492 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11493 let transpose_end = display_map
11494 .buffer_snapshot
11495 .clip_offset(transpose_offset + 1, Bias::Right);
11496 if let Some(ch) =
11497 display_map.buffer_snapshot.chars_at(transpose_start).next()
11498 {
11499 edits.push((transpose_start..transpose_offset, String::new()));
11500 edits.push((transpose_end..transpose_end, ch.to_string()));
11501 }
11502 }
11503 });
11504 edits
11505 });
11506 this.buffer
11507 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11508 let selections = this.selections.all::<usize>(cx);
11509 this.change_selections(Default::default(), window, cx, |s| {
11510 s.select(selections);
11511 });
11512 });
11513 }
11514
11515 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11517 if self.mode.is_single_line() {
11518 cx.propagate();
11519 return;
11520 }
11521
11522 self.rewrap_impl(RewrapOptions::default(), cx)
11523 }
11524
11525 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11526 let buffer = self.buffer.read(cx).snapshot(cx);
11527 let selections = self.selections.all::<Point>(cx);
11528
11529 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11530 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11531 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11532 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11533 .peekable();
11534
11535 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11536 row
11537 } else {
11538 return Vec::new();
11539 };
11540
11541 let language_settings = buffer.language_settings_at(selection.head(), cx);
11542 let language_scope = buffer.language_scope_at(selection.head());
11543
11544 let mut ranges = Vec::new();
11545 let mut current_range_start = first_row;
11546 let from_empty_selection = selection.is_empty();
11547
11548 let mut prev_row = first_row;
11549 let mut prev_indent = buffer.indent_size_for_line(MultiBufferRow(first_row));
11550 let mut prev_comment_prefix = if let Some(language_scope) = &language_scope {
11551 let indent = buffer.indent_size_for_line(MultiBufferRow(first_row));
11552 let indent_end = Point::new(first_row, indent.len);
11553 language_scope
11554 .line_comment_prefixes()
11555 .iter()
11556 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11557 .cloned()
11558 } else {
11559 None
11560 };
11561
11562 for row in non_blank_rows_iter.skip(1) {
11563 let has_paragraph_break = row > prev_row + 1;
11564
11565 let row_indent = buffer.indent_size_for_line(MultiBufferRow(row));
11566 let row_comment_prefix = if let Some(language_scope) = &language_scope {
11567 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11568 let indent_end = Point::new(row, indent.len);
11569 language_scope
11570 .line_comment_prefixes()
11571 .iter()
11572 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11573 .cloned()
11574 } else {
11575 None
11576 };
11577
11578 let has_boundary_change =
11579 row_indent != prev_indent || row_comment_prefix != prev_comment_prefix;
11580
11581 if has_paragraph_break || has_boundary_change {
11582 ranges.push((
11583 language_settings.clone(),
11584 Point::new(current_range_start, 0)
11585 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11586 prev_indent,
11587 prev_comment_prefix.clone(),
11588 from_empty_selection,
11589 ));
11590 current_range_start = row;
11591 }
11592
11593 prev_row = row;
11594 prev_indent = row_indent;
11595 prev_comment_prefix = row_comment_prefix;
11596 }
11597
11598 ranges.push((
11599 language_settings.clone(),
11600 Point::new(current_range_start, 0)
11601 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
11602 prev_indent,
11603 prev_comment_prefix,
11604 from_empty_selection,
11605 ));
11606
11607 ranges
11608 });
11609
11610 let mut edits = Vec::new();
11611 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11612
11613 for (language_settings, wrap_range, indent_size, comment_prefix, from_empty_selection) in
11614 wrap_ranges
11615 {
11616 let mut start_row = wrap_range.start.row;
11617 let mut end_row = wrap_range.end.row;
11618
11619 // Skip selections that overlap with a range that has already been rewrapped.
11620 let selection_range = start_row..end_row;
11621 if rewrapped_row_ranges
11622 .iter()
11623 .any(|range| range.overlaps(&selection_range))
11624 {
11625 continue;
11626 }
11627
11628 let tab_size = language_settings.tab_size;
11629
11630 let mut line_prefix = indent_size.chars().collect::<String>();
11631 let mut inside_comment = false;
11632 if let Some(prefix) = &comment_prefix {
11633 line_prefix.push_str(prefix);
11634 inside_comment = true;
11635 }
11636
11637 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11638 RewrapBehavior::InComments => inside_comment,
11639 RewrapBehavior::InSelections => !wrap_range.is_empty(),
11640 RewrapBehavior::Anywhere => true,
11641 };
11642
11643 let should_rewrap = options.override_language_settings
11644 || allow_rewrap_based_on_language
11645 || self.hard_wrap.is_some();
11646 if !should_rewrap {
11647 continue;
11648 }
11649
11650 if from_empty_selection {
11651 'expand_upwards: while start_row > 0 {
11652 let prev_row = start_row - 1;
11653 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11654 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11655 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11656 {
11657 start_row = prev_row;
11658 } else {
11659 break 'expand_upwards;
11660 }
11661 }
11662
11663 'expand_downwards: while end_row < buffer.max_point().row {
11664 let next_row = end_row + 1;
11665 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11666 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11667 && !buffer.is_line_blank(MultiBufferRow(next_row))
11668 {
11669 end_row = next_row;
11670 } else {
11671 break 'expand_downwards;
11672 }
11673 }
11674 }
11675
11676 let start = Point::new(start_row, 0);
11677 let start_offset = start.to_offset(&buffer);
11678 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11679 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11680 let Some(lines_without_prefixes) = selection_text
11681 .lines()
11682 .map(|line| {
11683 line.strip_prefix(&line_prefix)
11684 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11685 .with_context(|| {
11686 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11687 })
11688 })
11689 .collect::<Result<Vec<_>, _>>()
11690 .log_err()
11691 else {
11692 continue;
11693 };
11694
11695 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11696 buffer
11697 .language_settings_at(Point::new(start_row, 0), cx)
11698 .preferred_line_length as usize
11699 });
11700 let wrapped_text = wrap_with_prefix(
11701 line_prefix,
11702 lines_without_prefixes.join("\n"),
11703 wrap_column,
11704 tab_size,
11705 options.preserve_existing_whitespace,
11706 );
11707
11708 // TODO: should always use char-based diff while still supporting cursor behavior that
11709 // matches vim.
11710 let mut diff_options = DiffOptions::default();
11711 if options.override_language_settings {
11712 diff_options.max_word_diff_len = 0;
11713 diff_options.max_word_diff_line_count = 0;
11714 } else {
11715 diff_options.max_word_diff_len = usize::MAX;
11716 diff_options.max_word_diff_line_count = usize::MAX;
11717 }
11718
11719 for (old_range, new_text) in
11720 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11721 {
11722 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11723 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11724 edits.push((edit_start..edit_end, new_text));
11725 }
11726
11727 rewrapped_row_ranges.push(start_row..=end_row);
11728 }
11729
11730 self.buffer
11731 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11732 }
11733
11734 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11735 let mut text = String::new();
11736 let buffer = self.buffer.read(cx).snapshot(cx);
11737 let mut selections = self.selections.all::<Point>(cx);
11738 let mut clipboard_selections = Vec::with_capacity(selections.len());
11739 {
11740 let max_point = buffer.max_point();
11741 let mut is_first = true;
11742 for selection in &mut selections {
11743 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11744 if is_entire_line {
11745 selection.start = Point::new(selection.start.row, 0);
11746 if !selection.is_empty() && selection.end.column == 0 {
11747 selection.end = cmp::min(max_point, selection.end);
11748 } else {
11749 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11750 }
11751 selection.goal = SelectionGoal::None;
11752 }
11753 if is_first {
11754 is_first = false;
11755 } else {
11756 text += "\n";
11757 }
11758 let mut len = 0;
11759 for chunk in buffer.text_for_range(selection.start..selection.end) {
11760 text.push_str(chunk);
11761 len += chunk.len();
11762 }
11763 clipboard_selections.push(ClipboardSelection {
11764 len,
11765 is_entire_line,
11766 first_line_indent: buffer
11767 .indent_size_for_line(MultiBufferRow(selection.start.row))
11768 .len,
11769 });
11770 }
11771 }
11772
11773 self.transact(window, cx, |this, window, cx| {
11774 this.change_selections(Default::default(), window, cx, |s| {
11775 s.select(selections);
11776 });
11777 this.insert("", window, cx);
11778 });
11779 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11780 }
11781
11782 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11783 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11784 let item = self.cut_common(window, cx);
11785 cx.write_to_clipboard(item);
11786 }
11787
11788 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11791 s.move_with(|snapshot, sel| {
11792 if sel.is_empty() {
11793 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11794 }
11795 });
11796 });
11797 let item = self.cut_common(window, cx);
11798 cx.set_global(KillRing(item))
11799 }
11800
11801 pub fn kill_ring_yank(
11802 &mut self,
11803 _: &KillRingYank,
11804 window: &mut Window,
11805 cx: &mut Context<Self>,
11806 ) {
11807 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11808 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11809 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11810 (kill_ring.text().to_string(), kill_ring.metadata_json())
11811 } else {
11812 return;
11813 }
11814 } else {
11815 return;
11816 };
11817 self.do_paste(&text, metadata, false, window, cx);
11818 }
11819
11820 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11821 self.do_copy(true, cx);
11822 }
11823
11824 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11825 self.do_copy(false, cx);
11826 }
11827
11828 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11829 let selections = self.selections.all::<Point>(cx);
11830 let buffer = self.buffer.read(cx).read(cx);
11831 let mut text = String::new();
11832
11833 let mut clipboard_selections = Vec::with_capacity(selections.len());
11834 {
11835 let max_point = buffer.max_point();
11836 let mut is_first = true;
11837 for selection in &selections {
11838 let mut start = selection.start;
11839 let mut end = selection.end;
11840 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11841 if is_entire_line {
11842 start = Point::new(start.row, 0);
11843 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11844 }
11845
11846 let mut trimmed_selections = Vec::new();
11847 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11848 let row = MultiBufferRow(start.row);
11849 let first_indent = buffer.indent_size_for_line(row);
11850 if first_indent.len == 0 || start.column > first_indent.len {
11851 trimmed_selections.push(start..end);
11852 } else {
11853 trimmed_selections.push(
11854 Point::new(row.0, first_indent.len)
11855 ..Point::new(row.0, buffer.line_len(row)),
11856 );
11857 for row in start.row + 1..=end.row {
11858 let mut line_len = buffer.line_len(MultiBufferRow(row));
11859 if row == end.row {
11860 line_len = end.column;
11861 }
11862 if line_len == 0 {
11863 trimmed_selections
11864 .push(Point::new(row, 0)..Point::new(row, line_len));
11865 continue;
11866 }
11867 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11868 if row_indent_size.len >= first_indent.len {
11869 trimmed_selections.push(
11870 Point::new(row, first_indent.len)..Point::new(row, line_len),
11871 );
11872 } else {
11873 trimmed_selections.clear();
11874 trimmed_selections.push(start..end);
11875 break;
11876 }
11877 }
11878 }
11879 } else {
11880 trimmed_selections.push(start..end);
11881 }
11882
11883 for trimmed_range in trimmed_selections {
11884 if is_first {
11885 is_first = false;
11886 } else {
11887 text += "\n";
11888 }
11889 let mut len = 0;
11890 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11891 text.push_str(chunk);
11892 len += chunk.len();
11893 }
11894 clipboard_selections.push(ClipboardSelection {
11895 len,
11896 is_entire_line,
11897 first_line_indent: buffer
11898 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11899 .len,
11900 });
11901 }
11902 }
11903 }
11904
11905 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11906 text,
11907 clipboard_selections,
11908 ));
11909 }
11910
11911 pub fn do_paste(
11912 &mut self,
11913 text: &String,
11914 clipboard_selections: Option<Vec<ClipboardSelection>>,
11915 handle_entire_lines: bool,
11916 window: &mut Window,
11917 cx: &mut Context<Self>,
11918 ) {
11919 if self.read_only(cx) {
11920 return;
11921 }
11922
11923 let clipboard_text = Cow::Borrowed(text);
11924
11925 self.transact(window, cx, |this, window, cx| {
11926 if let Some(mut clipboard_selections) = clipboard_selections {
11927 let old_selections = this.selections.all::<usize>(cx);
11928 let all_selections_were_entire_line =
11929 clipboard_selections.iter().all(|s| s.is_entire_line);
11930 let first_selection_indent_column =
11931 clipboard_selections.first().map(|s| s.first_line_indent);
11932 if clipboard_selections.len() != old_selections.len() {
11933 clipboard_selections.drain(..);
11934 }
11935 let cursor_offset = this.selections.last::<usize>(cx).head();
11936 let mut auto_indent_on_paste = true;
11937
11938 this.buffer.update(cx, |buffer, cx| {
11939 let snapshot = buffer.read(cx);
11940 auto_indent_on_paste = snapshot
11941 .language_settings_at(cursor_offset, cx)
11942 .auto_indent_on_paste;
11943
11944 let mut start_offset = 0;
11945 let mut edits = Vec::new();
11946 let mut original_indent_columns = Vec::new();
11947 for (ix, selection) in old_selections.iter().enumerate() {
11948 let to_insert;
11949 let entire_line;
11950 let original_indent_column;
11951 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11952 let end_offset = start_offset + clipboard_selection.len;
11953 to_insert = &clipboard_text[start_offset..end_offset];
11954 entire_line = clipboard_selection.is_entire_line;
11955 start_offset = end_offset + 1;
11956 original_indent_column = Some(clipboard_selection.first_line_indent);
11957 } else {
11958 to_insert = clipboard_text.as_str();
11959 entire_line = all_selections_were_entire_line;
11960 original_indent_column = first_selection_indent_column
11961 }
11962
11963 // If the corresponding selection was empty when this slice of the
11964 // clipboard text was written, then the entire line containing the
11965 // selection was copied. If this selection is also currently empty,
11966 // then paste the line before the current line of the buffer.
11967 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11968 let column = selection.start.to_point(&snapshot).column as usize;
11969 let line_start = selection.start - column;
11970 line_start..line_start
11971 } else {
11972 selection.range()
11973 };
11974
11975 edits.push((range, to_insert));
11976 original_indent_columns.push(original_indent_column);
11977 }
11978 drop(snapshot);
11979
11980 buffer.edit(
11981 edits,
11982 if auto_indent_on_paste {
11983 Some(AutoindentMode::Block {
11984 original_indent_columns,
11985 })
11986 } else {
11987 None
11988 },
11989 cx,
11990 );
11991 });
11992
11993 let selections = this.selections.all::<usize>(cx);
11994 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11995 } else {
11996 this.insert(&clipboard_text, window, cx);
11997 }
11998 });
11999 }
12000
12001 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12003 if let Some(item) = cx.read_from_clipboard() {
12004 let entries = item.entries();
12005
12006 match entries.first() {
12007 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12008 // of all the pasted entries.
12009 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12010 .do_paste(
12011 clipboard_string.text(),
12012 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12013 true,
12014 window,
12015 cx,
12016 ),
12017 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12018 }
12019 }
12020 }
12021
12022 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12023 if self.read_only(cx) {
12024 return;
12025 }
12026
12027 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12028
12029 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12030 if let Some((selections, _)) =
12031 self.selection_history.transaction(transaction_id).cloned()
12032 {
12033 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12034 s.select_anchors(selections.to_vec());
12035 });
12036 } else {
12037 log::error!(
12038 "No entry in selection_history found for undo. \
12039 This may correspond to a bug where undo does not update the selection. \
12040 If this is occurring, please add details to \
12041 https://github.com/zed-industries/zed/issues/22692"
12042 );
12043 }
12044 self.request_autoscroll(Autoscroll::fit(), cx);
12045 self.unmark_text(window, cx);
12046 self.refresh_inline_completion(true, false, window, cx);
12047 cx.emit(EditorEvent::Edited { transaction_id });
12048 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12049 }
12050 }
12051
12052 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12053 if self.read_only(cx) {
12054 return;
12055 }
12056
12057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12058
12059 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12060 if let Some((_, Some(selections))) =
12061 self.selection_history.transaction(transaction_id).cloned()
12062 {
12063 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12064 s.select_anchors(selections.to_vec());
12065 });
12066 } else {
12067 log::error!(
12068 "No entry in selection_history found for redo. \
12069 This may correspond to a bug where undo does not update the selection. \
12070 If this is occurring, please add details to \
12071 https://github.com/zed-industries/zed/issues/22692"
12072 );
12073 }
12074 self.request_autoscroll(Autoscroll::fit(), cx);
12075 self.unmark_text(window, cx);
12076 self.refresh_inline_completion(true, false, window, cx);
12077 cx.emit(EditorEvent::Edited { transaction_id });
12078 }
12079 }
12080
12081 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12082 self.buffer
12083 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12084 }
12085
12086 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12087 self.buffer
12088 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12089 }
12090
12091 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12093 self.change_selections(Default::default(), window, cx, |s| {
12094 s.move_with(|map, selection| {
12095 let cursor = if selection.is_empty() {
12096 movement::left(map, selection.start)
12097 } else {
12098 selection.start
12099 };
12100 selection.collapse_to(cursor, SelectionGoal::None);
12101 });
12102 })
12103 }
12104
12105 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12107 self.change_selections(Default::default(), window, cx, |s| {
12108 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12109 })
12110 }
12111
12112 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12114 self.change_selections(Default::default(), window, cx, |s| {
12115 s.move_with(|map, selection| {
12116 let cursor = if selection.is_empty() {
12117 movement::right(map, selection.end)
12118 } else {
12119 selection.end
12120 };
12121 selection.collapse_to(cursor, SelectionGoal::None)
12122 });
12123 })
12124 }
12125
12126 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12127 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12128 self.change_selections(Default::default(), window, cx, |s| {
12129 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12130 })
12131 }
12132
12133 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12134 if self.take_rename(true, window, cx).is_some() {
12135 return;
12136 }
12137
12138 if self.mode.is_single_line() {
12139 cx.propagate();
12140 return;
12141 }
12142
12143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12144
12145 let text_layout_details = &self.text_layout_details(window);
12146 let selection_count = self.selections.count();
12147 let first_selection = self.selections.first_anchor();
12148
12149 self.change_selections(Default::default(), window, cx, |s| {
12150 s.move_with(|map, selection| {
12151 if !selection.is_empty() {
12152 selection.goal = SelectionGoal::None;
12153 }
12154 let (cursor, goal) = movement::up(
12155 map,
12156 selection.start,
12157 selection.goal,
12158 false,
12159 text_layout_details,
12160 );
12161 selection.collapse_to(cursor, goal);
12162 });
12163 });
12164
12165 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12166 {
12167 cx.propagate();
12168 }
12169 }
12170
12171 pub fn move_up_by_lines(
12172 &mut self,
12173 action: &MoveUpByLines,
12174 window: &mut Window,
12175 cx: &mut Context<Self>,
12176 ) {
12177 if self.take_rename(true, window, cx).is_some() {
12178 return;
12179 }
12180
12181 if self.mode.is_single_line() {
12182 cx.propagate();
12183 return;
12184 }
12185
12186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12187
12188 let text_layout_details = &self.text_layout_details(window);
12189
12190 self.change_selections(Default::default(), window, cx, |s| {
12191 s.move_with(|map, selection| {
12192 if !selection.is_empty() {
12193 selection.goal = SelectionGoal::None;
12194 }
12195 let (cursor, goal) = movement::up_by_rows(
12196 map,
12197 selection.start,
12198 action.lines,
12199 selection.goal,
12200 false,
12201 text_layout_details,
12202 );
12203 selection.collapse_to(cursor, goal);
12204 });
12205 })
12206 }
12207
12208 pub fn move_down_by_lines(
12209 &mut self,
12210 action: &MoveDownByLines,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 if self.take_rename(true, window, cx).is_some() {
12215 return;
12216 }
12217
12218 if self.mode.is_single_line() {
12219 cx.propagate();
12220 return;
12221 }
12222
12223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12224
12225 let text_layout_details = &self.text_layout_details(window);
12226
12227 self.change_selections(Default::default(), window, cx, |s| {
12228 s.move_with(|map, selection| {
12229 if !selection.is_empty() {
12230 selection.goal = SelectionGoal::None;
12231 }
12232 let (cursor, goal) = movement::down_by_rows(
12233 map,
12234 selection.start,
12235 action.lines,
12236 selection.goal,
12237 false,
12238 text_layout_details,
12239 );
12240 selection.collapse_to(cursor, goal);
12241 });
12242 })
12243 }
12244
12245 pub fn select_down_by_lines(
12246 &mut self,
12247 action: &SelectDownByLines,
12248 window: &mut Window,
12249 cx: &mut Context<Self>,
12250 ) {
12251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12252 let text_layout_details = &self.text_layout_details(window);
12253 self.change_selections(Default::default(), window, cx, |s| {
12254 s.move_heads_with(|map, head, goal| {
12255 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12256 })
12257 })
12258 }
12259
12260 pub fn select_up_by_lines(
12261 &mut self,
12262 action: &SelectUpByLines,
12263 window: &mut Window,
12264 cx: &mut Context<Self>,
12265 ) {
12266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12267 let text_layout_details = &self.text_layout_details(window);
12268 self.change_selections(Default::default(), window, cx, |s| {
12269 s.move_heads_with(|map, head, goal| {
12270 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12271 })
12272 })
12273 }
12274
12275 pub fn select_page_up(
12276 &mut self,
12277 _: &SelectPageUp,
12278 window: &mut Window,
12279 cx: &mut Context<Self>,
12280 ) {
12281 let Some(row_count) = self.visible_row_count() else {
12282 return;
12283 };
12284
12285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12286
12287 let text_layout_details = &self.text_layout_details(window);
12288
12289 self.change_selections(Default::default(), window, cx, |s| {
12290 s.move_heads_with(|map, head, goal| {
12291 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12292 })
12293 })
12294 }
12295
12296 pub fn move_page_up(
12297 &mut self,
12298 action: &MovePageUp,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 if self.take_rename(true, window, cx).is_some() {
12303 return;
12304 }
12305
12306 if self
12307 .context_menu
12308 .borrow_mut()
12309 .as_mut()
12310 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12311 .unwrap_or(false)
12312 {
12313 return;
12314 }
12315
12316 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12317 cx.propagate();
12318 return;
12319 }
12320
12321 let Some(row_count) = self.visible_row_count() else {
12322 return;
12323 };
12324
12325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12326
12327 let effects = if action.center_cursor {
12328 SelectionEffects::scroll(Autoscroll::center())
12329 } else {
12330 SelectionEffects::default()
12331 };
12332
12333 let text_layout_details = &self.text_layout_details(window);
12334
12335 self.change_selections(effects, window, cx, |s| {
12336 s.move_with(|map, selection| {
12337 if !selection.is_empty() {
12338 selection.goal = SelectionGoal::None;
12339 }
12340 let (cursor, goal) = movement::up_by_rows(
12341 map,
12342 selection.end,
12343 row_count,
12344 selection.goal,
12345 false,
12346 text_layout_details,
12347 );
12348 selection.collapse_to(cursor, goal);
12349 });
12350 });
12351 }
12352
12353 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12355 let text_layout_details = &self.text_layout_details(window);
12356 self.change_selections(Default::default(), window, cx, |s| {
12357 s.move_heads_with(|map, head, goal| {
12358 movement::up(map, head, goal, false, text_layout_details)
12359 })
12360 })
12361 }
12362
12363 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12364 self.take_rename(true, window, cx);
12365
12366 if self.mode.is_single_line() {
12367 cx.propagate();
12368 return;
12369 }
12370
12371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12372
12373 let text_layout_details = &self.text_layout_details(window);
12374 let selection_count = self.selections.count();
12375 let first_selection = self.selections.first_anchor();
12376
12377 self.change_selections(Default::default(), window, cx, |s| {
12378 s.move_with(|map, selection| {
12379 if !selection.is_empty() {
12380 selection.goal = SelectionGoal::None;
12381 }
12382 let (cursor, goal) = movement::down(
12383 map,
12384 selection.end,
12385 selection.goal,
12386 false,
12387 text_layout_details,
12388 );
12389 selection.collapse_to(cursor, goal);
12390 });
12391 });
12392
12393 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12394 {
12395 cx.propagate();
12396 }
12397 }
12398
12399 pub fn select_page_down(
12400 &mut self,
12401 _: &SelectPageDown,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 let Some(row_count) = self.visible_row_count() else {
12406 return;
12407 };
12408
12409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12410
12411 let text_layout_details = &self.text_layout_details(window);
12412
12413 self.change_selections(Default::default(), window, cx, |s| {
12414 s.move_heads_with(|map, head, goal| {
12415 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12416 })
12417 })
12418 }
12419
12420 pub fn move_page_down(
12421 &mut self,
12422 action: &MovePageDown,
12423 window: &mut Window,
12424 cx: &mut Context<Self>,
12425 ) {
12426 if self.take_rename(true, window, cx).is_some() {
12427 return;
12428 }
12429
12430 if self
12431 .context_menu
12432 .borrow_mut()
12433 .as_mut()
12434 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12435 .unwrap_or(false)
12436 {
12437 return;
12438 }
12439
12440 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12441 cx.propagate();
12442 return;
12443 }
12444
12445 let Some(row_count) = self.visible_row_count() else {
12446 return;
12447 };
12448
12449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12450
12451 let effects = if action.center_cursor {
12452 SelectionEffects::scroll(Autoscroll::center())
12453 } else {
12454 SelectionEffects::default()
12455 };
12456
12457 let text_layout_details = &self.text_layout_details(window);
12458 self.change_selections(effects, window, cx, |s| {
12459 s.move_with(|map, selection| {
12460 if !selection.is_empty() {
12461 selection.goal = SelectionGoal::None;
12462 }
12463 let (cursor, goal) = movement::down_by_rows(
12464 map,
12465 selection.end,
12466 row_count,
12467 selection.goal,
12468 false,
12469 text_layout_details,
12470 );
12471 selection.collapse_to(cursor, goal);
12472 });
12473 });
12474 }
12475
12476 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12478 let text_layout_details = &self.text_layout_details(window);
12479 self.change_selections(Default::default(), window, cx, |s| {
12480 s.move_heads_with(|map, head, goal| {
12481 movement::down(map, head, goal, false, text_layout_details)
12482 })
12483 });
12484 }
12485
12486 pub fn context_menu_first(
12487 &mut self,
12488 _: &ContextMenuFirst,
12489 window: &mut Window,
12490 cx: &mut Context<Self>,
12491 ) {
12492 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12493 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12494 }
12495 }
12496
12497 pub fn context_menu_prev(
12498 &mut self,
12499 _: &ContextMenuPrevious,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) {
12503 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12504 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12505 }
12506 }
12507
12508 pub fn context_menu_next(
12509 &mut self,
12510 _: &ContextMenuNext,
12511 window: &mut Window,
12512 cx: &mut Context<Self>,
12513 ) {
12514 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12515 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12516 }
12517 }
12518
12519 pub fn context_menu_last(
12520 &mut self,
12521 _: &ContextMenuLast,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12526 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12527 }
12528 }
12529
12530 pub fn move_to_previous_word_start(
12531 &mut self,
12532 _: &MoveToPreviousWordStart,
12533 window: &mut Window,
12534 cx: &mut Context<Self>,
12535 ) {
12536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12537 self.change_selections(Default::default(), window, cx, |s| {
12538 s.move_cursors_with(|map, head, _| {
12539 (
12540 movement::previous_word_start(map, head),
12541 SelectionGoal::None,
12542 )
12543 });
12544 })
12545 }
12546
12547 pub fn move_to_previous_subword_start(
12548 &mut self,
12549 _: &MoveToPreviousSubwordStart,
12550 window: &mut Window,
12551 cx: &mut Context<Self>,
12552 ) {
12553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12554 self.change_selections(Default::default(), window, cx, |s| {
12555 s.move_cursors_with(|map, head, _| {
12556 (
12557 movement::previous_subword_start(map, head),
12558 SelectionGoal::None,
12559 )
12560 });
12561 })
12562 }
12563
12564 pub fn select_to_previous_word_start(
12565 &mut self,
12566 _: &SelectToPreviousWordStart,
12567 window: &mut Window,
12568 cx: &mut Context<Self>,
12569 ) {
12570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12571 self.change_selections(Default::default(), window, cx, |s| {
12572 s.move_heads_with(|map, head, _| {
12573 (
12574 movement::previous_word_start(map, head),
12575 SelectionGoal::None,
12576 )
12577 });
12578 })
12579 }
12580
12581 pub fn select_to_previous_subword_start(
12582 &mut self,
12583 _: &SelectToPreviousSubwordStart,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12588 self.change_selections(Default::default(), window, cx, |s| {
12589 s.move_heads_with(|map, head, _| {
12590 (
12591 movement::previous_subword_start(map, head),
12592 SelectionGoal::None,
12593 )
12594 });
12595 })
12596 }
12597
12598 pub fn delete_to_previous_word_start(
12599 &mut self,
12600 action: &DeleteToPreviousWordStart,
12601 window: &mut Window,
12602 cx: &mut Context<Self>,
12603 ) {
12604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12605 self.transact(window, cx, |this, window, cx| {
12606 this.select_autoclose_pair(window, cx);
12607 this.change_selections(Default::default(), window, cx, |s| {
12608 s.move_with(|map, selection| {
12609 if selection.is_empty() {
12610 let cursor = if action.ignore_newlines {
12611 movement::previous_word_start(map, selection.head())
12612 } else {
12613 movement::previous_word_start_or_newline(map, selection.head())
12614 };
12615 selection.set_head(cursor, SelectionGoal::None);
12616 }
12617 });
12618 });
12619 this.insert("", window, cx);
12620 });
12621 }
12622
12623 pub fn delete_to_previous_subword_start(
12624 &mut self,
12625 _: &DeleteToPreviousSubwordStart,
12626 window: &mut Window,
12627 cx: &mut Context<Self>,
12628 ) {
12629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12630 self.transact(window, cx, |this, window, cx| {
12631 this.select_autoclose_pair(window, cx);
12632 this.change_selections(Default::default(), window, cx, |s| {
12633 s.move_with(|map, selection| {
12634 if selection.is_empty() {
12635 let cursor = movement::previous_subword_start(map, selection.head());
12636 selection.set_head(cursor, SelectionGoal::None);
12637 }
12638 });
12639 });
12640 this.insert("", window, cx);
12641 });
12642 }
12643
12644 pub fn move_to_next_word_end(
12645 &mut self,
12646 _: &MoveToNextWordEnd,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) {
12650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12651 self.change_selections(Default::default(), window, cx, |s| {
12652 s.move_cursors_with(|map, head, _| {
12653 (movement::next_word_end(map, head), SelectionGoal::None)
12654 });
12655 })
12656 }
12657
12658 pub fn move_to_next_subword_end(
12659 &mut self,
12660 _: &MoveToNextSubwordEnd,
12661 window: &mut Window,
12662 cx: &mut Context<Self>,
12663 ) {
12664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12665 self.change_selections(Default::default(), window, cx, |s| {
12666 s.move_cursors_with(|map, head, _| {
12667 (movement::next_subword_end(map, head), SelectionGoal::None)
12668 });
12669 })
12670 }
12671
12672 pub fn select_to_next_word_end(
12673 &mut self,
12674 _: &SelectToNextWordEnd,
12675 window: &mut Window,
12676 cx: &mut Context<Self>,
12677 ) {
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 self.change_selections(Default::default(), window, cx, |s| {
12680 s.move_heads_with(|map, head, _| {
12681 (movement::next_word_end(map, head), SelectionGoal::None)
12682 });
12683 })
12684 }
12685
12686 pub fn select_to_next_subword_end(
12687 &mut self,
12688 _: &SelectToNextSubwordEnd,
12689 window: &mut Window,
12690 cx: &mut Context<Self>,
12691 ) {
12692 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12693 self.change_selections(Default::default(), window, cx, |s| {
12694 s.move_heads_with(|map, head, _| {
12695 (movement::next_subword_end(map, head), SelectionGoal::None)
12696 });
12697 })
12698 }
12699
12700 pub fn delete_to_next_word_end(
12701 &mut self,
12702 action: &DeleteToNextWordEnd,
12703 window: &mut Window,
12704 cx: &mut Context<Self>,
12705 ) {
12706 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12707 self.transact(window, cx, |this, window, cx| {
12708 this.change_selections(Default::default(), window, cx, |s| {
12709 s.move_with(|map, selection| {
12710 if selection.is_empty() {
12711 let cursor = if action.ignore_newlines {
12712 movement::next_word_end(map, selection.head())
12713 } else {
12714 movement::next_word_end_or_newline(map, selection.head())
12715 };
12716 selection.set_head(cursor, SelectionGoal::None);
12717 }
12718 });
12719 });
12720 this.insert("", window, cx);
12721 });
12722 }
12723
12724 pub fn delete_to_next_subword_end(
12725 &mut self,
12726 _: &DeleteToNextSubwordEnd,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12731 self.transact(window, cx, |this, window, cx| {
12732 this.change_selections(Default::default(), window, cx, |s| {
12733 s.move_with(|map, selection| {
12734 if selection.is_empty() {
12735 let cursor = movement::next_subword_end(map, selection.head());
12736 selection.set_head(cursor, SelectionGoal::None);
12737 }
12738 });
12739 });
12740 this.insert("", window, cx);
12741 });
12742 }
12743
12744 pub fn move_to_beginning_of_line(
12745 &mut self,
12746 action: &MoveToBeginningOfLine,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12751 self.change_selections(Default::default(), window, cx, |s| {
12752 s.move_cursors_with(|map, head, _| {
12753 (
12754 movement::indented_line_beginning(
12755 map,
12756 head,
12757 action.stop_at_soft_wraps,
12758 action.stop_at_indent,
12759 ),
12760 SelectionGoal::None,
12761 )
12762 });
12763 })
12764 }
12765
12766 pub fn select_to_beginning_of_line(
12767 &mut self,
12768 action: &SelectToBeginningOfLine,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 self.change_selections(Default::default(), window, cx, |s| {
12774 s.move_heads_with(|map, head, _| {
12775 (
12776 movement::indented_line_beginning(
12777 map,
12778 head,
12779 action.stop_at_soft_wraps,
12780 action.stop_at_indent,
12781 ),
12782 SelectionGoal::None,
12783 )
12784 });
12785 });
12786 }
12787
12788 pub fn delete_to_beginning_of_line(
12789 &mut self,
12790 action: &DeleteToBeginningOfLine,
12791 window: &mut Window,
12792 cx: &mut Context<Self>,
12793 ) {
12794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12795 self.transact(window, cx, |this, window, cx| {
12796 this.change_selections(Default::default(), window, cx, |s| {
12797 s.move_with(|_, selection| {
12798 selection.reversed = true;
12799 });
12800 });
12801
12802 this.select_to_beginning_of_line(
12803 &SelectToBeginningOfLine {
12804 stop_at_soft_wraps: false,
12805 stop_at_indent: action.stop_at_indent,
12806 },
12807 window,
12808 cx,
12809 );
12810 this.backspace(&Backspace, window, cx);
12811 });
12812 }
12813
12814 pub fn move_to_end_of_line(
12815 &mut self,
12816 action: &MoveToEndOfLine,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821 self.change_selections(Default::default(), window, cx, |s| {
12822 s.move_cursors_with(|map, head, _| {
12823 (
12824 movement::line_end(map, head, action.stop_at_soft_wraps),
12825 SelectionGoal::None,
12826 )
12827 });
12828 })
12829 }
12830
12831 pub fn select_to_end_of_line(
12832 &mut self,
12833 action: &SelectToEndOfLine,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12838 self.change_selections(Default::default(), window, cx, |s| {
12839 s.move_heads_with(|map, head, _| {
12840 (
12841 movement::line_end(map, head, action.stop_at_soft_wraps),
12842 SelectionGoal::None,
12843 )
12844 });
12845 })
12846 }
12847
12848 pub fn delete_to_end_of_line(
12849 &mut self,
12850 _: &DeleteToEndOfLine,
12851 window: &mut Window,
12852 cx: &mut Context<Self>,
12853 ) {
12854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12855 self.transact(window, cx, |this, window, cx| {
12856 this.select_to_end_of_line(
12857 &SelectToEndOfLine {
12858 stop_at_soft_wraps: false,
12859 },
12860 window,
12861 cx,
12862 );
12863 this.delete(&Delete, window, cx);
12864 });
12865 }
12866
12867 pub fn cut_to_end_of_line(
12868 &mut self,
12869 _: &CutToEndOfLine,
12870 window: &mut Window,
12871 cx: &mut Context<Self>,
12872 ) {
12873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12874 self.transact(window, cx, |this, window, cx| {
12875 this.select_to_end_of_line(
12876 &SelectToEndOfLine {
12877 stop_at_soft_wraps: false,
12878 },
12879 window,
12880 cx,
12881 );
12882 this.cut(&Cut, window, cx);
12883 });
12884 }
12885
12886 pub fn move_to_start_of_paragraph(
12887 &mut self,
12888 _: &MoveToStartOfParagraph,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) {
12892 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12893 cx.propagate();
12894 return;
12895 }
12896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12897 self.change_selections(Default::default(), window, cx, |s| {
12898 s.move_with(|map, selection| {
12899 selection.collapse_to(
12900 movement::start_of_paragraph(map, selection.head(), 1),
12901 SelectionGoal::None,
12902 )
12903 });
12904 })
12905 }
12906
12907 pub fn move_to_end_of_paragraph(
12908 &mut self,
12909 _: &MoveToEndOfParagraph,
12910 window: &mut Window,
12911 cx: &mut Context<Self>,
12912 ) {
12913 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12914 cx.propagate();
12915 return;
12916 }
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918 self.change_selections(Default::default(), window, cx, |s| {
12919 s.move_with(|map, selection| {
12920 selection.collapse_to(
12921 movement::end_of_paragraph(map, selection.head(), 1),
12922 SelectionGoal::None,
12923 )
12924 });
12925 })
12926 }
12927
12928 pub fn select_to_start_of_paragraph(
12929 &mut self,
12930 _: &SelectToStartOfParagraph,
12931 window: &mut Window,
12932 cx: &mut Context<Self>,
12933 ) {
12934 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12935 cx.propagate();
12936 return;
12937 }
12938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12939 self.change_selections(Default::default(), window, cx, |s| {
12940 s.move_heads_with(|map, head, _| {
12941 (
12942 movement::start_of_paragraph(map, head, 1),
12943 SelectionGoal::None,
12944 )
12945 });
12946 })
12947 }
12948
12949 pub fn select_to_end_of_paragraph(
12950 &mut self,
12951 _: &SelectToEndOfParagraph,
12952 window: &mut Window,
12953 cx: &mut Context<Self>,
12954 ) {
12955 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12956 cx.propagate();
12957 return;
12958 }
12959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12960 self.change_selections(Default::default(), window, cx, |s| {
12961 s.move_heads_with(|map, head, _| {
12962 (
12963 movement::end_of_paragraph(map, head, 1),
12964 SelectionGoal::None,
12965 )
12966 });
12967 })
12968 }
12969
12970 pub fn move_to_start_of_excerpt(
12971 &mut self,
12972 _: &MoveToStartOfExcerpt,
12973 window: &mut Window,
12974 cx: &mut Context<Self>,
12975 ) {
12976 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12977 cx.propagate();
12978 return;
12979 }
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 selection.collapse_to(
12984 movement::start_of_excerpt(
12985 map,
12986 selection.head(),
12987 workspace::searchable::Direction::Prev,
12988 ),
12989 SelectionGoal::None,
12990 )
12991 });
12992 })
12993 }
12994
12995 pub fn move_to_start_of_next_excerpt(
12996 &mut self,
12997 _: &MoveToStartOfNextExcerpt,
12998 window: &mut Window,
12999 cx: &mut Context<Self>,
13000 ) {
13001 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13002 cx.propagate();
13003 return;
13004 }
13005
13006 self.change_selections(Default::default(), window, cx, |s| {
13007 s.move_with(|map, selection| {
13008 selection.collapse_to(
13009 movement::start_of_excerpt(
13010 map,
13011 selection.head(),
13012 workspace::searchable::Direction::Next,
13013 ),
13014 SelectionGoal::None,
13015 )
13016 });
13017 })
13018 }
13019
13020 pub fn move_to_end_of_excerpt(
13021 &mut self,
13022 _: &MoveToEndOfExcerpt,
13023 window: &mut Window,
13024 cx: &mut Context<Self>,
13025 ) {
13026 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13027 cx.propagate();
13028 return;
13029 }
13030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13031 self.change_selections(Default::default(), window, cx, |s| {
13032 s.move_with(|map, selection| {
13033 selection.collapse_to(
13034 movement::end_of_excerpt(
13035 map,
13036 selection.head(),
13037 workspace::searchable::Direction::Next,
13038 ),
13039 SelectionGoal::None,
13040 )
13041 });
13042 })
13043 }
13044
13045 pub fn move_to_end_of_previous_excerpt(
13046 &mut self,
13047 _: &MoveToEndOfPreviousExcerpt,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13052 cx.propagate();
13053 return;
13054 }
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13056 self.change_selections(Default::default(), window, cx, |s| {
13057 s.move_with(|map, selection| {
13058 selection.collapse_to(
13059 movement::end_of_excerpt(
13060 map,
13061 selection.head(),
13062 workspace::searchable::Direction::Prev,
13063 ),
13064 SelectionGoal::None,
13065 )
13066 });
13067 })
13068 }
13069
13070 pub fn select_to_start_of_excerpt(
13071 &mut self,
13072 _: &SelectToStartOfExcerpt,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) {
13076 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13077 cx.propagate();
13078 return;
13079 }
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081 self.change_selections(Default::default(), window, cx, |s| {
13082 s.move_heads_with(|map, head, _| {
13083 (
13084 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13085 SelectionGoal::None,
13086 )
13087 });
13088 })
13089 }
13090
13091 pub fn select_to_start_of_next_excerpt(
13092 &mut self,
13093 _: &SelectToStartOfNextExcerpt,
13094 window: &mut Window,
13095 cx: &mut Context<Self>,
13096 ) {
13097 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13098 cx.propagate();
13099 return;
13100 }
13101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13102 self.change_selections(Default::default(), window, cx, |s| {
13103 s.move_heads_with(|map, head, _| {
13104 (
13105 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13106 SelectionGoal::None,
13107 )
13108 });
13109 })
13110 }
13111
13112 pub fn select_to_end_of_excerpt(
13113 &mut self,
13114 _: &SelectToEndOfExcerpt,
13115 window: &mut Window,
13116 cx: &mut Context<Self>,
13117 ) {
13118 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13119 cx.propagate();
13120 return;
13121 }
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 self.change_selections(Default::default(), window, cx, |s| {
13124 s.move_heads_with(|map, head, _| {
13125 (
13126 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13127 SelectionGoal::None,
13128 )
13129 });
13130 })
13131 }
13132
13133 pub fn select_to_end_of_previous_excerpt(
13134 &mut self,
13135 _: &SelectToEndOfPreviousExcerpt,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13140 cx.propagate();
13141 return;
13142 }
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13144 self.change_selections(Default::default(), window, cx, |s| {
13145 s.move_heads_with(|map, head, _| {
13146 (
13147 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13148 SelectionGoal::None,
13149 )
13150 });
13151 })
13152 }
13153
13154 pub fn move_to_beginning(
13155 &mut self,
13156 _: &MoveToBeginning,
13157 window: &mut Window,
13158 cx: &mut Context<Self>,
13159 ) {
13160 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13161 cx.propagate();
13162 return;
13163 }
13164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13165 self.change_selections(Default::default(), window, cx, |s| {
13166 s.select_ranges(vec![0..0]);
13167 });
13168 }
13169
13170 pub fn select_to_beginning(
13171 &mut self,
13172 _: &SelectToBeginning,
13173 window: &mut Window,
13174 cx: &mut Context<Self>,
13175 ) {
13176 let mut selection = self.selections.last::<Point>(cx);
13177 selection.set_head(Point::zero(), SelectionGoal::None);
13178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13179 self.change_selections(Default::default(), window, cx, |s| {
13180 s.select(vec![selection]);
13181 });
13182 }
13183
13184 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13185 if matches!(self.mode, EditorMode::SingleLine { .. }) {
13186 cx.propagate();
13187 return;
13188 }
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13190 let cursor = self.buffer.read(cx).read(cx).len();
13191 self.change_selections(Default::default(), window, cx, |s| {
13192 s.select_ranges(vec![cursor..cursor])
13193 });
13194 }
13195
13196 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13197 self.nav_history = nav_history;
13198 }
13199
13200 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13201 self.nav_history.as_ref()
13202 }
13203
13204 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13205 self.push_to_nav_history(
13206 self.selections.newest_anchor().head(),
13207 None,
13208 false,
13209 true,
13210 cx,
13211 );
13212 }
13213
13214 fn push_to_nav_history(
13215 &mut self,
13216 cursor_anchor: Anchor,
13217 new_position: Option<Point>,
13218 is_deactivate: bool,
13219 always: bool,
13220 cx: &mut Context<Self>,
13221 ) {
13222 if let Some(nav_history) = self.nav_history.as_mut() {
13223 let buffer = self.buffer.read(cx).read(cx);
13224 let cursor_position = cursor_anchor.to_point(&buffer);
13225 let scroll_state = self.scroll_manager.anchor();
13226 let scroll_top_row = scroll_state.top_row(&buffer);
13227 drop(buffer);
13228
13229 if let Some(new_position) = new_position {
13230 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13231 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13232 return;
13233 }
13234 }
13235
13236 nav_history.push(
13237 Some(NavigationData {
13238 cursor_anchor,
13239 cursor_position,
13240 scroll_anchor: scroll_state,
13241 scroll_top_row,
13242 }),
13243 cx,
13244 );
13245 cx.emit(EditorEvent::PushedToNavHistory {
13246 anchor: cursor_anchor,
13247 is_deactivate,
13248 })
13249 }
13250 }
13251
13252 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13254 let buffer = self.buffer.read(cx).snapshot(cx);
13255 let mut selection = self.selections.first::<usize>(cx);
13256 selection.set_head(buffer.len(), SelectionGoal::None);
13257 self.change_selections(Default::default(), window, cx, |s| {
13258 s.select(vec![selection]);
13259 });
13260 }
13261
13262 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13264 let end = self.buffer.read(cx).read(cx).len();
13265 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13266 s.select_ranges(vec![0..end]);
13267 });
13268 }
13269
13270 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13273 let mut selections = self.selections.all::<Point>(cx);
13274 let max_point = display_map.buffer_snapshot.max_point();
13275 for selection in &mut selections {
13276 let rows = selection.spanned_rows(true, &display_map);
13277 selection.start = Point::new(rows.start.0, 0);
13278 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13279 selection.reversed = false;
13280 }
13281 self.change_selections(Default::default(), window, cx, |s| {
13282 s.select(selections);
13283 });
13284 }
13285
13286 pub fn split_selection_into_lines(
13287 &mut self,
13288 _: &SplitSelectionIntoLines,
13289 window: &mut Window,
13290 cx: &mut Context<Self>,
13291 ) {
13292 let selections = self
13293 .selections
13294 .all::<Point>(cx)
13295 .into_iter()
13296 .map(|selection| selection.start..selection.end)
13297 .collect::<Vec<_>>();
13298 self.unfold_ranges(&selections, true, true, cx);
13299
13300 let mut new_selection_ranges = Vec::new();
13301 {
13302 let buffer = self.buffer.read(cx).read(cx);
13303 for selection in selections {
13304 for row in selection.start.row..selection.end.row {
13305 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13306 new_selection_ranges.push(cursor..cursor);
13307 }
13308
13309 let is_multiline_selection = selection.start.row != selection.end.row;
13310 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13311 // so this action feels more ergonomic when paired with other selection operations
13312 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13313 if !should_skip_last {
13314 new_selection_ranges.push(selection.end..selection.end);
13315 }
13316 }
13317 }
13318 self.change_selections(Default::default(), window, cx, |s| {
13319 s.select_ranges(new_selection_ranges);
13320 });
13321 }
13322
13323 pub fn add_selection_above(
13324 &mut self,
13325 _: &AddSelectionAbove,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 self.add_selection(true, window, cx);
13330 }
13331
13332 pub fn add_selection_below(
13333 &mut self,
13334 _: &AddSelectionBelow,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.add_selection(false, window, cx);
13339 }
13340
13341 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343
13344 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13345 let all_selections = self.selections.all::<Point>(cx);
13346 let text_layout_details = self.text_layout_details(window);
13347
13348 let (mut columnar_selections, new_selections_to_columnarize) = {
13349 if let Some(state) = self.add_selections_state.as_ref() {
13350 let columnar_selection_ids: HashSet<_> = state
13351 .groups
13352 .iter()
13353 .flat_map(|group| group.stack.iter())
13354 .copied()
13355 .collect();
13356
13357 all_selections
13358 .into_iter()
13359 .partition(|s| columnar_selection_ids.contains(&s.id))
13360 } else {
13361 (Vec::new(), all_selections)
13362 }
13363 };
13364
13365 let mut state = self
13366 .add_selections_state
13367 .take()
13368 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13369
13370 for selection in new_selections_to_columnarize {
13371 let range = selection.display_range(&display_map).sorted();
13372 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13373 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13374 let positions = start_x.min(end_x)..start_x.max(end_x);
13375 let mut stack = Vec::new();
13376 for row in range.start.row().0..=range.end.row().0 {
13377 if let Some(selection) = self.selections.build_columnar_selection(
13378 &display_map,
13379 DisplayRow(row),
13380 &positions,
13381 selection.reversed,
13382 &text_layout_details,
13383 ) {
13384 stack.push(selection.id);
13385 columnar_selections.push(selection);
13386 }
13387 }
13388 if !stack.is_empty() {
13389 if above {
13390 stack.reverse();
13391 }
13392 state.groups.push(AddSelectionsGroup { above, stack });
13393 }
13394 }
13395
13396 let mut final_selections = Vec::new();
13397 let end_row = if above {
13398 DisplayRow(0)
13399 } else {
13400 display_map.max_point().row()
13401 };
13402
13403 let mut last_added_item_per_group = HashMap::default();
13404 for group in state.groups.iter_mut() {
13405 if let Some(last_id) = group.stack.last() {
13406 last_added_item_per_group.insert(*last_id, group);
13407 }
13408 }
13409
13410 for selection in columnar_selections {
13411 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13412 if above == group.above {
13413 let range = selection.display_range(&display_map).sorted();
13414 debug_assert_eq!(range.start.row(), range.end.row());
13415 let mut row = range.start.row();
13416 let positions =
13417 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13418 px(start)..px(end)
13419 } else {
13420 let start_x =
13421 display_map.x_for_display_point(range.start, &text_layout_details);
13422 let end_x =
13423 display_map.x_for_display_point(range.end, &text_layout_details);
13424 start_x.min(end_x)..start_x.max(end_x)
13425 };
13426
13427 let mut maybe_new_selection = None;
13428 while row != end_row {
13429 if above {
13430 row.0 -= 1;
13431 } else {
13432 row.0 += 1;
13433 }
13434 if let Some(new_selection) = self.selections.build_columnar_selection(
13435 &display_map,
13436 row,
13437 &positions,
13438 selection.reversed,
13439 &text_layout_details,
13440 ) {
13441 maybe_new_selection = Some(new_selection);
13442 break;
13443 }
13444 }
13445
13446 if let Some(new_selection) = maybe_new_selection {
13447 group.stack.push(new_selection.id);
13448 if above {
13449 final_selections.push(new_selection);
13450 final_selections.push(selection);
13451 } else {
13452 final_selections.push(selection);
13453 final_selections.push(new_selection);
13454 }
13455 } else {
13456 final_selections.push(selection);
13457 }
13458 } else {
13459 group.stack.pop();
13460 }
13461 } else {
13462 final_selections.push(selection);
13463 }
13464 }
13465
13466 self.change_selections(Default::default(), window, cx, |s| {
13467 s.select(final_selections);
13468 });
13469
13470 let final_selection_ids: HashSet<_> = self
13471 .selections
13472 .all::<Point>(cx)
13473 .iter()
13474 .map(|s| s.id)
13475 .collect();
13476 state.groups.retain_mut(|group| {
13477 // selections might get merged above so we remove invalid items from stacks
13478 group.stack.retain(|id| final_selection_ids.contains(id));
13479
13480 // single selection in stack can be treated as initial state
13481 group.stack.len() > 1
13482 });
13483
13484 if !state.groups.is_empty() {
13485 self.add_selections_state = Some(state);
13486 }
13487 }
13488
13489 fn select_match_ranges(
13490 &mut self,
13491 range: Range<usize>,
13492 reversed: bool,
13493 replace_newest: bool,
13494 auto_scroll: Option<Autoscroll>,
13495 window: &mut Window,
13496 cx: &mut Context<Editor>,
13497 ) {
13498 self.unfold_ranges(
13499 std::slice::from_ref(&range),
13500 false,
13501 auto_scroll.is_some(),
13502 cx,
13503 );
13504 let effects = if let Some(scroll) = auto_scroll {
13505 SelectionEffects::scroll(scroll)
13506 } else {
13507 SelectionEffects::no_scroll()
13508 };
13509 self.change_selections(effects, window, cx, |s| {
13510 if replace_newest {
13511 s.delete(s.newest_anchor().id);
13512 }
13513 if reversed {
13514 s.insert_range(range.end..range.start);
13515 } else {
13516 s.insert_range(range);
13517 }
13518 });
13519 }
13520
13521 pub fn select_next_match_internal(
13522 &mut self,
13523 display_map: &DisplaySnapshot,
13524 replace_newest: bool,
13525 autoscroll: Option<Autoscroll>,
13526 window: &mut Window,
13527 cx: &mut Context<Self>,
13528 ) -> Result<()> {
13529 let buffer = &display_map.buffer_snapshot;
13530 let mut selections = self.selections.all::<usize>(cx);
13531 if let Some(mut select_next_state) = self.select_next_state.take() {
13532 let query = &select_next_state.query;
13533 if !select_next_state.done {
13534 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13535 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13536 let mut next_selected_range = None;
13537
13538 let bytes_after_last_selection =
13539 buffer.bytes_in_range(last_selection.end..buffer.len());
13540 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13541 let query_matches = query
13542 .stream_find_iter(bytes_after_last_selection)
13543 .map(|result| (last_selection.end, result))
13544 .chain(
13545 query
13546 .stream_find_iter(bytes_before_first_selection)
13547 .map(|result| (0, result)),
13548 );
13549
13550 for (start_offset, query_match) in query_matches {
13551 let query_match = query_match.unwrap(); // can only fail due to I/O
13552 let offset_range =
13553 start_offset + query_match.start()..start_offset + query_match.end();
13554
13555 if !select_next_state.wordwise
13556 || (!buffer.is_inside_word(offset_range.start, false)
13557 && !buffer.is_inside_word(offset_range.end, false))
13558 {
13559 // TODO: This is n^2, because we might check all the selections
13560 if !selections
13561 .iter()
13562 .any(|selection| selection.range().overlaps(&offset_range))
13563 {
13564 next_selected_range = Some(offset_range);
13565 break;
13566 }
13567 }
13568 }
13569
13570 if let Some(next_selected_range) = next_selected_range {
13571 self.select_match_ranges(
13572 next_selected_range,
13573 last_selection.reversed,
13574 replace_newest,
13575 autoscroll,
13576 window,
13577 cx,
13578 );
13579 } else {
13580 select_next_state.done = true;
13581 }
13582 }
13583
13584 self.select_next_state = Some(select_next_state);
13585 } else {
13586 let mut only_carets = true;
13587 let mut same_text_selected = true;
13588 let mut selected_text = None;
13589
13590 let mut selections_iter = selections.iter().peekable();
13591 while let Some(selection) = selections_iter.next() {
13592 if selection.start != selection.end {
13593 only_carets = false;
13594 }
13595
13596 if same_text_selected {
13597 if selected_text.is_none() {
13598 selected_text =
13599 Some(buffer.text_for_range(selection.range()).collect::<String>());
13600 }
13601
13602 if let Some(next_selection) = selections_iter.peek() {
13603 if next_selection.range().len() == selection.range().len() {
13604 let next_selected_text = buffer
13605 .text_for_range(next_selection.range())
13606 .collect::<String>();
13607 if Some(next_selected_text) != selected_text {
13608 same_text_selected = false;
13609 selected_text = None;
13610 }
13611 } else {
13612 same_text_selected = false;
13613 selected_text = None;
13614 }
13615 }
13616 }
13617 }
13618
13619 if only_carets {
13620 for selection in &mut selections {
13621 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13622 selection.start = word_range.start;
13623 selection.end = word_range.end;
13624 selection.goal = SelectionGoal::None;
13625 selection.reversed = false;
13626 self.select_match_ranges(
13627 selection.start..selection.end,
13628 selection.reversed,
13629 replace_newest,
13630 autoscroll,
13631 window,
13632 cx,
13633 );
13634 }
13635
13636 if selections.len() == 1 {
13637 let selection = selections
13638 .last()
13639 .expect("ensured that there's only one selection");
13640 let query = buffer
13641 .text_for_range(selection.start..selection.end)
13642 .collect::<String>();
13643 let is_empty = query.is_empty();
13644 let select_state = SelectNextState {
13645 query: AhoCorasick::new(&[query])?,
13646 wordwise: true,
13647 done: is_empty,
13648 };
13649 self.select_next_state = Some(select_state);
13650 } else {
13651 self.select_next_state = None;
13652 }
13653 } else if let Some(selected_text) = selected_text {
13654 self.select_next_state = Some(SelectNextState {
13655 query: AhoCorasick::new(&[selected_text])?,
13656 wordwise: false,
13657 done: false,
13658 });
13659 self.select_next_match_internal(
13660 display_map,
13661 replace_newest,
13662 autoscroll,
13663 window,
13664 cx,
13665 )?;
13666 }
13667 }
13668 Ok(())
13669 }
13670
13671 pub fn select_all_matches(
13672 &mut self,
13673 _action: &SelectAllMatches,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) -> Result<()> {
13677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13678
13679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13680
13681 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13682 let Some(select_next_state) = self.select_next_state.as_mut() else {
13683 return Ok(());
13684 };
13685 if select_next_state.done {
13686 return Ok(());
13687 }
13688
13689 let mut new_selections = Vec::new();
13690
13691 let reversed = self.selections.oldest::<usize>(cx).reversed;
13692 let buffer = &display_map.buffer_snapshot;
13693 let query_matches = select_next_state
13694 .query
13695 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13696
13697 for query_match in query_matches.into_iter() {
13698 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13699 let offset_range = if reversed {
13700 query_match.end()..query_match.start()
13701 } else {
13702 query_match.start()..query_match.end()
13703 };
13704
13705 if !select_next_state.wordwise
13706 || (!buffer.is_inside_word(offset_range.start, false)
13707 && !buffer.is_inside_word(offset_range.end, false))
13708 {
13709 new_selections.push(offset_range.start..offset_range.end);
13710 }
13711 }
13712
13713 select_next_state.done = true;
13714
13715 if new_selections.is_empty() {
13716 log::error!("bug: new_selections is empty in select_all_matches");
13717 return Ok(());
13718 }
13719
13720 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
13722 selections.select_ranges(new_selections)
13723 });
13724
13725 Ok(())
13726 }
13727
13728 pub fn select_next(
13729 &mut self,
13730 action: &SelectNext,
13731 window: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) -> Result<()> {
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13736 self.select_next_match_internal(
13737 &display_map,
13738 action.replace_newest,
13739 Some(Autoscroll::newest()),
13740 window,
13741 cx,
13742 )?;
13743 Ok(())
13744 }
13745
13746 pub fn select_previous(
13747 &mut self,
13748 action: &SelectPrevious,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) -> Result<()> {
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13754 let buffer = &display_map.buffer_snapshot;
13755 let mut selections = self.selections.all::<usize>(cx);
13756 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13757 let query = &select_prev_state.query;
13758 if !select_prev_state.done {
13759 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13760 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13761 let mut next_selected_range = None;
13762 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13763 let bytes_before_last_selection =
13764 buffer.reversed_bytes_in_range(0..last_selection.start);
13765 let bytes_after_first_selection =
13766 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13767 let query_matches = query
13768 .stream_find_iter(bytes_before_last_selection)
13769 .map(|result| (last_selection.start, result))
13770 .chain(
13771 query
13772 .stream_find_iter(bytes_after_first_selection)
13773 .map(|result| (buffer.len(), result)),
13774 );
13775 for (end_offset, query_match) in query_matches {
13776 let query_match = query_match.unwrap(); // can only fail due to I/O
13777 let offset_range =
13778 end_offset - query_match.end()..end_offset - query_match.start();
13779
13780 if !select_prev_state.wordwise
13781 || (!buffer.is_inside_word(offset_range.start, false)
13782 && !buffer.is_inside_word(offset_range.end, false))
13783 {
13784 next_selected_range = Some(offset_range);
13785 break;
13786 }
13787 }
13788
13789 if let Some(next_selected_range) = next_selected_range {
13790 self.select_match_ranges(
13791 next_selected_range,
13792 last_selection.reversed,
13793 action.replace_newest,
13794 Some(Autoscroll::newest()),
13795 window,
13796 cx,
13797 );
13798 } else {
13799 select_prev_state.done = true;
13800 }
13801 }
13802
13803 self.select_prev_state = Some(select_prev_state);
13804 } else {
13805 let mut only_carets = true;
13806 let mut same_text_selected = true;
13807 let mut selected_text = None;
13808
13809 let mut selections_iter = selections.iter().peekable();
13810 while let Some(selection) = selections_iter.next() {
13811 if selection.start != selection.end {
13812 only_carets = false;
13813 }
13814
13815 if same_text_selected {
13816 if selected_text.is_none() {
13817 selected_text =
13818 Some(buffer.text_for_range(selection.range()).collect::<String>());
13819 }
13820
13821 if let Some(next_selection) = selections_iter.peek() {
13822 if next_selection.range().len() == selection.range().len() {
13823 let next_selected_text = buffer
13824 .text_for_range(next_selection.range())
13825 .collect::<String>();
13826 if Some(next_selected_text) != selected_text {
13827 same_text_selected = false;
13828 selected_text = None;
13829 }
13830 } else {
13831 same_text_selected = false;
13832 selected_text = None;
13833 }
13834 }
13835 }
13836 }
13837
13838 if only_carets {
13839 for selection in &mut selections {
13840 let (word_range, _) = buffer.surrounding_word(selection.start, false);
13841 selection.start = word_range.start;
13842 selection.end = word_range.end;
13843 selection.goal = SelectionGoal::None;
13844 selection.reversed = false;
13845 self.select_match_ranges(
13846 selection.start..selection.end,
13847 selection.reversed,
13848 action.replace_newest,
13849 Some(Autoscroll::newest()),
13850 window,
13851 cx,
13852 );
13853 }
13854 if selections.len() == 1 {
13855 let selection = selections
13856 .last()
13857 .expect("ensured that there's only one selection");
13858 let query = buffer
13859 .text_for_range(selection.start..selection.end)
13860 .collect::<String>();
13861 let is_empty = query.is_empty();
13862 let select_state = SelectNextState {
13863 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13864 wordwise: true,
13865 done: is_empty,
13866 };
13867 self.select_prev_state = Some(select_state);
13868 } else {
13869 self.select_prev_state = None;
13870 }
13871 } else if let Some(selected_text) = selected_text {
13872 self.select_prev_state = Some(SelectNextState {
13873 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13874 wordwise: false,
13875 done: false,
13876 });
13877 self.select_previous(action, window, cx)?;
13878 }
13879 }
13880 Ok(())
13881 }
13882
13883 pub fn find_next_match(
13884 &mut self,
13885 _: &FindNextMatch,
13886 window: &mut Window,
13887 cx: &mut Context<Self>,
13888 ) -> Result<()> {
13889 let selections = self.selections.disjoint_anchors();
13890 match selections.first() {
13891 Some(first) if selections.len() >= 2 => {
13892 self.change_selections(Default::default(), window, cx, |s| {
13893 s.select_ranges([first.range()]);
13894 });
13895 }
13896 _ => self.select_next(
13897 &SelectNext {
13898 replace_newest: true,
13899 },
13900 window,
13901 cx,
13902 )?,
13903 }
13904 Ok(())
13905 }
13906
13907 pub fn find_previous_match(
13908 &mut self,
13909 _: &FindPreviousMatch,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) -> Result<()> {
13913 let selections = self.selections.disjoint_anchors();
13914 match selections.last() {
13915 Some(last) if selections.len() >= 2 => {
13916 self.change_selections(Default::default(), window, cx, |s| {
13917 s.select_ranges([last.range()]);
13918 });
13919 }
13920 _ => self.select_previous(
13921 &SelectPrevious {
13922 replace_newest: true,
13923 },
13924 window,
13925 cx,
13926 )?,
13927 }
13928 Ok(())
13929 }
13930
13931 pub fn toggle_comments(
13932 &mut self,
13933 action: &ToggleComments,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 if self.read_only(cx) {
13938 return;
13939 }
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13941 let text_layout_details = &self.text_layout_details(window);
13942 self.transact(window, cx, |this, window, cx| {
13943 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13944 let mut edits = Vec::new();
13945 let mut selection_edit_ranges = Vec::new();
13946 let mut last_toggled_row = None;
13947 let snapshot = this.buffer.read(cx).read(cx);
13948 let empty_str: Arc<str> = Arc::default();
13949 let mut suffixes_inserted = Vec::new();
13950 let ignore_indent = action.ignore_indent;
13951
13952 fn comment_prefix_range(
13953 snapshot: &MultiBufferSnapshot,
13954 row: MultiBufferRow,
13955 comment_prefix: &str,
13956 comment_prefix_whitespace: &str,
13957 ignore_indent: bool,
13958 ) -> Range<Point> {
13959 let indent_size = if ignore_indent {
13960 0
13961 } else {
13962 snapshot.indent_size_for_line(row).len
13963 };
13964
13965 let start = Point::new(row.0, indent_size);
13966
13967 let mut line_bytes = snapshot
13968 .bytes_in_range(start..snapshot.max_point())
13969 .flatten()
13970 .copied();
13971
13972 // If this line currently begins with the line comment prefix, then record
13973 // the range containing the prefix.
13974 if line_bytes
13975 .by_ref()
13976 .take(comment_prefix.len())
13977 .eq(comment_prefix.bytes())
13978 {
13979 // Include any whitespace that matches the comment prefix.
13980 let matching_whitespace_len = line_bytes
13981 .zip(comment_prefix_whitespace.bytes())
13982 .take_while(|(a, b)| a == b)
13983 .count() as u32;
13984 let end = Point::new(
13985 start.row,
13986 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13987 );
13988 start..end
13989 } else {
13990 start..start
13991 }
13992 }
13993
13994 fn comment_suffix_range(
13995 snapshot: &MultiBufferSnapshot,
13996 row: MultiBufferRow,
13997 comment_suffix: &str,
13998 comment_suffix_has_leading_space: bool,
13999 ) -> Range<Point> {
14000 let end = Point::new(row.0, snapshot.line_len(row));
14001 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14002
14003 let mut line_end_bytes = snapshot
14004 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14005 .flatten()
14006 .copied();
14007
14008 let leading_space_len = if suffix_start_column > 0
14009 && line_end_bytes.next() == Some(b' ')
14010 && comment_suffix_has_leading_space
14011 {
14012 1
14013 } else {
14014 0
14015 };
14016
14017 // If this line currently begins with the line comment prefix, then record
14018 // the range containing the prefix.
14019 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14020 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14021 start..end
14022 } else {
14023 end..end
14024 }
14025 }
14026
14027 // TODO: Handle selections that cross excerpts
14028 for selection in &mut selections {
14029 let start_column = snapshot
14030 .indent_size_for_line(MultiBufferRow(selection.start.row))
14031 .len;
14032 let language = if let Some(language) =
14033 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14034 {
14035 language
14036 } else {
14037 continue;
14038 };
14039
14040 selection_edit_ranges.clear();
14041
14042 // If multiple selections contain a given row, avoid processing that
14043 // row more than once.
14044 let mut start_row = MultiBufferRow(selection.start.row);
14045 if last_toggled_row == Some(start_row) {
14046 start_row = start_row.next_row();
14047 }
14048 let end_row =
14049 if selection.end.row > selection.start.row && selection.end.column == 0 {
14050 MultiBufferRow(selection.end.row - 1)
14051 } else {
14052 MultiBufferRow(selection.end.row)
14053 };
14054 last_toggled_row = Some(end_row);
14055
14056 if start_row > end_row {
14057 continue;
14058 }
14059
14060 // If the language has line comments, toggle those.
14061 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14062
14063 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14064 if ignore_indent {
14065 full_comment_prefixes = full_comment_prefixes
14066 .into_iter()
14067 .map(|s| Arc::from(s.trim_end()))
14068 .collect();
14069 }
14070
14071 if !full_comment_prefixes.is_empty() {
14072 let first_prefix = full_comment_prefixes
14073 .first()
14074 .expect("prefixes is non-empty");
14075 let prefix_trimmed_lengths = full_comment_prefixes
14076 .iter()
14077 .map(|p| p.trim_end_matches(' ').len())
14078 .collect::<SmallVec<[usize; 4]>>();
14079
14080 let mut all_selection_lines_are_comments = true;
14081
14082 for row in start_row.0..=end_row.0 {
14083 let row = MultiBufferRow(row);
14084 if start_row < end_row && snapshot.is_line_blank(row) {
14085 continue;
14086 }
14087
14088 let prefix_range = full_comment_prefixes
14089 .iter()
14090 .zip(prefix_trimmed_lengths.iter().copied())
14091 .map(|(prefix, trimmed_prefix_len)| {
14092 comment_prefix_range(
14093 snapshot.deref(),
14094 row,
14095 &prefix[..trimmed_prefix_len],
14096 &prefix[trimmed_prefix_len..],
14097 ignore_indent,
14098 )
14099 })
14100 .max_by_key(|range| range.end.column - range.start.column)
14101 .expect("prefixes is non-empty");
14102
14103 if prefix_range.is_empty() {
14104 all_selection_lines_are_comments = false;
14105 }
14106
14107 selection_edit_ranges.push(prefix_range);
14108 }
14109
14110 if all_selection_lines_are_comments {
14111 edits.extend(
14112 selection_edit_ranges
14113 .iter()
14114 .cloned()
14115 .map(|range| (range, empty_str.clone())),
14116 );
14117 } else {
14118 let min_column = selection_edit_ranges
14119 .iter()
14120 .map(|range| range.start.column)
14121 .min()
14122 .unwrap_or(0);
14123 edits.extend(selection_edit_ranges.iter().map(|range| {
14124 let position = Point::new(range.start.row, min_column);
14125 (position..position, first_prefix.clone())
14126 }));
14127 }
14128 } else if let Some((full_comment_prefix, comment_suffix)) =
14129 language.block_comment_delimiters()
14130 {
14131 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14132 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14133 let prefix_range = comment_prefix_range(
14134 snapshot.deref(),
14135 start_row,
14136 comment_prefix,
14137 comment_prefix_whitespace,
14138 ignore_indent,
14139 );
14140 let suffix_range = comment_suffix_range(
14141 snapshot.deref(),
14142 end_row,
14143 comment_suffix.trim_start_matches(' '),
14144 comment_suffix.starts_with(' '),
14145 );
14146
14147 if prefix_range.is_empty() || suffix_range.is_empty() {
14148 edits.push((
14149 prefix_range.start..prefix_range.start,
14150 full_comment_prefix.clone(),
14151 ));
14152 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14153 suffixes_inserted.push((end_row, comment_suffix.len()));
14154 } else {
14155 edits.push((prefix_range, empty_str.clone()));
14156 edits.push((suffix_range, empty_str.clone()));
14157 }
14158 } else {
14159 continue;
14160 }
14161 }
14162
14163 drop(snapshot);
14164 this.buffer.update(cx, |buffer, cx| {
14165 buffer.edit(edits, None, cx);
14166 });
14167
14168 // Adjust selections so that they end before any comment suffixes that
14169 // were inserted.
14170 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14171 let mut selections = this.selections.all::<Point>(cx);
14172 let snapshot = this.buffer.read(cx).read(cx);
14173 for selection in &mut selections {
14174 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14175 match row.cmp(&MultiBufferRow(selection.end.row)) {
14176 Ordering::Less => {
14177 suffixes_inserted.next();
14178 continue;
14179 }
14180 Ordering::Greater => break,
14181 Ordering::Equal => {
14182 if selection.end.column == snapshot.line_len(row) {
14183 if selection.is_empty() {
14184 selection.start.column -= suffix_len as u32;
14185 }
14186 selection.end.column -= suffix_len as u32;
14187 }
14188 break;
14189 }
14190 }
14191 }
14192 }
14193
14194 drop(snapshot);
14195 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14196
14197 let selections = this.selections.all::<Point>(cx);
14198 let selections_on_single_row = selections.windows(2).all(|selections| {
14199 selections[0].start.row == selections[1].start.row
14200 && selections[0].end.row == selections[1].end.row
14201 && selections[0].start.row == selections[0].end.row
14202 });
14203 let selections_selecting = selections
14204 .iter()
14205 .any(|selection| selection.start != selection.end);
14206 let advance_downwards = action.advance_downwards
14207 && selections_on_single_row
14208 && !selections_selecting
14209 && !matches!(this.mode, EditorMode::SingleLine { .. });
14210
14211 if advance_downwards {
14212 let snapshot = this.buffer.read(cx).snapshot(cx);
14213
14214 this.change_selections(Default::default(), window, cx, |s| {
14215 s.move_cursors_with(|display_snapshot, display_point, _| {
14216 let mut point = display_point.to_point(display_snapshot);
14217 point.row += 1;
14218 point = snapshot.clip_point(point, Bias::Left);
14219 let display_point = point.to_display_point(display_snapshot);
14220 let goal = SelectionGoal::HorizontalPosition(
14221 display_snapshot
14222 .x_for_display_point(display_point, text_layout_details)
14223 .into(),
14224 );
14225 (display_point, goal)
14226 })
14227 });
14228 }
14229 });
14230 }
14231
14232 pub fn select_enclosing_symbol(
14233 &mut self,
14234 _: &SelectEnclosingSymbol,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) {
14238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14239
14240 let buffer = self.buffer.read(cx).snapshot(cx);
14241 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14242
14243 fn update_selection(
14244 selection: &Selection<usize>,
14245 buffer_snap: &MultiBufferSnapshot,
14246 ) -> Option<Selection<usize>> {
14247 let cursor = selection.head();
14248 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14249 for symbol in symbols.iter().rev() {
14250 let start = symbol.range.start.to_offset(buffer_snap);
14251 let end = symbol.range.end.to_offset(buffer_snap);
14252 let new_range = start..end;
14253 if start < selection.start || end > selection.end {
14254 return Some(Selection {
14255 id: selection.id,
14256 start: new_range.start,
14257 end: new_range.end,
14258 goal: SelectionGoal::None,
14259 reversed: selection.reversed,
14260 });
14261 }
14262 }
14263 None
14264 }
14265
14266 let mut selected_larger_symbol = false;
14267 let new_selections = old_selections
14268 .iter()
14269 .map(|selection| match update_selection(selection, &buffer) {
14270 Some(new_selection) => {
14271 if new_selection.range() != selection.range() {
14272 selected_larger_symbol = true;
14273 }
14274 new_selection
14275 }
14276 None => selection.clone(),
14277 })
14278 .collect::<Vec<_>>();
14279
14280 if selected_larger_symbol {
14281 self.change_selections(Default::default(), window, cx, |s| {
14282 s.select(new_selections);
14283 });
14284 }
14285 }
14286
14287 pub fn select_larger_syntax_node(
14288 &mut self,
14289 _: &SelectLargerSyntaxNode,
14290 window: &mut Window,
14291 cx: &mut Context<Self>,
14292 ) {
14293 let Some(visible_row_count) = self.visible_row_count() else {
14294 return;
14295 };
14296 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
14297 if old_selections.is_empty() {
14298 return;
14299 }
14300
14301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14302
14303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14304 let buffer = self.buffer.read(cx).snapshot(cx);
14305
14306 let mut selected_larger_node = false;
14307 let mut new_selections = old_selections
14308 .iter()
14309 .map(|selection| {
14310 let old_range = selection.start..selection.end;
14311
14312 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14313 // manually select word at selection
14314 if ["string_content", "inline"].contains(&node.kind()) {
14315 let (word_range, _) = buffer.surrounding_word(old_range.start, false);
14316 // ignore if word is already selected
14317 if !word_range.is_empty() && old_range != word_range {
14318 let (last_word_range, _) =
14319 buffer.surrounding_word(old_range.end, false);
14320 // only select word if start and end point belongs to same word
14321 if word_range == last_word_range {
14322 selected_larger_node = true;
14323 return Selection {
14324 id: selection.id,
14325 start: word_range.start,
14326 end: word_range.end,
14327 goal: SelectionGoal::None,
14328 reversed: selection.reversed,
14329 };
14330 }
14331 }
14332 }
14333 }
14334
14335 let mut new_range = old_range.clone();
14336 while let Some((_node, containing_range)) =
14337 buffer.syntax_ancestor(new_range.clone())
14338 {
14339 new_range = match containing_range {
14340 MultiOrSingleBufferOffsetRange::Single(_) => break,
14341 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14342 };
14343 if !display_map.intersects_fold(new_range.start)
14344 && !display_map.intersects_fold(new_range.end)
14345 {
14346 break;
14347 }
14348 }
14349
14350 selected_larger_node |= new_range != old_range;
14351 Selection {
14352 id: selection.id,
14353 start: new_range.start,
14354 end: new_range.end,
14355 goal: SelectionGoal::None,
14356 reversed: selection.reversed,
14357 }
14358 })
14359 .collect::<Vec<_>>();
14360
14361 if !selected_larger_node {
14362 return; // don't put this call in the history
14363 }
14364
14365 // scroll based on transformation done to the last selection created by the user
14366 let (last_old, last_new) = old_selections
14367 .last()
14368 .zip(new_selections.last().cloned())
14369 .expect("old_selections isn't empty");
14370
14371 // revert selection
14372 let is_selection_reversed = {
14373 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14374 new_selections.last_mut().expect("checked above").reversed =
14375 should_newest_selection_be_reversed;
14376 should_newest_selection_be_reversed
14377 };
14378
14379 if selected_larger_node {
14380 self.select_syntax_node_history.disable_clearing = true;
14381 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14382 s.select(new_selections.clone());
14383 });
14384 self.select_syntax_node_history.disable_clearing = false;
14385 }
14386
14387 let start_row = last_new.start.to_display_point(&display_map).row().0;
14388 let end_row = last_new.end.to_display_point(&display_map).row().0;
14389 let selection_height = end_row - start_row + 1;
14390 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14391
14392 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14393 let scroll_behavior = if fits_on_the_screen {
14394 self.request_autoscroll(Autoscroll::fit(), cx);
14395 SelectSyntaxNodeScrollBehavior::FitSelection
14396 } else if is_selection_reversed {
14397 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14398 SelectSyntaxNodeScrollBehavior::CursorTop
14399 } else {
14400 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14401 SelectSyntaxNodeScrollBehavior::CursorBottom
14402 };
14403
14404 self.select_syntax_node_history.push((
14405 old_selections,
14406 scroll_behavior,
14407 is_selection_reversed,
14408 ));
14409 }
14410
14411 pub fn select_smaller_syntax_node(
14412 &mut self,
14413 _: &SelectSmallerSyntaxNode,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418
14419 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14420 self.select_syntax_node_history.pop()
14421 {
14422 if let Some(selection) = selections.last_mut() {
14423 selection.reversed = is_selection_reversed;
14424 }
14425
14426 self.select_syntax_node_history.disable_clearing = true;
14427 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14428 s.select(selections.to_vec());
14429 });
14430 self.select_syntax_node_history.disable_clearing = false;
14431
14432 match scroll_behavior {
14433 SelectSyntaxNodeScrollBehavior::CursorTop => {
14434 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14435 }
14436 SelectSyntaxNodeScrollBehavior::FitSelection => {
14437 self.request_autoscroll(Autoscroll::fit(), cx);
14438 }
14439 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14440 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14441 }
14442 }
14443 }
14444 }
14445
14446 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14447 if !EditorSettings::get_global(cx).gutter.runnables {
14448 self.clear_tasks();
14449 return Task::ready(());
14450 }
14451 let project = self.project.as_ref().map(Entity::downgrade);
14452 let task_sources = self.lsp_task_sources(cx);
14453 let multi_buffer = self.buffer.downgrade();
14454 cx.spawn_in(window, async move |editor, cx| {
14455 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14456 let Some(project) = project.and_then(|p| p.upgrade()) else {
14457 return;
14458 };
14459 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14460 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14461 }) else {
14462 return;
14463 };
14464
14465 let hide_runnables = project
14466 .update(cx, |project, cx| {
14467 // Do not display any test indicators in non-dev server remote projects.
14468 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14469 })
14470 .unwrap_or(true);
14471 if hide_runnables {
14472 return;
14473 }
14474 let new_rows =
14475 cx.background_spawn({
14476 let snapshot = display_snapshot.clone();
14477 async move {
14478 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14479 }
14480 })
14481 .await;
14482 let Ok(lsp_tasks) =
14483 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14484 else {
14485 return;
14486 };
14487 let lsp_tasks = lsp_tasks.await;
14488
14489 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14490 lsp_tasks
14491 .into_iter()
14492 .flat_map(|(kind, tasks)| {
14493 tasks.into_iter().filter_map(move |(location, task)| {
14494 Some((kind.clone(), location?, task))
14495 })
14496 })
14497 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14498 let buffer = location.target.buffer;
14499 let buffer_snapshot = buffer.read(cx).snapshot();
14500 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14501 |(excerpt_id, snapshot, _)| {
14502 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14503 display_snapshot
14504 .buffer_snapshot
14505 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14506 } else {
14507 None
14508 }
14509 },
14510 );
14511 if let Some(offset) = offset {
14512 let task_buffer_range =
14513 location.target.range.to_point(&buffer_snapshot);
14514 let context_buffer_range =
14515 task_buffer_range.to_offset(&buffer_snapshot);
14516 let context_range = BufferOffset(context_buffer_range.start)
14517 ..BufferOffset(context_buffer_range.end);
14518
14519 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14520 .or_insert_with(|| RunnableTasks {
14521 templates: Vec::new(),
14522 offset,
14523 column: task_buffer_range.start.column,
14524 extra_variables: HashMap::default(),
14525 context_range,
14526 })
14527 .templates
14528 .push((kind, task.original_task().clone()));
14529 }
14530
14531 acc
14532 })
14533 }) else {
14534 return;
14535 };
14536
14537 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14538 buffer.language_settings(cx).tasks.prefer_lsp
14539 }) else {
14540 return;
14541 };
14542
14543 let rows = Self::runnable_rows(
14544 project,
14545 display_snapshot,
14546 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14547 new_rows,
14548 cx.clone(),
14549 )
14550 .await;
14551 editor
14552 .update(cx, |editor, _| {
14553 editor.clear_tasks();
14554 for (key, mut value) in rows {
14555 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14556 value.templates.extend(lsp_tasks.templates);
14557 }
14558
14559 editor.insert_tasks(key, value);
14560 }
14561 for (key, value) in lsp_tasks_by_rows {
14562 editor.insert_tasks(key, value);
14563 }
14564 })
14565 .ok();
14566 })
14567 }
14568 fn fetch_runnable_ranges(
14569 snapshot: &DisplaySnapshot,
14570 range: Range<Anchor>,
14571 ) -> Vec<language::RunnableRange> {
14572 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14573 }
14574
14575 fn runnable_rows(
14576 project: Entity<Project>,
14577 snapshot: DisplaySnapshot,
14578 prefer_lsp: bool,
14579 runnable_ranges: Vec<RunnableRange>,
14580 cx: AsyncWindowContext,
14581 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14582 cx.spawn(async move |cx| {
14583 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14584 for mut runnable in runnable_ranges {
14585 let Some(tasks) = cx
14586 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14587 .ok()
14588 else {
14589 continue;
14590 };
14591 let mut tasks = tasks.await;
14592
14593 if prefer_lsp {
14594 tasks.retain(|(task_kind, _)| {
14595 !matches!(task_kind, TaskSourceKind::Language { .. })
14596 });
14597 }
14598 if tasks.is_empty() {
14599 continue;
14600 }
14601
14602 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14603 let Some(row) = snapshot
14604 .buffer_snapshot
14605 .buffer_line_for_row(MultiBufferRow(point.row))
14606 .map(|(_, range)| range.start.row)
14607 else {
14608 continue;
14609 };
14610
14611 let context_range =
14612 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14613 runnable_rows.push((
14614 (runnable.buffer_id, row),
14615 RunnableTasks {
14616 templates: tasks,
14617 offset: snapshot
14618 .buffer_snapshot
14619 .anchor_before(runnable.run_range.start),
14620 context_range,
14621 column: point.column,
14622 extra_variables: runnable.extra_captures,
14623 },
14624 ));
14625 }
14626 runnable_rows
14627 })
14628 }
14629
14630 fn templates_with_tags(
14631 project: &Entity<Project>,
14632 runnable: &mut Runnable,
14633 cx: &mut App,
14634 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14635 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14636 let (worktree_id, file) = project
14637 .buffer_for_id(runnable.buffer, cx)
14638 .and_then(|buffer| buffer.read(cx).file())
14639 .map(|file| (file.worktree_id(cx), file.clone()))
14640 .unzip();
14641
14642 (
14643 project.task_store().read(cx).task_inventory().cloned(),
14644 worktree_id,
14645 file,
14646 )
14647 });
14648
14649 let tags = mem::take(&mut runnable.tags);
14650 let language = runnable.language.clone();
14651 cx.spawn(async move |cx| {
14652 let mut templates_with_tags = Vec::new();
14653 if let Some(inventory) = inventory {
14654 for RunnableTag(tag) in tags {
14655 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14656 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14657 }) else {
14658 return templates_with_tags;
14659 };
14660 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14661 move |(_, template)| {
14662 template.tags.iter().any(|source_tag| source_tag == &tag)
14663 },
14664 ));
14665 }
14666 }
14667 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14668
14669 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14670 // Strongest source wins; if we have worktree tag binding, prefer that to
14671 // global and language bindings;
14672 // if we have a global binding, prefer that to language binding.
14673 let first_mismatch = templates_with_tags
14674 .iter()
14675 .position(|(tag_source, _)| tag_source != leading_tag_source);
14676 if let Some(index) = first_mismatch {
14677 templates_with_tags.truncate(index);
14678 }
14679 }
14680
14681 templates_with_tags
14682 })
14683 }
14684
14685 pub fn move_to_enclosing_bracket(
14686 &mut self,
14687 _: &MoveToEnclosingBracket,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 ) {
14691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14692 self.change_selections(Default::default(), window, cx, |s| {
14693 s.move_offsets_with(|snapshot, selection| {
14694 let Some(enclosing_bracket_ranges) =
14695 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14696 else {
14697 return;
14698 };
14699
14700 let mut best_length = usize::MAX;
14701 let mut best_inside = false;
14702 let mut best_in_bracket_range = false;
14703 let mut best_destination = None;
14704 for (open, close) in enclosing_bracket_ranges {
14705 let close = close.to_inclusive();
14706 let length = close.end() - open.start;
14707 let inside = selection.start >= open.end && selection.end <= *close.start();
14708 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14709 || close.contains(&selection.head());
14710
14711 // If best is next to a bracket and current isn't, skip
14712 if !in_bracket_range && best_in_bracket_range {
14713 continue;
14714 }
14715
14716 // Prefer smaller lengths unless best is inside and current isn't
14717 if length > best_length && (best_inside || !inside) {
14718 continue;
14719 }
14720
14721 best_length = length;
14722 best_inside = inside;
14723 best_in_bracket_range = in_bracket_range;
14724 best_destination = Some(
14725 if close.contains(&selection.start) && close.contains(&selection.end) {
14726 if inside { open.end } else { open.start }
14727 } else if inside {
14728 *close.start()
14729 } else {
14730 *close.end()
14731 },
14732 );
14733 }
14734
14735 if let Some(destination) = best_destination {
14736 selection.collapse_to(destination, SelectionGoal::None);
14737 }
14738 })
14739 });
14740 }
14741
14742 pub fn undo_selection(
14743 &mut self,
14744 _: &UndoSelection,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) {
14748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14749 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14750 self.selection_history.mode = SelectionHistoryMode::Undoing;
14751 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14752 this.end_selection(window, cx);
14753 this.change_selections(
14754 SelectionEffects::scroll(Autoscroll::newest()),
14755 window,
14756 cx,
14757 |s| s.select_anchors(entry.selections.to_vec()),
14758 );
14759 });
14760 self.selection_history.mode = SelectionHistoryMode::Normal;
14761
14762 self.select_next_state = entry.select_next_state;
14763 self.select_prev_state = entry.select_prev_state;
14764 self.add_selections_state = entry.add_selections_state;
14765 }
14766 }
14767
14768 pub fn redo_selection(
14769 &mut self,
14770 _: &RedoSelection,
14771 window: &mut Window,
14772 cx: &mut Context<Self>,
14773 ) {
14774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14775 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14776 self.selection_history.mode = SelectionHistoryMode::Redoing;
14777 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14778 this.end_selection(window, cx);
14779 this.change_selections(
14780 SelectionEffects::scroll(Autoscroll::newest()),
14781 window,
14782 cx,
14783 |s| s.select_anchors(entry.selections.to_vec()),
14784 );
14785 });
14786 self.selection_history.mode = SelectionHistoryMode::Normal;
14787
14788 self.select_next_state = entry.select_next_state;
14789 self.select_prev_state = entry.select_prev_state;
14790 self.add_selections_state = entry.add_selections_state;
14791 }
14792 }
14793
14794 pub fn expand_excerpts(
14795 &mut self,
14796 action: &ExpandExcerpts,
14797 _: &mut Window,
14798 cx: &mut Context<Self>,
14799 ) {
14800 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14801 }
14802
14803 pub fn expand_excerpts_down(
14804 &mut self,
14805 action: &ExpandExcerptsDown,
14806 _: &mut Window,
14807 cx: &mut Context<Self>,
14808 ) {
14809 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14810 }
14811
14812 pub fn expand_excerpts_up(
14813 &mut self,
14814 action: &ExpandExcerptsUp,
14815 _: &mut Window,
14816 cx: &mut Context<Self>,
14817 ) {
14818 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14819 }
14820
14821 pub fn expand_excerpts_for_direction(
14822 &mut self,
14823 lines: u32,
14824 direction: ExpandExcerptDirection,
14825
14826 cx: &mut Context<Self>,
14827 ) {
14828 let selections = self.selections.disjoint_anchors();
14829
14830 let lines = if lines == 0 {
14831 EditorSettings::get_global(cx).expand_excerpt_lines
14832 } else {
14833 lines
14834 };
14835
14836 self.buffer.update(cx, |buffer, cx| {
14837 let snapshot = buffer.snapshot(cx);
14838 let mut excerpt_ids = selections
14839 .iter()
14840 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14841 .collect::<Vec<_>>();
14842 excerpt_ids.sort();
14843 excerpt_ids.dedup();
14844 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14845 })
14846 }
14847
14848 pub fn expand_excerpt(
14849 &mut self,
14850 excerpt: ExcerptId,
14851 direction: ExpandExcerptDirection,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 let current_scroll_position = self.scroll_position(cx);
14856 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14857 let mut should_scroll_up = false;
14858
14859 if direction == ExpandExcerptDirection::Down {
14860 let multi_buffer = self.buffer.read(cx);
14861 let snapshot = multi_buffer.snapshot(cx);
14862 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14863 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14864 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14865 let buffer_snapshot = buffer.read(cx).snapshot();
14866 let excerpt_end_row =
14867 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14868 let last_row = buffer_snapshot.max_point().row;
14869 let lines_below = last_row.saturating_sub(excerpt_end_row);
14870 should_scroll_up = lines_below >= lines_to_expand;
14871 }
14872 }
14873 }
14874 }
14875
14876 self.buffer.update(cx, |buffer, cx| {
14877 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14878 });
14879
14880 if should_scroll_up {
14881 let new_scroll_position =
14882 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14883 self.set_scroll_position(new_scroll_position, window, cx);
14884 }
14885 }
14886
14887 pub fn go_to_singleton_buffer_point(
14888 &mut self,
14889 point: Point,
14890 window: &mut Window,
14891 cx: &mut Context<Self>,
14892 ) {
14893 self.go_to_singleton_buffer_range(point..point, window, cx);
14894 }
14895
14896 pub fn go_to_singleton_buffer_range(
14897 &mut self,
14898 range: Range<Point>,
14899 window: &mut Window,
14900 cx: &mut Context<Self>,
14901 ) {
14902 let multibuffer = self.buffer().read(cx);
14903 let Some(buffer) = multibuffer.as_singleton() else {
14904 return;
14905 };
14906 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14907 return;
14908 };
14909 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14910 return;
14911 };
14912 self.change_selections(
14913 SelectionEffects::default().nav_history(true),
14914 window,
14915 cx,
14916 |s| s.select_anchor_ranges([start..end]),
14917 );
14918 }
14919
14920 pub fn go_to_diagnostic(
14921 &mut self,
14922 _: &GoToDiagnostic,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) {
14926 if !self.diagnostics_enabled() {
14927 return;
14928 }
14929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14930 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14931 }
14932
14933 pub fn go_to_prev_diagnostic(
14934 &mut self,
14935 _: &GoToPreviousDiagnostic,
14936 window: &mut Window,
14937 cx: &mut Context<Self>,
14938 ) {
14939 if !self.diagnostics_enabled() {
14940 return;
14941 }
14942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14943 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14944 }
14945
14946 pub fn go_to_diagnostic_impl(
14947 &mut self,
14948 direction: Direction,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 let buffer = self.buffer.read(cx).snapshot(cx);
14953 let selection = self.selections.newest::<usize>(cx);
14954
14955 let mut active_group_id = None;
14956 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14957 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14958 active_group_id = Some(active_group.group_id);
14959 }
14960 }
14961
14962 fn filtered(
14963 snapshot: EditorSnapshot,
14964 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14965 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14966 diagnostics
14967 .filter(|entry| entry.range.start != entry.range.end)
14968 .filter(|entry| !entry.diagnostic.is_unnecessary)
14969 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14970 }
14971
14972 let snapshot = self.snapshot(window, cx);
14973 let before = filtered(
14974 snapshot.clone(),
14975 buffer
14976 .diagnostics_in_range(0..selection.start)
14977 .filter(|entry| entry.range.start <= selection.start),
14978 );
14979 let after = filtered(
14980 snapshot,
14981 buffer
14982 .diagnostics_in_range(selection.start..buffer.len())
14983 .filter(|entry| entry.range.start >= selection.start),
14984 );
14985
14986 let mut found: Option<DiagnosticEntry<usize>> = None;
14987 if direction == Direction::Prev {
14988 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14989 {
14990 for diagnostic in prev_diagnostics.into_iter().rev() {
14991 if diagnostic.range.start != selection.start
14992 || active_group_id
14993 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14994 {
14995 found = Some(diagnostic);
14996 break 'outer;
14997 }
14998 }
14999 }
15000 } else {
15001 for diagnostic in after.chain(before) {
15002 if diagnostic.range.start != selection.start
15003 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15004 {
15005 found = Some(diagnostic);
15006 break;
15007 }
15008 }
15009 }
15010 let Some(next_diagnostic) = found else {
15011 return;
15012 };
15013
15014 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
15015 return;
15016 };
15017 self.change_selections(Default::default(), window, cx, |s| {
15018 s.select_ranges(vec![
15019 next_diagnostic.range.start..next_diagnostic.range.start,
15020 ])
15021 });
15022 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15023 self.refresh_inline_completion(false, true, window, cx);
15024 }
15025
15026 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15028 let snapshot = self.snapshot(window, cx);
15029 let selection = self.selections.newest::<Point>(cx);
15030 self.go_to_hunk_before_or_after_position(
15031 &snapshot,
15032 selection.head(),
15033 Direction::Next,
15034 window,
15035 cx,
15036 );
15037 }
15038
15039 pub fn go_to_hunk_before_or_after_position(
15040 &mut self,
15041 snapshot: &EditorSnapshot,
15042 position: Point,
15043 direction: Direction,
15044 window: &mut Window,
15045 cx: &mut Context<Editor>,
15046 ) {
15047 let row = if direction == Direction::Next {
15048 self.hunk_after_position(snapshot, position)
15049 .map(|hunk| hunk.row_range.start)
15050 } else {
15051 self.hunk_before_position(snapshot, position)
15052 };
15053
15054 if let Some(row) = row {
15055 let destination = Point::new(row.0, 0);
15056 let autoscroll = Autoscroll::center();
15057
15058 self.unfold_ranges(&[destination..destination], false, false, cx);
15059 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15060 s.select_ranges([destination..destination]);
15061 });
15062 }
15063 }
15064
15065 fn hunk_after_position(
15066 &mut self,
15067 snapshot: &EditorSnapshot,
15068 position: Point,
15069 ) -> Option<MultiBufferDiffHunk> {
15070 snapshot
15071 .buffer_snapshot
15072 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15073 .find(|hunk| hunk.row_range.start.0 > position.row)
15074 .or_else(|| {
15075 snapshot
15076 .buffer_snapshot
15077 .diff_hunks_in_range(Point::zero()..position)
15078 .find(|hunk| hunk.row_range.end.0 < position.row)
15079 })
15080 }
15081
15082 fn go_to_prev_hunk(
15083 &mut self,
15084 _: &GoToPreviousHunk,
15085 window: &mut Window,
15086 cx: &mut Context<Self>,
15087 ) {
15088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15089 let snapshot = self.snapshot(window, cx);
15090 let selection = self.selections.newest::<Point>(cx);
15091 self.go_to_hunk_before_or_after_position(
15092 &snapshot,
15093 selection.head(),
15094 Direction::Prev,
15095 window,
15096 cx,
15097 );
15098 }
15099
15100 fn hunk_before_position(
15101 &mut self,
15102 snapshot: &EditorSnapshot,
15103 position: Point,
15104 ) -> Option<MultiBufferRow> {
15105 snapshot
15106 .buffer_snapshot
15107 .diff_hunk_before(position)
15108 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
15109 }
15110
15111 fn go_to_next_change(
15112 &mut self,
15113 _: &GoToNextChange,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) {
15117 if let Some(selections) = self
15118 .change_list
15119 .next_change(1, Direction::Next)
15120 .map(|s| s.to_vec())
15121 {
15122 self.change_selections(Default::default(), window, cx, |s| {
15123 let map = s.display_map();
15124 s.select_display_ranges(selections.iter().map(|a| {
15125 let point = a.to_display_point(&map);
15126 point..point
15127 }))
15128 })
15129 }
15130 }
15131
15132 fn go_to_previous_change(
15133 &mut self,
15134 _: &GoToPreviousChange,
15135 window: &mut Window,
15136 cx: &mut Context<Self>,
15137 ) {
15138 if let Some(selections) = self
15139 .change_list
15140 .next_change(1, Direction::Prev)
15141 .map(|s| s.to_vec())
15142 {
15143 self.change_selections(Default::default(), window, cx, |s| {
15144 let map = s.display_map();
15145 s.select_display_ranges(selections.iter().map(|a| {
15146 let point = a.to_display_point(&map);
15147 point..point
15148 }))
15149 })
15150 }
15151 }
15152
15153 fn go_to_line<T: 'static>(
15154 &mut self,
15155 position: Anchor,
15156 highlight_color: Option<Hsla>,
15157 window: &mut Window,
15158 cx: &mut Context<Self>,
15159 ) {
15160 let snapshot = self.snapshot(window, cx).display_snapshot;
15161 let position = position.to_point(&snapshot.buffer_snapshot);
15162 let start = snapshot
15163 .buffer_snapshot
15164 .clip_point(Point::new(position.row, 0), Bias::Left);
15165 let end = start + Point::new(1, 0);
15166 let start = snapshot.buffer_snapshot.anchor_before(start);
15167 let end = snapshot.buffer_snapshot.anchor_before(end);
15168
15169 self.highlight_rows::<T>(
15170 start..end,
15171 highlight_color
15172 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
15173 Default::default(),
15174 cx,
15175 );
15176
15177 if self.buffer.read(cx).is_singleton() {
15178 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
15179 }
15180 }
15181
15182 pub fn go_to_definition(
15183 &mut self,
15184 _: &GoToDefinition,
15185 window: &mut Window,
15186 cx: &mut Context<Self>,
15187 ) -> Task<Result<Navigated>> {
15188 let definition =
15189 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
15190 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
15191 cx.spawn_in(window, async move |editor, cx| {
15192 if definition.await? == Navigated::Yes {
15193 return Ok(Navigated::Yes);
15194 }
15195 match fallback_strategy {
15196 GoToDefinitionFallback::None => Ok(Navigated::No),
15197 GoToDefinitionFallback::FindAllReferences => {
15198 match editor.update_in(cx, |editor, window, cx| {
15199 editor.find_all_references(&FindAllReferences, window, cx)
15200 })? {
15201 Some(references) => references.await,
15202 None => Ok(Navigated::No),
15203 }
15204 }
15205 }
15206 })
15207 }
15208
15209 pub fn go_to_declaration(
15210 &mut self,
15211 _: &GoToDeclaration,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) -> Task<Result<Navigated>> {
15215 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
15216 }
15217
15218 pub fn go_to_declaration_split(
15219 &mut self,
15220 _: &GoToDeclaration,
15221 window: &mut Window,
15222 cx: &mut Context<Self>,
15223 ) -> Task<Result<Navigated>> {
15224 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
15225 }
15226
15227 pub fn go_to_implementation(
15228 &mut self,
15229 _: &GoToImplementation,
15230 window: &mut Window,
15231 cx: &mut Context<Self>,
15232 ) -> Task<Result<Navigated>> {
15233 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
15234 }
15235
15236 pub fn go_to_implementation_split(
15237 &mut self,
15238 _: &GoToImplementationSplit,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) -> Task<Result<Navigated>> {
15242 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
15243 }
15244
15245 pub fn go_to_type_definition(
15246 &mut self,
15247 _: &GoToTypeDefinition,
15248 window: &mut Window,
15249 cx: &mut Context<Self>,
15250 ) -> Task<Result<Navigated>> {
15251 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
15252 }
15253
15254 pub fn go_to_definition_split(
15255 &mut self,
15256 _: &GoToDefinitionSplit,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) -> Task<Result<Navigated>> {
15260 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
15261 }
15262
15263 pub fn go_to_type_definition_split(
15264 &mut self,
15265 _: &GoToTypeDefinitionSplit,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) -> Task<Result<Navigated>> {
15269 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
15270 }
15271
15272 fn go_to_definition_of_kind(
15273 &mut self,
15274 kind: GotoDefinitionKind,
15275 split: bool,
15276 window: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) -> Task<Result<Navigated>> {
15279 let Some(provider) = self.semantics_provider.clone() else {
15280 return Task::ready(Ok(Navigated::No));
15281 };
15282 let head = self.selections.newest::<usize>(cx).head();
15283 let buffer = self.buffer.read(cx);
15284 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
15285 text_anchor
15286 } else {
15287 return Task::ready(Ok(Navigated::No));
15288 };
15289
15290 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
15291 return Task::ready(Ok(Navigated::No));
15292 };
15293
15294 cx.spawn_in(window, async move |editor, cx| {
15295 let definitions = definitions.await?;
15296 let navigated = editor
15297 .update_in(cx, |editor, window, cx| {
15298 editor.navigate_to_hover_links(
15299 Some(kind),
15300 definitions
15301 .into_iter()
15302 .filter(|location| {
15303 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
15304 })
15305 .map(HoverLink::Text)
15306 .collect::<Vec<_>>(),
15307 split,
15308 window,
15309 cx,
15310 )
15311 })?
15312 .await?;
15313 anyhow::Ok(navigated)
15314 })
15315 }
15316
15317 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15318 let selection = self.selections.newest_anchor();
15319 let head = selection.head();
15320 let tail = selection.tail();
15321
15322 let Some((buffer, start_position)) =
15323 self.buffer.read(cx).text_anchor_for_position(head, cx)
15324 else {
15325 return;
15326 };
15327
15328 let end_position = if head != tail {
15329 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15330 return;
15331 };
15332 Some(pos)
15333 } else {
15334 None
15335 };
15336
15337 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15338 let url = if let Some(end_pos) = end_position {
15339 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15340 } else {
15341 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15342 };
15343
15344 if let Some(url) = url {
15345 editor.update(cx, |_, cx| {
15346 cx.open_url(&url);
15347 })
15348 } else {
15349 Ok(())
15350 }
15351 });
15352
15353 url_finder.detach();
15354 }
15355
15356 pub fn open_selected_filename(
15357 &mut self,
15358 _: &OpenSelectedFilename,
15359 window: &mut Window,
15360 cx: &mut Context<Self>,
15361 ) {
15362 let Some(workspace) = self.workspace() else {
15363 return;
15364 };
15365
15366 let position = self.selections.newest_anchor().head();
15367
15368 let Some((buffer, buffer_position)) =
15369 self.buffer.read(cx).text_anchor_for_position(position, cx)
15370 else {
15371 return;
15372 };
15373
15374 let project = self.project.clone();
15375
15376 cx.spawn_in(window, async move |_, cx| {
15377 let result = find_file(&buffer, project, buffer_position, cx).await;
15378
15379 if let Some((_, path)) = result {
15380 workspace
15381 .update_in(cx, |workspace, window, cx| {
15382 workspace.open_resolved_path(path, window, cx)
15383 })?
15384 .await?;
15385 }
15386 anyhow::Ok(())
15387 })
15388 .detach();
15389 }
15390
15391 pub(crate) fn navigate_to_hover_links(
15392 &mut self,
15393 kind: Option<GotoDefinitionKind>,
15394 mut definitions: Vec<HoverLink>,
15395 split: bool,
15396 window: &mut Window,
15397 cx: &mut Context<Editor>,
15398 ) -> Task<Result<Navigated>> {
15399 // If there is one definition, just open it directly
15400 if definitions.len() == 1 {
15401 let definition = definitions.pop().unwrap();
15402
15403 enum TargetTaskResult {
15404 Location(Option<Location>),
15405 AlreadyNavigated,
15406 }
15407
15408 let target_task = match definition {
15409 HoverLink::Text(link) => {
15410 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15411 }
15412 HoverLink::InlayHint(lsp_location, server_id) => {
15413 let computation =
15414 self.compute_target_location(lsp_location, server_id, window, cx);
15415 cx.background_spawn(async move {
15416 let location = computation.await?;
15417 Ok(TargetTaskResult::Location(location))
15418 })
15419 }
15420 HoverLink::Url(url) => {
15421 cx.open_url(&url);
15422 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15423 }
15424 HoverLink::File(path) => {
15425 if let Some(workspace) = self.workspace() {
15426 cx.spawn_in(window, async move |_, cx| {
15427 workspace
15428 .update_in(cx, |workspace, window, cx| {
15429 workspace.open_resolved_path(path, window, cx)
15430 })?
15431 .await
15432 .map(|_| TargetTaskResult::AlreadyNavigated)
15433 })
15434 } else {
15435 Task::ready(Ok(TargetTaskResult::Location(None)))
15436 }
15437 }
15438 };
15439 cx.spawn_in(window, async move |editor, cx| {
15440 let target = match target_task.await.context("target resolution task")? {
15441 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15442 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15443 TargetTaskResult::Location(Some(target)) => target,
15444 };
15445
15446 editor.update_in(cx, |editor, window, cx| {
15447 let Some(workspace) = editor.workspace() else {
15448 return Navigated::No;
15449 };
15450 let pane = workspace.read(cx).active_pane().clone();
15451
15452 let range = target.range.to_point(target.buffer.read(cx));
15453 let range = editor.range_for_match(&range);
15454 let range = collapse_multiline_range(range);
15455
15456 if !split
15457 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15458 {
15459 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15460 } else {
15461 window.defer(cx, move |window, cx| {
15462 let target_editor: Entity<Self> =
15463 workspace.update(cx, |workspace, cx| {
15464 let pane = if split {
15465 workspace.adjacent_pane(window, cx)
15466 } else {
15467 workspace.active_pane().clone()
15468 };
15469
15470 workspace.open_project_item(
15471 pane,
15472 target.buffer.clone(),
15473 true,
15474 true,
15475 window,
15476 cx,
15477 )
15478 });
15479 target_editor.update(cx, |target_editor, cx| {
15480 // When selecting a definition in a different buffer, disable the nav history
15481 // to avoid creating a history entry at the previous cursor location.
15482 pane.update(cx, |pane, _| pane.disable_history());
15483 target_editor.go_to_singleton_buffer_range(range, window, cx);
15484 pane.update(cx, |pane, _| pane.enable_history());
15485 });
15486 });
15487 }
15488 Navigated::Yes
15489 })
15490 })
15491 } else if !definitions.is_empty() {
15492 cx.spawn_in(window, async move |editor, cx| {
15493 let (title, location_tasks, workspace) = editor
15494 .update_in(cx, |editor, window, cx| {
15495 let tab_kind = match kind {
15496 Some(GotoDefinitionKind::Implementation) => "Implementations",
15497 _ => "Definitions",
15498 };
15499 let title = definitions
15500 .iter()
15501 .find_map(|definition| match definition {
15502 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15503 let buffer = origin.buffer.read(cx);
15504 format!(
15505 "{} for {}",
15506 tab_kind,
15507 buffer
15508 .text_for_range(origin.range.clone())
15509 .collect::<String>()
15510 )
15511 }),
15512 HoverLink::InlayHint(_, _) => None,
15513 HoverLink::Url(_) => None,
15514 HoverLink::File(_) => None,
15515 })
15516 .unwrap_or(tab_kind.to_string());
15517 let location_tasks = definitions
15518 .into_iter()
15519 .map(|definition| match definition {
15520 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15521 HoverLink::InlayHint(lsp_location, server_id) => editor
15522 .compute_target_location(lsp_location, server_id, window, cx),
15523 HoverLink::Url(_) => Task::ready(Ok(None)),
15524 HoverLink::File(_) => Task::ready(Ok(None)),
15525 })
15526 .collect::<Vec<_>>();
15527 (title, location_tasks, editor.workspace().clone())
15528 })
15529 .context("location tasks preparation")?;
15530
15531 let locations: Vec<Location> = future::join_all(location_tasks)
15532 .await
15533 .into_iter()
15534 .filter_map(|location| location.transpose())
15535 .collect::<Result<_>>()
15536 .context("location tasks")?;
15537
15538 if locations.is_empty() {
15539 return Ok(Navigated::No);
15540 }
15541
15542 let Some(workspace) = workspace else {
15543 return Ok(Navigated::No);
15544 };
15545
15546 let opened = workspace
15547 .update_in(cx, |workspace, window, cx| {
15548 Self::open_locations_in_multibuffer(
15549 workspace,
15550 locations,
15551 title,
15552 split,
15553 MultibufferSelectionMode::First,
15554 window,
15555 cx,
15556 )
15557 })
15558 .ok();
15559
15560 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15561 })
15562 } else {
15563 Task::ready(Ok(Navigated::No))
15564 }
15565 }
15566
15567 fn compute_target_location(
15568 &self,
15569 lsp_location: lsp::Location,
15570 server_id: LanguageServerId,
15571 window: &mut Window,
15572 cx: &mut Context<Self>,
15573 ) -> Task<anyhow::Result<Option<Location>>> {
15574 let Some(project) = self.project.clone() else {
15575 return Task::ready(Ok(None));
15576 };
15577
15578 cx.spawn_in(window, async move |editor, cx| {
15579 let location_task = editor.update(cx, |_, cx| {
15580 project.update(cx, |project, cx| {
15581 let language_server_name = project
15582 .language_server_statuses(cx)
15583 .find(|(id, _)| server_id == *id)
15584 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15585 language_server_name.map(|language_server_name| {
15586 project.open_local_buffer_via_lsp(
15587 lsp_location.uri.clone(),
15588 server_id,
15589 language_server_name,
15590 cx,
15591 )
15592 })
15593 })
15594 })?;
15595 let location = match location_task {
15596 Some(task) => Some({
15597 let target_buffer_handle = task.await.context("open local buffer")?;
15598 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15599 let target_start = target_buffer
15600 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15601 let target_end = target_buffer
15602 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15603 target_buffer.anchor_after(target_start)
15604 ..target_buffer.anchor_before(target_end)
15605 })?;
15606 Location {
15607 buffer: target_buffer_handle,
15608 range,
15609 }
15610 }),
15611 None => None,
15612 };
15613 Ok(location)
15614 })
15615 }
15616
15617 pub fn find_all_references(
15618 &mut self,
15619 _: &FindAllReferences,
15620 window: &mut Window,
15621 cx: &mut Context<Self>,
15622 ) -> Option<Task<Result<Navigated>>> {
15623 let selection = self.selections.newest::<usize>(cx);
15624 let multi_buffer = self.buffer.read(cx);
15625 let head = selection.head();
15626
15627 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15628 let head_anchor = multi_buffer_snapshot.anchor_at(
15629 head,
15630 if head < selection.tail() {
15631 Bias::Right
15632 } else {
15633 Bias::Left
15634 },
15635 );
15636
15637 match self
15638 .find_all_references_task_sources
15639 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15640 {
15641 Ok(_) => {
15642 log::info!(
15643 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15644 );
15645 return None;
15646 }
15647 Err(i) => {
15648 self.find_all_references_task_sources.insert(i, head_anchor);
15649 }
15650 }
15651
15652 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15653 let workspace = self.workspace()?;
15654 let project = workspace.read(cx).project().clone();
15655 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15656 Some(cx.spawn_in(window, async move |editor, cx| {
15657 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15658 if let Ok(i) = editor
15659 .find_all_references_task_sources
15660 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15661 {
15662 editor.find_all_references_task_sources.remove(i);
15663 }
15664 });
15665
15666 let locations = references.await?;
15667 if locations.is_empty() {
15668 return anyhow::Ok(Navigated::No);
15669 }
15670
15671 workspace.update_in(cx, |workspace, window, cx| {
15672 let title = locations
15673 .first()
15674 .as_ref()
15675 .map(|location| {
15676 let buffer = location.buffer.read(cx);
15677 format!(
15678 "References to `{}`",
15679 buffer
15680 .text_for_range(location.range.clone())
15681 .collect::<String>()
15682 )
15683 })
15684 .unwrap();
15685 Self::open_locations_in_multibuffer(
15686 workspace,
15687 locations,
15688 title,
15689 false,
15690 MultibufferSelectionMode::First,
15691 window,
15692 cx,
15693 );
15694 Navigated::Yes
15695 })
15696 }))
15697 }
15698
15699 /// Opens a multibuffer with the given project locations in it
15700 pub fn open_locations_in_multibuffer(
15701 workspace: &mut Workspace,
15702 mut locations: Vec<Location>,
15703 title: String,
15704 split: bool,
15705 multibuffer_selection_mode: MultibufferSelectionMode,
15706 window: &mut Window,
15707 cx: &mut Context<Workspace>,
15708 ) {
15709 if locations.is_empty() {
15710 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15711 return;
15712 }
15713
15714 // If there are multiple definitions, open them in a multibuffer
15715 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15716 let mut locations = locations.into_iter().peekable();
15717 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15718 let capability = workspace.project().read(cx).capability();
15719
15720 let excerpt_buffer = cx.new(|cx| {
15721 let mut multibuffer = MultiBuffer::new(capability);
15722 while let Some(location) = locations.next() {
15723 let buffer = location.buffer.read(cx);
15724 let mut ranges_for_buffer = Vec::new();
15725 let range = location.range.to_point(buffer);
15726 ranges_for_buffer.push(range.clone());
15727
15728 while let Some(next_location) = locations.peek() {
15729 if next_location.buffer == location.buffer {
15730 ranges_for_buffer.push(next_location.range.to_point(buffer));
15731 locations.next();
15732 } else {
15733 break;
15734 }
15735 }
15736
15737 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15738 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15739 PathKey::for_buffer(&location.buffer, cx),
15740 location.buffer.clone(),
15741 ranges_for_buffer,
15742 DEFAULT_MULTIBUFFER_CONTEXT,
15743 cx,
15744 );
15745 ranges.extend(new_ranges)
15746 }
15747
15748 multibuffer.with_title(title)
15749 });
15750
15751 let editor = cx.new(|cx| {
15752 Editor::for_multibuffer(
15753 excerpt_buffer,
15754 Some(workspace.project().clone()),
15755 window,
15756 cx,
15757 )
15758 });
15759 editor.update(cx, |editor, cx| {
15760 match multibuffer_selection_mode {
15761 MultibufferSelectionMode::First => {
15762 if let Some(first_range) = ranges.first() {
15763 editor.change_selections(
15764 SelectionEffects::no_scroll(),
15765 window,
15766 cx,
15767 |selections| {
15768 selections.clear_disjoint();
15769 selections
15770 .select_anchor_ranges(std::iter::once(first_range.clone()));
15771 },
15772 );
15773 }
15774 editor.highlight_background::<Self>(
15775 &ranges,
15776 |theme| theme.colors().editor_highlighted_line_background,
15777 cx,
15778 );
15779 }
15780 MultibufferSelectionMode::All => {
15781 editor.change_selections(
15782 SelectionEffects::no_scroll(),
15783 window,
15784 cx,
15785 |selections| {
15786 selections.clear_disjoint();
15787 selections.select_anchor_ranges(ranges);
15788 },
15789 );
15790 }
15791 }
15792 editor.register_buffers_with_language_servers(cx);
15793 });
15794
15795 let item = Box::new(editor);
15796 let item_id = item.item_id();
15797
15798 if split {
15799 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15800 } else {
15801 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15802 let (preview_item_id, preview_item_idx) =
15803 workspace.active_pane().read_with(cx, |pane, _| {
15804 (pane.preview_item_id(), pane.preview_item_idx())
15805 });
15806
15807 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15808
15809 if let Some(preview_item_id) = preview_item_id {
15810 workspace.active_pane().update(cx, |pane, cx| {
15811 pane.remove_item(preview_item_id, false, false, window, cx);
15812 });
15813 }
15814 } else {
15815 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15816 }
15817 }
15818 workspace.active_pane().update(cx, |pane, cx| {
15819 pane.set_preview_item_id(Some(item_id), cx);
15820 });
15821 }
15822
15823 pub fn rename(
15824 &mut self,
15825 _: &Rename,
15826 window: &mut Window,
15827 cx: &mut Context<Self>,
15828 ) -> Option<Task<Result<()>>> {
15829 use language::ToOffset as _;
15830
15831 let provider = self.semantics_provider.clone()?;
15832 let selection = self.selections.newest_anchor().clone();
15833 let (cursor_buffer, cursor_buffer_position) = self
15834 .buffer
15835 .read(cx)
15836 .text_anchor_for_position(selection.head(), cx)?;
15837 let (tail_buffer, cursor_buffer_position_end) = self
15838 .buffer
15839 .read(cx)
15840 .text_anchor_for_position(selection.tail(), cx)?;
15841 if tail_buffer != cursor_buffer {
15842 return None;
15843 }
15844
15845 let snapshot = cursor_buffer.read(cx).snapshot();
15846 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15847 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15848 let prepare_rename = provider
15849 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15850 .unwrap_or_else(|| Task::ready(Ok(None)));
15851 drop(snapshot);
15852
15853 Some(cx.spawn_in(window, async move |this, cx| {
15854 let rename_range = if let Some(range) = prepare_rename.await? {
15855 Some(range)
15856 } else {
15857 this.update(cx, |this, cx| {
15858 let buffer = this.buffer.read(cx).snapshot(cx);
15859 let mut buffer_highlights = this
15860 .document_highlights_for_position(selection.head(), &buffer)
15861 .filter(|highlight| {
15862 highlight.start.excerpt_id == selection.head().excerpt_id
15863 && highlight.end.excerpt_id == selection.head().excerpt_id
15864 });
15865 buffer_highlights
15866 .next()
15867 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15868 })?
15869 };
15870 if let Some(rename_range) = rename_range {
15871 this.update_in(cx, |this, window, cx| {
15872 let snapshot = cursor_buffer.read(cx).snapshot();
15873 let rename_buffer_range = rename_range.to_offset(&snapshot);
15874 let cursor_offset_in_rename_range =
15875 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15876 let cursor_offset_in_rename_range_end =
15877 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15878
15879 this.take_rename(false, window, cx);
15880 let buffer = this.buffer.read(cx).read(cx);
15881 let cursor_offset = selection.head().to_offset(&buffer);
15882 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15883 let rename_end = rename_start + rename_buffer_range.len();
15884 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15885 let mut old_highlight_id = None;
15886 let old_name: Arc<str> = buffer
15887 .chunks(rename_start..rename_end, true)
15888 .map(|chunk| {
15889 if old_highlight_id.is_none() {
15890 old_highlight_id = chunk.syntax_highlight_id;
15891 }
15892 chunk.text
15893 })
15894 .collect::<String>()
15895 .into();
15896
15897 drop(buffer);
15898
15899 // Position the selection in the rename editor so that it matches the current selection.
15900 this.show_local_selections = false;
15901 let rename_editor = cx.new(|cx| {
15902 let mut editor = Editor::single_line(window, cx);
15903 editor.buffer.update(cx, |buffer, cx| {
15904 buffer.edit([(0..0, old_name.clone())], None, cx)
15905 });
15906 let rename_selection_range = match cursor_offset_in_rename_range
15907 .cmp(&cursor_offset_in_rename_range_end)
15908 {
15909 Ordering::Equal => {
15910 editor.select_all(&SelectAll, window, cx);
15911 return editor;
15912 }
15913 Ordering::Less => {
15914 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15915 }
15916 Ordering::Greater => {
15917 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15918 }
15919 };
15920 if rename_selection_range.end > old_name.len() {
15921 editor.select_all(&SelectAll, window, cx);
15922 } else {
15923 editor.change_selections(Default::default(), window, cx, |s| {
15924 s.select_ranges([rename_selection_range]);
15925 });
15926 }
15927 editor
15928 });
15929 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15930 if e == &EditorEvent::Focused {
15931 cx.emit(EditorEvent::FocusedIn)
15932 }
15933 })
15934 .detach();
15935
15936 let write_highlights =
15937 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15938 let read_highlights =
15939 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15940 let ranges = write_highlights
15941 .iter()
15942 .flat_map(|(_, ranges)| ranges.iter())
15943 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15944 .cloned()
15945 .collect();
15946
15947 this.highlight_text::<Rename>(
15948 ranges,
15949 HighlightStyle {
15950 fade_out: Some(0.6),
15951 ..Default::default()
15952 },
15953 cx,
15954 );
15955 let rename_focus_handle = rename_editor.focus_handle(cx);
15956 window.focus(&rename_focus_handle);
15957 let block_id = this.insert_blocks(
15958 [BlockProperties {
15959 style: BlockStyle::Flex,
15960 placement: BlockPlacement::Below(range.start),
15961 height: Some(1),
15962 render: Arc::new({
15963 let rename_editor = rename_editor.clone();
15964 move |cx: &mut BlockContext| {
15965 let mut text_style = cx.editor_style.text.clone();
15966 if let Some(highlight_style) = old_highlight_id
15967 .and_then(|h| h.style(&cx.editor_style.syntax))
15968 {
15969 text_style = text_style.highlight(highlight_style);
15970 }
15971 div()
15972 .block_mouse_except_scroll()
15973 .pl(cx.anchor_x)
15974 .child(EditorElement::new(
15975 &rename_editor,
15976 EditorStyle {
15977 background: cx.theme().system().transparent,
15978 local_player: cx.editor_style.local_player,
15979 text: text_style,
15980 scrollbar_width: cx.editor_style.scrollbar_width,
15981 syntax: cx.editor_style.syntax.clone(),
15982 status: cx.editor_style.status.clone(),
15983 inlay_hints_style: HighlightStyle {
15984 font_weight: Some(FontWeight::BOLD),
15985 ..make_inlay_hints_style(cx.app)
15986 },
15987 inline_completion_styles: make_suggestion_styles(
15988 cx.app,
15989 ),
15990 ..EditorStyle::default()
15991 },
15992 ))
15993 .into_any_element()
15994 }
15995 }),
15996 priority: 0,
15997 render_in_minimap: true,
15998 }],
15999 Some(Autoscroll::fit()),
16000 cx,
16001 )[0];
16002 this.pending_rename = Some(RenameState {
16003 range,
16004 old_name,
16005 editor: rename_editor,
16006 block_id,
16007 });
16008 })?;
16009 }
16010
16011 Ok(())
16012 }))
16013 }
16014
16015 pub fn confirm_rename(
16016 &mut self,
16017 _: &ConfirmRename,
16018 window: &mut Window,
16019 cx: &mut Context<Self>,
16020 ) -> Option<Task<Result<()>>> {
16021 let rename = self.take_rename(false, window, cx)?;
16022 let workspace = self.workspace()?.downgrade();
16023 let (buffer, start) = self
16024 .buffer
16025 .read(cx)
16026 .text_anchor_for_position(rename.range.start, cx)?;
16027 let (end_buffer, _) = self
16028 .buffer
16029 .read(cx)
16030 .text_anchor_for_position(rename.range.end, cx)?;
16031 if buffer != end_buffer {
16032 return None;
16033 }
16034
16035 let old_name = rename.old_name;
16036 let new_name = rename.editor.read(cx).text(cx);
16037
16038 let rename = self.semantics_provider.as_ref()?.perform_rename(
16039 &buffer,
16040 start,
16041 new_name.clone(),
16042 cx,
16043 )?;
16044
16045 Some(cx.spawn_in(window, async move |editor, cx| {
16046 let project_transaction = rename.await?;
16047 Self::open_project_transaction(
16048 &editor,
16049 workspace,
16050 project_transaction,
16051 format!("Rename: {} → {}", old_name, new_name),
16052 cx,
16053 )
16054 .await?;
16055
16056 editor.update(cx, |editor, cx| {
16057 editor.refresh_document_highlights(cx);
16058 })?;
16059 Ok(())
16060 }))
16061 }
16062
16063 fn take_rename(
16064 &mut self,
16065 moving_cursor: bool,
16066 window: &mut Window,
16067 cx: &mut Context<Self>,
16068 ) -> Option<RenameState> {
16069 let rename = self.pending_rename.take()?;
16070 if rename.editor.focus_handle(cx).is_focused(window) {
16071 window.focus(&self.focus_handle);
16072 }
16073
16074 self.remove_blocks(
16075 [rename.block_id].into_iter().collect(),
16076 Some(Autoscroll::fit()),
16077 cx,
16078 );
16079 self.clear_highlights::<Rename>(cx);
16080 self.show_local_selections = true;
16081
16082 if moving_cursor {
16083 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
16084 editor.selections.newest::<usize>(cx).head()
16085 });
16086
16087 // Update the selection to match the position of the selection inside
16088 // the rename editor.
16089 let snapshot = self.buffer.read(cx).read(cx);
16090 let rename_range = rename.range.to_offset(&snapshot);
16091 let cursor_in_editor = snapshot
16092 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
16093 .min(rename_range.end);
16094 drop(snapshot);
16095
16096 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16097 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
16098 });
16099 } else {
16100 self.refresh_document_highlights(cx);
16101 }
16102
16103 Some(rename)
16104 }
16105
16106 pub fn pending_rename(&self) -> Option<&RenameState> {
16107 self.pending_rename.as_ref()
16108 }
16109
16110 fn format(
16111 &mut self,
16112 _: &Format,
16113 window: &mut Window,
16114 cx: &mut Context<Self>,
16115 ) -> Option<Task<Result<()>>> {
16116 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16117
16118 let project = match &self.project {
16119 Some(project) => project.clone(),
16120 None => return None,
16121 };
16122
16123 Some(self.perform_format(
16124 project,
16125 FormatTrigger::Manual,
16126 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
16127 window,
16128 cx,
16129 ))
16130 }
16131
16132 fn format_selections(
16133 &mut self,
16134 _: &FormatSelections,
16135 window: &mut Window,
16136 cx: &mut Context<Self>,
16137 ) -> Option<Task<Result<()>>> {
16138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16139
16140 let project = match &self.project {
16141 Some(project) => project.clone(),
16142 None => return None,
16143 };
16144
16145 let ranges = self
16146 .selections
16147 .all_adjusted(cx)
16148 .into_iter()
16149 .map(|selection| selection.range())
16150 .collect_vec();
16151
16152 Some(self.perform_format(
16153 project,
16154 FormatTrigger::Manual,
16155 FormatTarget::Ranges(ranges),
16156 window,
16157 cx,
16158 ))
16159 }
16160
16161 fn perform_format(
16162 &mut self,
16163 project: Entity<Project>,
16164 trigger: FormatTrigger,
16165 target: FormatTarget,
16166 window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) -> Task<Result<()>> {
16169 let buffer = self.buffer.clone();
16170 let (buffers, target) = match target {
16171 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
16172 FormatTarget::Ranges(selection_ranges) => {
16173 let multi_buffer = buffer.read(cx);
16174 let snapshot = multi_buffer.read(cx);
16175 let mut buffers = HashSet::default();
16176 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
16177 BTreeMap::new();
16178 for selection_range in selection_ranges {
16179 for (buffer, buffer_range, _) in
16180 snapshot.range_to_buffer_ranges(selection_range)
16181 {
16182 let buffer_id = buffer.remote_id();
16183 let start = buffer.anchor_before(buffer_range.start);
16184 let end = buffer.anchor_after(buffer_range.end);
16185 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
16186 buffer_id_to_ranges
16187 .entry(buffer_id)
16188 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
16189 .or_insert_with(|| vec![start..end]);
16190 }
16191 }
16192 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
16193 }
16194 };
16195
16196 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
16197 let selections_prev = transaction_id_prev
16198 .and_then(|transaction_id_prev| {
16199 // default to selections as they were after the last edit, if we have them,
16200 // instead of how they are now.
16201 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
16202 // will take you back to where you made the last edit, instead of staying where you scrolled
16203 self.selection_history
16204 .transaction(transaction_id_prev)
16205 .map(|t| t.0.clone())
16206 })
16207 .unwrap_or_else(|| {
16208 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
16209 self.selections.disjoint_anchors()
16210 });
16211
16212 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
16213 let format = project.update(cx, |project, cx| {
16214 project.format(buffers, target, true, trigger, cx)
16215 });
16216
16217 cx.spawn_in(window, async move |editor, cx| {
16218 let transaction = futures::select_biased! {
16219 transaction = format.log_err().fuse() => transaction,
16220 () = timeout => {
16221 log::warn!("timed out waiting for formatting");
16222 None
16223 }
16224 };
16225
16226 buffer
16227 .update(cx, |buffer, cx| {
16228 if let Some(transaction) = transaction {
16229 if !buffer.is_singleton() {
16230 buffer.push_transaction(&transaction.0, cx);
16231 }
16232 }
16233 cx.notify();
16234 })
16235 .ok();
16236
16237 if let Some(transaction_id_now) =
16238 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
16239 {
16240 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
16241 if has_new_transaction {
16242 _ = editor.update(cx, |editor, _| {
16243 editor
16244 .selection_history
16245 .insert_transaction(transaction_id_now, selections_prev);
16246 });
16247 }
16248 }
16249
16250 Ok(())
16251 })
16252 }
16253
16254 fn organize_imports(
16255 &mut self,
16256 _: &OrganizeImports,
16257 window: &mut Window,
16258 cx: &mut Context<Self>,
16259 ) -> Option<Task<Result<()>>> {
16260 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16261 let project = match &self.project {
16262 Some(project) => project.clone(),
16263 None => return None,
16264 };
16265 Some(self.perform_code_action_kind(
16266 project,
16267 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
16268 window,
16269 cx,
16270 ))
16271 }
16272
16273 fn perform_code_action_kind(
16274 &mut self,
16275 project: Entity<Project>,
16276 kind: CodeActionKind,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) -> Task<Result<()>> {
16280 let buffer = self.buffer.clone();
16281 let buffers = buffer.read(cx).all_buffers();
16282 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
16283 let apply_action = project.update(cx, |project, cx| {
16284 project.apply_code_action_kind(buffers, kind, true, cx)
16285 });
16286 cx.spawn_in(window, async move |_, cx| {
16287 let transaction = futures::select_biased! {
16288 () = timeout => {
16289 log::warn!("timed out waiting for executing code action");
16290 None
16291 }
16292 transaction = apply_action.log_err().fuse() => transaction,
16293 };
16294 buffer
16295 .update(cx, |buffer, cx| {
16296 // check if we need this
16297 if let Some(transaction) = transaction {
16298 if !buffer.is_singleton() {
16299 buffer.push_transaction(&transaction.0, cx);
16300 }
16301 }
16302 cx.notify();
16303 })
16304 .ok();
16305 Ok(())
16306 })
16307 }
16308
16309 pub fn restart_language_server(
16310 &mut self,
16311 _: &RestartLanguageServer,
16312 _: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 if let Some(project) = self.project.clone() {
16316 self.buffer.update(cx, |multi_buffer, cx| {
16317 project.update(cx, |project, cx| {
16318 project.restart_language_servers_for_buffers(
16319 multi_buffer.all_buffers().into_iter().collect(),
16320 HashSet::default(),
16321 cx,
16322 );
16323 });
16324 })
16325 }
16326 }
16327
16328 pub fn stop_language_server(
16329 &mut self,
16330 _: &StopLanguageServer,
16331 _: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) {
16334 if let Some(project) = self.project.clone() {
16335 self.buffer.update(cx, |multi_buffer, cx| {
16336 project.update(cx, |project, cx| {
16337 project.stop_language_servers_for_buffers(
16338 multi_buffer.all_buffers().into_iter().collect(),
16339 HashSet::default(),
16340 cx,
16341 );
16342 cx.emit(project::Event::RefreshInlayHints);
16343 });
16344 });
16345 }
16346 }
16347
16348 fn cancel_language_server_work(
16349 workspace: &mut Workspace,
16350 _: &actions::CancelLanguageServerWork,
16351 _: &mut Window,
16352 cx: &mut Context<Workspace>,
16353 ) {
16354 let project = workspace.project();
16355 let buffers = workspace
16356 .active_item(cx)
16357 .and_then(|item| item.act_as::<Editor>(cx))
16358 .map_or(HashSet::default(), |editor| {
16359 editor.read(cx).buffer.read(cx).all_buffers()
16360 });
16361 project.update(cx, |project, cx| {
16362 project.cancel_language_server_work_for_buffers(buffers, cx);
16363 });
16364 }
16365
16366 fn show_character_palette(
16367 &mut self,
16368 _: &ShowCharacterPalette,
16369 window: &mut Window,
16370 _: &mut Context<Self>,
16371 ) {
16372 window.show_character_palette();
16373 }
16374
16375 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16376 if !self.diagnostics_enabled() {
16377 return;
16378 }
16379
16380 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16381 let buffer = self.buffer.read(cx).snapshot(cx);
16382 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16383 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16384 let is_valid = buffer
16385 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16386 .any(|entry| {
16387 entry.diagnostic.is_primary
16388 && !entry.range.is_empty()
16389 && entry.range.start == primary_range_start
16390 && entry.diagnostic.message == active_diagnostics.active_message
16391 });
16392
16393 if !is_valid {
16394 self.dismiss_diagnostics(cx);
16395 }
16396 }
16397 }
16398
16399 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16400 match &self.active_diagnostics {
16401 ActiveDiagnostic::Group(group) => Some(group),
16402 _ => None,
16403 }
16404 }
16405
16406 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16407 if !self.diagnostics_enabled() {
16408 return;
16409 }
16410 self.dismiss_diagnostics(cx);
16411 self.active_diagnostics = ActiveDiagnostic::All;
16412 }
16413
16414 fn activate_diagnostics(
16415 &mut self,
16416 buffer_id: BufferId,
16417 diagnostic: DiagnosticEntry<usize>,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16422 return;
16423 }
16424 self.dismiss_diagnostics(cx);
16425 let snapshot = self.snapshot(window, cx);
16426 let buffer = self.buffer.read(cx).snapshot(cx);
16427 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16428 return;
16429 };
16430
16431 let diagnostic_group = buffer
16432 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16433 .collect::<Vec<_>>();
16434
16435 let blocks =
16436 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16437
16438 let blocks = self.display_map.update(cx, |display_map, cx| {
16439 display_map.insert_blocks(blocks, cx).into_iter().collect()
16440 });
16441 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16442 active_range: buffer.anchor_before(diagnostic.range.start)
16443 ..buffer.anchor_after(diagnostic.range.end),
16444 active_message: diagnostic.diagnostic.message.clone(),
16445 group_id: diagnostic.diagnostic.group_id,
16446 blocks,
16447 });
16448 cx.notify();
16449 }
16450
16451 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16452 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16453 return;
16454 };
16455
16456 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16457 if let ActiveDiagnostic::Group(group) = prev {
16458 self.display_map.update(cx, |display_map, cx| {
16459 display_map.remove_blocks(group.blocks, cx);
16460 });
16461 cx.notify();
16462 }
16463 }
16464
16465 /// Disable inline diagnostics rendering for this editor.
16466 pub fn disable_inline_diagnostics(&mut self) {
16467 self.inline_diagnostics_enabled = false;
16468 self.inline_diagnostics_update = Task::ready(());
16469 self.inline_diagnostics.clear();
16470 }
16471
16472 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
16473 self.diagnostics_enabled = false;
16474 self.dismiss_diagnostics(cx);
16475 self.inline_diagnostics_update = Task::ready(());
16476 self.inline_diagnostics.clear();
16477 }
16478
16479 pub fn diagnostics_enabled(&self) -> bool {
16480 self.diagnostics_enabled && self.mode.is_full()
16481 }
16482
16483 pub fn inline_diagnostics_enabled(&self) -> bool {
16484 self.inline_diagnostics_enabled && self.diagnostics_enabled()
16485 }
16486
16487 pub fn show_inline_diagnostics(&self) -> bool {
16488 self.show_inline_diagnostics
16489 }
16490
16491 pub fn toggle_inline_diagnostics(
16492 &mut self,
16493 _: &ToggleInlineDiagnostics,
16494 window: &mut Window,
16495 cx: &mut Context<Editor>,
16496 ) {
16497 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16498 self.refresh_inline_diagnostics(false, window, cx);
16499 }
16500
16501 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16502 self.diagnostics_max_severity = severity;
16503 self.display_map.update(cx, |display_map, _| {
16504 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16505 });
16506 }
16507
16508 pub fn toggle_diagnostics(
16509 &mut self,
16510 _: &ToggleDiagnostics,
16511 window: &mut Window,
16512 cx: &mut Context<Editor>,
16513 ) {
16514 if !self.diagnostics_enabled() {
16515 return;
16516 }
16517
16518 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16519 EditorSettings::get_global(cx)
16520 .diagnostics_max_severity
16521 .filter(|severity| severity != &DiagnosticSeverity::Off)
16522 .unwrap_or(DiagnosticSeverity::Hint)
16523 } else {
16524 DiagnosticSeverity::Off
16525 };
16526 self.set_max_diagnostics_severity(new_severity, cx);
16527 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16528 self.active_diagnostics = ActiveDiagnostic::None;
16529 self.inline_diagnostics_update = Task::ready(());
16530 self.inline_diagnostics.clear();
16531 } else {
16532 self.refresh_inline_diagnostics(false, window, cx);
16533 }
16534
16535 cx.notify();
16536 }
16537
16538 pub fn toggle_minimap(
16539 &mut self,
16540 _: &ToggleMinimap,
16541 window: &mut Window,
16542 cx: &mut Context<Editor>,
16543 ) {
16544 if self.supports_minimap(cx) {
16545 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16546 }
16547 }
16548
16549 fn refresh_inline_diagnostics(
16550 &mut self,
16551 debounce: bool,
16552 window: &mut Window,
16553 cx: &mut Context<Self>,
16554 ) {
16555 let max_severity = ProjectSettings::get_global(cx)
16556 .diagnostics
16557 .inline
16558 .max_severity
16559 .unwrap_or(self.diagnostics_max_severity);
16560
16561 if !self.inline_diagnostics_enabled()
16562 || !self.show_inline_diagnostics
16563 || max_severity == DiagnosticSeverity::Off
16564 {
16565 self.inline_diagnostics_update = Task::ready(());
16566 self.inline_diagnostics.clear();
16567 return;
16568 }
16569
16570 let debounce_ms = ProjectSettings::get_global(cx)
16571 .diagnostics
16572 .inline
16573 .update_debounce_ms;
16574 let debounce = if debounce && debounce_ms > 0 {
16575 Some(Duration::from_millis(debounce_ms))
16576 } else {
16577 None
16578 };
16579 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16580 if let Some(debounce) = debounce {
16581 cx.background_executor().timer(debounce).await;
16582 }
16583 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16584 editor
16585 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16586 .ok()
16587 }) else {
16588 return;
16589 };
16590
16591 let new_inline_diagnostics = cx
16592 .background_spawn(async move {
16593 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16594 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16595 let message = diagnostic_entry
16596 .diagnostic
16597 .message
16598 .split_once('\n')
16599 .map(|(line, _)| line)
16600 .map(SharedString::new)
16601 .unwrap_or_else(|| {
16602 SharedString::from(diagnostic_entry.diagnostic.message)
16603 });
16604 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16605 let (Ok(i) | Err(i)) = inline_diagnostics
16606 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16607 inline_diagnostics.insert(
16608 i,
16609 (
16610 start_anchor,
16611 InlineDiagnostic {
16612 message,
16613 group_id: diagnostic_entry.diagnostic.group_id,
16614 start: diagnostic_entry.range.start.to_point(&snapshot),
16615 is_primary: diagnostic_entry.diagnostic.is_primary,
16616 severity: diagnostic_entry.diagnostic.severity,
16617 },
16618 ),
16619 );
16620 }
16621 inline_diagnostics
16622 })
16623 .await;
16624
16625 editor
16626 .update(cx, |editor, cx| {
16627 editor.inline_diagnostics = new_inline_diagnostics;
16628 cx.notify();
16629 })
16630 .ok();
16631 });
16632 }
16633
16634 fn pull_diagnostics(
16635 &mut self,
16636 buffer_id: Option<BufferId>,
16637 window: &Window,
16638 cx: &mut Context<Self>,
16639 ) -> Option<()> {
16640 if !self.mode().is_full() {
16641 return None;
16642 }
16643 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16644 .diagnostics
16645 .lsp_pull_diagnostics;
16646 if !pull_diagnostics_settings.enabled {
16647 return None;
16648 }
16649 let project = self.project.as_ref()?.downgrade();
16650 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16651 let mut buffers = self.buffer.read(cx).all_buffers();
16652 if let Some(buffer_id) = buffer_id {
16653 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16654 }
16655
16656 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16657 cx.background_executor().timer(debounce).await;
16658
16659 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16660 buffers
16661 .into_iter()
16662 .filter_map(|buffer| {
16663 project
16664 .update(cx, |project, cx| {
16665 project.lsp_store().update(cx, |lsp_store, cx| {
16666 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16667 })
16668 })
16669 .ok()
16670 })
16671 .collect::<FuturesUnordered<_>>()
16672 }) else {
16673 return;
16674 };
16675
16676 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16677 match pull_task {
16678 Ok(()) => {
16679 if editor
16680 .update_in(cx, |editor, window, cx| {
16681 editor.update_diagnostics_state(window, cx);
16682 })
16683 .is_err()
16684 {
16685 return;
16686 }
16687 }
16688 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16689 }
16690 }
16691 });
16692
16693 Some(())
16694 }
16695
16696 pub fn set_selections_from_remote(
16697 &mut self,
16698 selections: Vec<Selection<Anchor>>,
16699 pending_selection: Option<Selection<Anchor>>,
16700 window: &mut Window,
16701 cx: &mut Context<Self>,
16702 ) {
16703 let old_cursor_position = self.selections.newest_anchor().head();
16704 self.selections.change_with(cx, |s| {
16705 s.select_anchors(selections);
16706 if let Some(pending_selection) = pending_selection {
16707 s.set_pending(pending_selection, SelectMode::Character);
16708 } else {
16709 s.clear_pending();
16710 }
16711 });
16712 self.selections_did_change(
16713 false,
16714 &old_cursor_position,
16715 SelectionEffects::default(),
16716 window,
16717 cx,
16718 );
16719 }
16720
16721 pub fn transact(
16722 &mut self,
16723 window: &mut Window,
16724 cx: &mut Context<Self>,
16725 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16726 ) -> Option<TransactionId> {
16727 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16728 this.start_transaction_at(Instant::now(), window, cx);
16729 update(this, window, cx);
16730 this.end_transaction_at(Instant::now(), cx)
16731 })
16732 }
16733
16734 pub fn start_transaction_at(
16735 &mut self,
16736 now: Instant,
16737 window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 self.end_selection(window, cx);
16741 if let Some(tx_id) = self
16742 .buffer
16743 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16744 {
16745 self.selection_history
16746 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16747 cx.emit(EditorEvent::TransactionBegun {
16748 transaction_id: tx_id,
16749 })
16750 }
16751 }
16752
16753 pub fn end_transaction_at(
16754 &mut self,
16755 now: Instant,
16756 cx: &mut Context<Self>,
16757 ) -> Option<TransactionId> {
16758 if let Some(transaction_id) = self
16759 .buffer
16760 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16761 {
16762 if let Some((_, end_selections)) =
16763 self.selection_history.transaction_mut(transaction_id)
16764 {
16765 *end_selections = Some(self.selections.disjoint_anchors());
16766 } else {
16767 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16768 }
16769
16770 cx.emit(EditorEvent::Edited { transaction_id });
16771 Some(transaction_id)
16772 } else {
16773 None
16774 }
16775 }
16776
16777 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16778 if self.selection_mark_mode {
16779 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16780 s.move_with(|_, sel| {
16781 sel.collapse_to(sel.head(), SelectionGoal::None);
16782 });
16783 })
16784 }
16785 self.selection_mark_mode = true;
16786 cx.notify();
16787 }
16788
16789 pub fn swap_selection_ends(
16790 &mut self,
16791 _: &actions::SwapSelectionEnds,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16796 s.move_with(|_, sel| {
16797 if sel.start != sel.end {
16798 sel.reversed = !sel.reversed
16799 }
16800 });
16801 });
16802 self.request_autoscroll(Autoscroll::newest(), cx);
16803 cx.notify();
16804 }
16805
16806 pub fn toggle_fold(
16807 &mut self,
16808 _: &actions::ToggleFold,
16809 window: &mut Window,
16810 cx: &mut Context<Self>,
16811 ) {
16812 if self.is_singleton(cx) {
16813 let selection = self.selections.newest::<Point>(cx);
16814
16815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16816 let range = if selection.is_empty() {
16817 let point = selection.head().to_display_point(&display_map);
16818 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16819 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16820 .to_point(&display_map);
16821 start..end
16822 } else {
16823 selection.range()
16824 };
16825 if display_map.folds_in_range(range).next().is_some() {
16826 self.unfold_lines(&Default::default(), window, cx)
16827 } else {
16828 self.fold(&Default::default(), window, cx)
16829 }
16830 } else {
16831 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16832 let buffer_ids: HashSet<_> = self
16833 .selections
16834 .disjoint_anchor_ranges()
16835 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16836 .collect();
16837
16838 let should_unfold = buffer_ids
16839 .iter()
16840 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16841
16842 for buffer_id in buffer_ids {
16843 if should_unfold {
16844 self.unfold_buffer(buffer_id, cx);
16845 } else {
16846 self.fold_buffer(buffer_id, cx);
16847 }
16848 }
16849 }
16850 }
16851
16852 pub fn toggle_fold_recursive(
16853 &mut self,
16854 _: &actions::ToggleFoldRecursive,
16855 window: &mut Window,
16856 cx: &mut Context<Self>,
16857 ) {
16858 let selection = self.selections.newest::<Point>(cx);
16859
16860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16861 let range = if selection.is_empty() {
16862 let point = selection.head().to_display_point(&display_map);
16863 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16864 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16865 .to_point(&display_map);
16866 start..end
16867 } else {
16868 selection.range()
16869 };
16870 if display_map.folds_in_range(range).next().is_some() {
16871 self.unfold_recursive(&Default::default(), window, cx)
16872 } else {
16873 self.fold_recursive(&Default::default(), window, cx)
16874 }
16875 }
16876
16877 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16878 if self.is_singleton(cx) {
16879 let mut to_fold = Vec::new();
16880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16881 let selections = self.selections.all_adjusted(cx);
16882
16883 for selection in selections {
16884 let range = selection.range().sorted();
16885 let buffer_start_row = range.start.row;
16886
16887 if range.start.row != range.end.row {
16888 let mut found = false;
16889 let mut row = range.start.row;
16890 while row <= range.end.row {
16891 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16892 {
16893 found = true;
16894 row = crease.range().end.row + 1;
16895 to_fold.push(crease);
16896 } else {
16897 row += 1
16898 }
16899 }
16900 if found {
16901 continue;
16902 }
16903 }
16904
16905 for row in (0..=range.start.row).rev() {
16906 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16907 if crease.range().end.row >= buffer_start_row {
16908 to_fold.push(crease);
16909 if row <= range.start.row {
16910 break;
16911 }
16912 }
16913 }
16914 }
16915 }
16916
16917 self.fold_creases(to_fold, true, window, cx);
16918 } else {
16919 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16920 let buffer_ids = self
16921 .selections
16922 .disjoint_anchor_ranges()
16923 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16924 .collect::<HashSet<_>>();
16925 for buffer_id in buffer_ids {
16926 self.fold_buffer(buffer_id, cx);
16927 }
16928 }
16929 }
16930
16931 fn fold_at_level(
16932 &mut self,
16933 fold_at: &FoldAtLevel,
16934 window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 if !self.buffer.read(cx).is_singleton() {
16938 return;
16939 }
16940
16941 let fold_at_level = fold_at.0;
16942 let snapshot = self.buffer.read(cx).snapshot(cx);
16943 let mut to_fold = Vec::new();
16944 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16945
16946 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16947 while start_row < end_row {
16948 match self
16949 .snapshot(window, cx)
16950 .crease_for_buffer_row(MultiBufferRow(start_row))
16951 {
16952 Some(crease) => {
16953 let nested_start_row = crease.range().start.row + 1;
16954 let nested_end_row = crease.range().end.row;
16955
16956 if current_level < fold_at_level {
16957 stack.push((nested_start_row, nested_end_row, current_level + 1));
16958 } else if current_level == fold_at_level {
16959 to_fold.push(crease);
16960 }
16961
16962 start_row = nested_end_row + 1;
16963 }
16964 None => start_row += 1,
16965 }
16966 }
16967 }
16968
16969 self.fold_creases(to_fold, true, window, cx);
16970 }
16971
16972 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16973 if self.buffer.read(cx).is_singleton() {
16974 let mut fold_ranges = Vec::new();
16975 let snapshot = self.buffer.read(cx).snapshot(cx);
16976
16977 for row in 0..snapshot.max_row().0 {
16978 if let Some(foldable_range) = self
16979 .snapshot(window, cx)
16980 .crease_for_buffer_row(MultiBufferRow(row))
16981 {
16982 fold_ranges.push(foldable_range);
16983 }
16984 }
16985
16986 self.fold_creases(fold_ranges, true, window, cx);
16987 } else {
16988 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16989 editor
16990 .update_in(cx, |editor, _, cx| {
16991 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16992 editor.fold_buffer(buffer_id, cx);
16993 }
16994 })
16995 .ok();
16996 });
16997 }
16998 }
16999
17000 pub fn fold_function_bodies(
17001 &mut self,
17002 _: &actions::FoldFunctionBodies,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 let snapshot = self.buffer.read(cx).snapshot(cx);
17007
17008 let ranges = snapshot
17009 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
17010 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
17011 .collect::<Vec<_>>();
17012
17013 let creases = ranges
17014 .into_iter()
17015 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
17016 .collect();
17017
17018 self.fold_creases(creases, true, window, cx);
17019 }
17020
17021 pub fn fold_recursive(
17022 &mut self,
17023 _: &actions::FoldRecursive,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) {
17027 let mut to_fold = Vec::new();
17028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17029 let selections = self.selections.all_adjusted(cx);
17030
17031 for selection in selections {
17032 let range = selection.range().sorted();
17033 let buffer_start_row = range.start.row;
17034
17035 if range.start.row != range.end.row {
17036 let mut found = false;
17037 for row in range.start.row..=range.end.row {
17038 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17039 found = true;
17040 to_fold.push(crease);
17041 }
17042 }
17043 if found {
17044 continue;
17045 }
17046 }
17047
17048 for row in (0..=range.start.row).rev() {
17049 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
17050 if crease.range().end.row >= buffer_start_row {
17051 to_fold.push(crease);
17052 } else {
17053 break;
17054 }
17055 }
17056 }
17057 }
17058
17059 self.fold_creases(to_fold, true, window, cx);
17060 }
17061
17062 pub fn fold_at(
17063 &mut self,
17064 buffer_row: MultiBufferRow,
17065 window: &mut Window,
17066 cx: &mut Context<Self>,
17067 ) {
17068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17069
17070 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
17071 let autoscroll = self
17072 .selections
17073 .all::<Point>(cx)
17074 .iter()
17075 .any(|selection| crease.range().overlaps(&selection.range()));
17076
17077 self.fold_creases(vec![crease], autoscroll, window, cx);
17078 }
17079 }
17080
17081 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
17082 if self.is_singleton(cx) {
17083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17084 let buffer = &display_map.buffer_snapshot;
17085 let selections = self.selections.all::<Point>(cx);
17086 let ranges = selections
17087 .iter()
17088 .map(|s| {
17089 let range = s.display_range(&display_map).sorted();
17090 let mut start = range.start.to_point(&display_map);
17091 let mut end = range.end.to_point(&display_map);
17092 start.column = 0;
17093 end.column = buffer.line_len(MultiBufferRow(end.row));
17094 start..end
17095 })
17096 .collect::<Vec<_>>();
17097
17098 self.unfold_ranges(&ranges, true, true, cx);
17099 } else {
17100 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17101 let buffer_ids = self
17102 .selections
17103 .disjoint_anchor_ranges()
17104 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17105 .collect::<HashSet<_>>();
17106 for buffer_id in buffer_ids {
17107 self.unfold_buffer(buffer_id, cx);
17108 }
17109 }
17110 }
17111
17112 pub fn unfold_recursive(
17113 &mut self,
17114 _: &UnfoldRecursive,
17115 _window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) {
17118 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17119 let selections = self.selections.all::<Point>(cx);
17120 let ranges = selections
17121 .iter()
17122 .map(|s| {
17123 let mut range = s.display_range(&display_map).sorted();
17124 *range.start.column_mut() = 0;
17125 *range.end.column_mut() = display_map.line_len(range.end.row());
17126 let start = range.start.to_point(&display_map);
17127 let end = range.end.to_point(&display_map);
17128 start..end
17129 })
17130 .collect::<Vec<_>>();
17131
17132 self.unfold_ranges(&ranges, true, true, cx);
17133 }
17134
17135 pub fn unfold_at(
17136 &mut self,
17137 buffer_row: MultiBufferRow,
17138 _window: &mut Window,
17139 cx: &mut Context<Self>,
17140 ) {
17141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17142
17143 let intersection_range = Point::new(buffer_row.0, 0)
17144 ..Point::new(
17145 buffer_row.0,
17146 display_map.buffer_snapshot.line_len(buffer_row),
17147 );
17148
17149 let autoscroll = self
17150 .selections
17151 .all::<Point>(cx)
17152 .iter()
17153 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
17154
17155 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
17156 }
17157
17158 pub fn unfold_all(
17159 &mut self,
17160 _: &actions::UnfoldAll,
17161 _window: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) {
17164 if self.buffer.read(cx).is_singleton() {
17165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17166 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
17167 } else {
17168 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
17169 editor
17170 .update(cx, |editor, cx| {
17171 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
17172 editor.unfold_buffer(buffer_id, cx);
17173 }
17174 })
17175 .ok();
17176 });
17177 }
17178 }
17179
17180 pub fn fold_selected_ranges(
17181 &mut self,
17182 _: &FoldSelectedRanges,
17183 window: &mut Window,
17184 cx: &mut Context<Self>,
17185 ) {
17186 let selections = self.selections.all_adjusted(cx);
17187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17188 let ranges = selections
17189 .into_iter()
17190 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
17191 .collect::<Vec<_>>();
17192 self.fold_creases(ranges, true, window, cx);
17193 }
17194
17195 pub fn fold_ranges<T: ToOffset + Clone>(
17196 &mut self,
17197 ranges: Vec<Range<T>>,
17198 auto_scroll: bool,
17199 window: &mut Window,
17200 cx: &mut Context<Self>,
17201 ) {
17202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17203 let ranges = ranges
17204 .into_iter()
17205 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
17206 .collect::<Vec<_>>();
17207 self.fold_creases(ranges, auto_scroll, window, cx);
17208 }
17209
17210 pub fn fold_creases<T: ToOffset + Clone>(
17211 &mut self,
17212 creases: Vec<Crease<T>>,
17213 auto_scroll: bool,
17214 _window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 if creases.is_empty() {
17218 return;
17219 }
17220
17221 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
17222
17223 if auto_scroll {
17224 self.request_autoscroll(Autoscroll::fit(), cx);
17225 }
17226
17227 cx.notify();
17228
17229 self.scrollbar_marker_state.dirty = true;
17230 self.folds_did_change(cx);
17231 }
17232
17233 /// Removes any folds whose ranges intersect any of the given ranges.
17234 pub fn unfold_ranges<T: ToOffset + Clone>(
17235 &mut self,
17236 ranges: &[Range<T>],
17237 inclusive: bool,
17238 auto_scroll: bool,
17239 cx: &mut Context<Self>,
17240 ) {
17241 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17242 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
17243 });
17244 self.folds_did_change(cx);
17245 }
17246
17247 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17248 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
17249 return;
17250 }
17251 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17252 self.display_map.update(cx, |display_map, cx| {
17253 display_map.fold_buffers([buffer_id], cx)
17254 });
17255 cx.emit(EditorEvent::BufferFoldToggled {
17256 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
17257 folded: true,
17258 });
17259 cx.notify();
17260 }
17261
17262 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17263 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
17264 return;
17265 }
17266 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
17267 self.display_map.update(cx, |display_map, cx| {
17268 display_map.unfold_buffers([buffer_id], cx);
17269 });
17270 cx.emit(EditorEvent::BufferFoldToggled {
17271 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
17272 folded: false,
17273 });
17274 cx.notify();
17275 }
17276
17277 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
17278 self.display_map.read(cx).is_buffer_folded(buffer)
17279 }
17280
17281 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
17282 self.display_map.read(cx).folded_buffers()
17283 }
17284
17285 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
17286 self.display_map.update(cx, |display_map, cx| {
17287 display_map.disable_header_for_buffer(buffer_id, cx);
17288 });
17289 cx.notify();
17290 }
17291
17292 /// Removes any folds with the given ranges.
17293 pub fn remove_folds_with_type<T: ToOffset + Clone>(
17294 &mut self,
17295 ranges: &[Range<T>],
17296 type_id: TypeId,
17297 auto_scroll: bool,
17298 cx: &mut Context<Self>,
17299 ) {
17300 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
17301 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
17302 });
17303 self.folds_did_change(cx);
17304 }
17305
17306 fn remove_folds_with<T: ToOffset + Clone>(
17307 &mut self,
17308 ranges: &[Range<T>],
17309 auto_scroll: bool,
17310 cx: &mut Context<Self>,
17311 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
17312 ) {
17313 if ranges.is_empty() {
17314 return;
17315 }
17316
17317 let mut buffers_affected = HashSet::default();
17318 let multi_buffer = self.buffer().read(cx);
17319 for range in ranges {
17320 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
17321 buffers_affected.insert(buffer.read(cx).remote_id());
17322 };
17323 }
17324
17325 self.display_map.update(cx, update);
17326
17327 if auto_scroll {
17328 self.request_autoscroll(Autoscroll::fit(), cx);
17329 }
17330
17331 cx.notify();
17332 self.scrollbar_marker_state.dirty = true;
17333 self.active_indent_guides_state.dirty = true;
17334 }
17335
17336 pub fn update_renderer_widths(
17337 &mut self,
17338 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
17339 cx: &mut Context<Self>,
17340 ) -> bool {
17341 self.display_map
17342 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17343 }
17344
17345 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17346 self.display_map.read(cx).fold_placeholder.clone()
17347 }
17348
17349 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17350 self.buffer.update(cx, |buffer, cx| {
17351 buffer.set_all_diff_hunks_expanded(cx);
17352 });
17353 }
17354
17355 pub fn expand_all_diff_hunks(
17356 &mut self,
17357 _: &ExpandAllDiffHunks,
17358 _window: &mut Window,
17359 cx: &mut Context<Self>,
17360 ) {
17361 self.buffer.update(cx, |buffer, cx| {
17362 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17363 });
17364 }
17365
17366 pub fn toggle_selected_diff_hunks(
17367 &mut self,
17368 _: &ToggleSelectedDiffHunks,
17369 _window: &mut Window,
17370 cx: &mut Context<Self>,
17371 ) {
17372 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17373 self.toggle_diff_hunks_in_ranges(ranges, cx);
17374 }
17375
17376 pub fn diff_hunks_in_ranges<'a>(
17377 &'a self,
17378 ranges: &'a [Range<Anchor>],
17379 buffer: &'a MultiBufferSnapshot,
17380 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17381 ranges.iter().flat_map(move |range| {
17382 let end_excerpt_id = range.end.excerpt_id;
17383 let range = range.to_point(buffer);
17384 let mut peek_end = range.end;
17385 if range.end.row < buffer.max_row().0 {
17386 peek_end = Point::new(range.end.row + 1, 0);
17387 }
17388 buffer
17389 .diff_hunks_in_range(range.start..peek_end)
17390 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17391 })
17392 }
17393
17394 pub fn has_stageable_diff_hunks_in_ranges(
17395 &self,
17396 ranges: &[Range<Anchor>],
17397 snapshot: &MultiBufferSnapshot,
17398 ) -> bool {
17399 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17400 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17401 }
17402
17403 pub fn toggle_staged_selected_diff_hunks(
17404 &mut self,
17405 _: &::git::ToggleStaged,
17406 _: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 let snapshot = self.buffer.read(cx).snapshot(cx);
17410 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17411 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17412 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17413 }
17414
17415 pub fn set_render_diff_hunk_controls(
17416 &mut self,
17417 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17418 cx: &mut Context<Self>,
17419 ) {
17420 self.render_diff_hunk_controls = render_diff_hunk_controls;
17421 cx.notify();
17422 }
17423
17424 pub fn stage_and_next(
17425 &mut self,
17426 _: &::git::StageAndNext,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.do_stage_or_unstage_and_next(true, window, cx);
17431 }
17432
17433 pub fn unstage_and_next(
17434 &mut self,
17435 _: &::git::UnstageAndNext,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 self.do_stage_or_unstage_and_next(false, window, cx);
17440 }
17441
17442 pub fn stage_or_unstage_diff_hunks(
17443 &mut self,
17444 stage: bool,
17445 ranges: Vec<Range<Anchor>>,
17446 cx: &mut Context<Self>,
17447 ) {
17448 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17449 cx.spawn(async move |this, cx| {
17450 task.await?;
17451 this.update(cx, |this, cx| {
17452 let snapshot = this.buffer.read(cx).snapshot(cx);
17453 let chunk_by = this
17454 .diff_hunks_in_ranges(&ranges, &snapshot)
17455 .chunk_by(|hunk| hunk.buffer_id);
17456 for (buffer_id, hunks) in &chunk_by {
17457 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17458 }
17459 })
17460 })
17461 .detach_and_log_err(cx);
17462 }
17463
17464 fn save_buffers_for_ranges_if_needed(
17465 &mut self,
17466 ranges: &[Range<Anchor>],
17467 cx: &mut Context<Editor>,
17468 ) -> Task<Result<()>> {
17469 let multibuffer = self.buffer.read(cx);
17470 let snapshot = multibuffer.read(cx);
17471 let buffer_ids: HashSet<_> = ranges
17472 .iter()
17473 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17474 .collect();
17475 drop(snapshot);
17476
17477 let mut buffers = HashSet::default();
17478 for buffer_id in buffer_ids {
17479 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17480 let buffer = buffer_entity.read(cx);
17481 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17482 {
17483 buffers.insert(buffer_entity);
17484 }
17485 }
17486 }
17487
17488 if let Some(project) = &self.project {
17489 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17490 } else {
17491 Task::ready(Ok(()))
17492 }
17493 }
17494
17495 fn do_stage_or_unstage_and_next(
17496 &mut self,
17497 stage: bool,
17498 window: &mut Window,
17499 cx: &mut Context<Self>,
17500 ) {
17501 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17502
17503 if ranges.iter().any(|range| range.start != range.end) {
17504 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17505 return;
17506 }
17507
17508 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17509 let snapshot = self.snapshot(window, cx);
17510 let position = self.selections.newest::<Point>(cx).head();
17511 let mut row = snapshot
17512 .buffer_snapshot
17513 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17514 .find(|hunk| hunk.row_range.start.0 > position.row)
17515 .map(|hunk| hunk.row_range.start);
17516
17517 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17518 // Outside of the project diff editor, wrap around to the beginning.
17519 if !all_diff_hunks_expanded {
17520 row = row.or_else(|| {
17521 snapshot
17522 .buffer_snapshot
17523 .diff_hunks_in_range(Point::zero()..position)
17524 .find(|hunk| hunk.row_range.end.0 < position.row)
17525 .map(|hunk| hunk.row_range.start)
17526 });
17527 }
17528
17529 if let Some(row) = row {
17530 let destination = Point::new(row.0, 0);
17531 let autoscroll = Autoscroll::center();
17532
17533 self.unfold_ranges(&[destination..destination], false, false, cx);
17534 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17535 s.select_ranges([destination..destination]);
17536 });
17537 }
17538 }
17539
17540 fn do_stage_or_unstage(
17541 &self,
17542 stage: bool,
17543 buffer_id: BufferId,
17544 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17545 cx: &mut App,
17546 ) -> Option<()> {
17547 let project = self.project.as_ref()?;
17548 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17549 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17550 let buffer_snapshot = buffer.read(cx).snapshot();
17551 let file_exists = buffer_snapshot
17552 .file()
17553 .is_some_and(|file| file.disk_state().exists());
17554 diff.update(cx, |diff, cx| {
17555 diff.stage_or_unstage_hunks(
17556 stage,
17557 &hunks
17558 .map(|hunk| buffer_diff::DiffHunk {
17559 buffer_range: hunk.buffer_range,
17560 diff_base_byte_range: hunk.diff_base_byte_range,
17561 secondary_status: hunk.secondary_status,
17562 range: Point::zero()..Point::zero(), // unused
17563 })
17564 .collect::<Vec<_>>(),
17565 &buffer_snapshot,
17566 file_exists,
17567 cx,
17568 )
17569 });
17570 None
17571 }
17572
17573 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17574 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17575 self.buffer
17576 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17577 }
17578
17579 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17580 self.buffer.update(cx, |buffer, cx| {
17581 let ranges = vec![Anchor::min()..Anchor::max()];
17582 if !buffer.all_diff_hunks_expanded()
17583 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17584 {
17585 buffer.collapse_diff_hunks(ranges, cx);
17586 true
17587 } else {
17588 false
17589 }
17590 })
17591 }
17592
17593 fn toggle_diff_hunks_in_ranges(
17594 &mut self,
17595 ranges: Vec<Range<Anchor>>,
17596 cx: &mut Context<Editor>,
17597 ) {
17598 self.buffer.update(cx, |buffer, cx| {
17599 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17600 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17601 })
17602 }
17603
17604 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17605 self.buffer.update(cx, |buffer, cx| {
17606 let snapshot = buffer.snapshot(cx);
17607 let excerpt_id = range.end.excerpt_id;
17608 let point_range = range.to_point(&snapshot);
17609 let expand = !buffer.single_hunk_is_expanded(range, cx);
17610 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17611 })
17612 }
17613
17614 pub(crate) fn apply_all_diff_hunks(
17615 &mut self,
17616 _: &ApplyAllDiffHunks,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17621
17622 let buffers = self.buffer.read(cx).all_buffers();
17623 for branch_buffer in buffers {
17624 branch_buffer.update(cx, |branch_buffer, cx| {
17625 branch_buffer.merge_into_base(Vec::new(), cx);
17626 });
17627 }
17628
17629 if let Some(project) = self.project.clone() {
17630 self.save(
17631 SaveOptions {
17632 format: true,
17633 autosave: false,
17634 },
17635 project,
17636 window,
17637 cx,
17638 )
17639 .detach_and_log_err(cx);
17640 }
17641 }
17642
17643 pub(crate) fn apply_selected_diff_hunks(
17644 &mut self,
17645 _: &ApplyDiffHunk,
17646 window: &mut Window,
17647 cx: &mut Context<Self>,
17648 ) {
17649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17650 let snapshot = self.snapshot(window, cx);
17651 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17652 let mut ranges_by_buffer = HashMap::default();
17653 self.transact(window, cx, |editor, _window, cx| {
17654 for hunk in hunks {
17655 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17656 ranges_by_buffer
17657 .entry(buffer.clone())
17658 .or_insert_with(Vec::new)
17659 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17660 }
17661 }
17662
17663 for (buffer, ranges) in ranges_by_buffer {
17664 buffer.update(cx, |buffer, cx| {
17665 buffer.merge_into_base(ranges, cx);
17666 });
17667 }
17668 });
17669
17670 if let Some(project) = self.project.clone() {
17671 self.save(
17672 SaveOptions {
17673 format: true,
17674 autosave: false,
17675 },
17676 project,
17677 window,
17678 cx,
17679 )
17680 .detach_and_log_err(cx);
17681 }
17682 }
17683
17684 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17685 if hovered != self.gutter_hovered {
17686 self.gutter_hovered = hovered;
17687 cx.notify();
17688 }
17689 }
17690
17691 pub fn insert_blocks(
17692 &mut self,
17693 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17694 autoscroll: Option<Autoscroll>,
17695 cx: &mut Context<Self>,
17696 ) -> Vec<CustomBlockId> {
17697 let blocks = self
17698 .display_map
17699 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17700 if let Some(autoscroll) = autoscroll {
17701 self.request_autoscroll(autoscroll, cx);
17702 }
17703 cx.notify();
17704 blocks
17705 }
17706
17707 pub fn resize_blocks(
17708 &mut self,
17709 heights: HashMap<CustomBlockId, u32>,
17710 autoscroll: Option<Autoscroll>,
17711 cx: &mut Context<Self>,
17712 ) {
17713 self.display_map
17714 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17715 if let Some(autoscroll) = autoscroll {
17716 self.request_autoscroll(autoscroll, cx);
17717 }
17718 cx.notify();
17719 }
17720
17721 pub fn replace_blocks(
17722 &mut self,
17723 renderers: HashMap<CustomBlockId, RenderBlock>,
17724 autoscroll: Option<Autoscroll>,
17725 cx: &mut Context<Self>,
17726 ) {
17727 self.display_map
17728 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17729 if let Some(autoscroll) = autoscroll {
17730 self.request_autoscroll(autoscroll, cx);
17731 }
17732 cx.notify();
17733 }
17734
17735 pub fn remove_blocks(
17736 &mut self,
17737 block_ids: HashSet<CustomBlockId>,
17738 autoscroll: Option<Autoscroll>,
17739 cx: &mut Context<Self>,
17740 ) {
17741 self.display_map.update(cx, |display_map, cx| {
17742 display_map.remove_blocks(block_ids, cx)
17743 });
17744 if let Some(autoscroll) = autoscroll {
17745 self.request_autoscroll(autoscroll, cx);
17746 }
17747 cx.notify();
17748 }
17749
17750 pub fn row_for_block(
17751 &self,
17752 block_id: CustomBlockId,
17753 cx: &mut Context<Self>,
17754 ) -> Option<DisplayRow> {
17755 self.display_map
17756 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17757 }
17758
17759 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17760 self.focused_block = Some(focused_block);
17761 }
17762
17763 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17764 self.focused_block.take()
17765 }
17766
17767 pub fn insert_creases(
17768 &mut self,
17769 creases: impl IntoIterator<Item = Crease<Anchor>>,
17770 cx: &mut Context<Self>,
17771 ) -> Vec<CreaseId> {
17772 self.display_map
17773 .update(cx, |map, cx| map.insert_creases(creases, cx))
17774 }
17775
17776 pub fn remove_creases(
17777 &mut self,
17778 ids: impl IntoIterator<Item = CreaseId>,
17779 cx: &mut Context<Self>,
17780 ) -> Vec<(CreaseId, Range<Anchor>)> {
17781 self.display_map
17782 .update(cx, |map, cx| map.remove_creases(ids, cx))
17783 }
17784
17785 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17786 self.display_map
17787 .update(cx, |map, cx| map.snapshot(cx))
17788 .longest_row()
17789 }
17790
17791 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17792 self.display_map
17793 .update(cx, |map, cx| map.snapshot(cx))
17794 .max_point()
17795 }
17796
17797 pub fn text(&self, cx: &App) -> String {
17798 self.buffer.read(cx).read(cx).text()
17799 }
17800
17801 pub fn is_empty(&self, cx: &App) -> bool {
17802 self.buffer.read(cx).read(cx).is_empty()
17803 }
17804
17805 pub fn text_option(&self, cx: &App) -> Option<String> {
17806 let text = self.text(cx);
17807 let text = text.trim();
17808
17809 if text.is_empty() {
17810 return None;
17811 }
17812
17813 Some(text.to_string())
17814 }
17815
17816 pub fn set_text(
17817 &mut self,
17818 text: impl Into<Arc<str>>,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 self.transact(window, cx, |this, _, cx| {
17823 this.buffer
17824 .read(cx)
17825 .as_singleton()
17826 .expect("you can only call set_text on editors for singleton buffers")
17827 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17828 });
17829 }
17830
17831 pub fn display_text(&self, cx: &mut App) -> String {
17832 self.display_map
17833 .update(cx, |map, cx| map.snapshot(cx))
17834 .text()
17835 }
17836
17837 fn create_minimap(
17838 &self,
17839 minimap_settings: MinimapSettings,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) -> Option<Entity<Self>> {
17843 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17844 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17845 }
17846
17847 fn initialize_new_minimap(
17848 &self,
17849 minimap_settings: MinimapSettings,
17850 window: &mut Window,
17851 cx: &mut Context<Self>,
17852 ) -> Entity<Self> {
17853 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17854
17855 let mut minimap = Editor::new_internal(
17856 EditorMode::Minimap {
17857 parent: cx.weak_entity(),
17858 },
17859 self.buffer.clone(),
17860 self.project.clone(),
17861 Some(self.display_map.clone()),
17862 window,
17863 cx,
17864 );
17865 minimap.scroll_manager.clone_state(&self.scroll_manager);
17866 minimap.set_text_style_refinement(TextStyleRefinement {
17867 font_size: Some(MINIMAP_FONT_SIZE),
17868 font_weight: Some(MINIMAP_FONT_WEIGHT),
17869 ..Default::default()
17870 });
17871 minimap.update_minimap_configuration(minimap_settings, cx);
17872 cx.new(|_| minimap)
17873 }
17874
17875 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17876 let current_line_highlight = minimap_settings
17877 .current_line_highlight
17878 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17879 self.set_current_line_highlight(Some(current_line_highlight));
17880 }
17881
17882 pub fn minimap(&self) -> Option<&Entity<Self>> {
17883 self.minimap
17884 .as_ref()
17885 .filter(|_| self.minimap_visibility.visible())
17886 }
17887
17888 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17889 let mut wrap_guides = smallvec![];
17890
17891 if self.show_wrap_guides == Some(false) {
17892 return wrap_guides;
17893 }
17894
17895 let settings = self.buffer.read(cx).language_settings(cx);
17896 if settings.show_wrap_guides {
17897 match self.soft_wrap_mode(cx) {
17898 SoftWrap::Column(soft_wrap) => {
17899 wrap_guides.push((soft_wrap as usize, true));
17900 }
17901 SoftWrap::Bounded(soft_wrap) => {
17902 wrap_guides.push((soft_wrap as usize, true));
17903 }
17904 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17905 }
17906 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17907 }
17908
17909 wrap_guides
17910 }
17911
17912 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17913 let settings = self.buffer.read(cx).language_settings(cx);
17914 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17915 match mode {
17916 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17917 SoftWrap::None
17918 }
17919 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17920 language_settings::SoftWrap::PreferredLineLength => {
17921 SoftWrap::Column(settings.preferred_line_length)
17922 }
17923 language_settings::SoftWrap::Bounded => {
17924 SoftWrap::Bounded(settings.preferred_line_length)
17925 }
17926 }
17927 }
17928
17929 pub fn set_soft_wrap_mode(
17930 &mut self,
17931 mode: language_settings::SoftWrap,
17932
17933 cx: &mut Context<Self>,
17934 ) {
17935 self.soft_wrap_mode_override = Some(mode);
17936 cx.notify();
17937 }
17938
17939 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17940 self.hard_wrap = hard_wrap;
17941 cx.notify();
17942 }
17943
17944 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17945 self.text_style_refinement = Some(style);
17946 }
17947
17948 /// called by the Element so we know what style we were most recently rendered with.
17949 pub(crate) fn set_style(
17950 &mut self,
17951 style: EditorStyle,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 // We intentionally do not inform the display map about the minimap style
17956 // so that wrapping is not recalculated and stays consistent for the editor
17957 // and its linked minimap.
17958 if !self.mode.is_minimap() {
17959 let rem_size = window.rem_size();
17960 self.display_map.update(cx, |map, cx| {
17961 map.set_font(
17962 style.text.font(),
17963 style.text.font_size.to_pixels(rem_size),
17964 cx,
17965 )
17966 });
17967 }
17968 self.style = Some(style);
17969 }
17970
17971 pub fn style(&self) -> Option<&EditorStyle> {
17972 self.style.as_ref()
17973 }
17974
17975 // Called by the element. This method is not designed to be called outside of the editor
17976 // element's layout code because it does not notify when rewrapping is computed synchronously.
17977 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17978 self.display_map
17979 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17980 }
17981
17982 pub fn set_soft_wrap(&mut self) {
17983 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17984 }
17985
17986 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17987 if self.soft_wrap_mode_override.is_some() {
17988 self.soft_wrap_mode_override.take();
17989 } else {
17990 let soft_wrap = match self.soft_wrap_mode(cx) {
17991 SoftWrap::GitDiff => return,
17992 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17993 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17994 language_settings::SoftWrap::None
17995 }
17996 };
17997 self.soft_wrap_mode_override = Some(soft_wrap);
17998 }
17999 cx.notify();
18000 }
18001
18002 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
18003 let Some(workspace) = self.workspace() else {
18004 return;
18005 };
18006 let fs = workspace.read(cx).app_state().fs.clone();
18007 let current_show = TabBarSettings::get_global(cx).show;
18008 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
18009 setting.show = Some(!current_show);
18010 });
18011 }
18012
18013 pub fn toggle_indent_guides(
18014 &mut self,
18015 _: &ToggleIndentGuides,
18016 _: &mut Window,
18017 cx: &mut Context<Self>,
18018 ) {
18019 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
18020 self.buffer
18021 .read(cx)
18022 .language_settings(cx)
18023 .indent_guides
18024 .enabled
18025 });
18026 self.show_indent_guides = Some(!currently_enabled);
18027 cx.notify();
18028 }
18029
18030 fn should_show_indent_guides(&self) -> Option<bool> {
18031 self.show_indent_guides
18032 }
18033
18034 pub fn toggle_line_numbers(
18035 &mut self,
18036 _: &ToggleLineNumbers,
18037 _: &mut Window,
18038 cx: &mut Context<Self>,
18039 ) {
18040 let mut editor_settings = EditorSettings::get_global(cx).clone();
18041 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
18042 EditorSettings::override_global(editor_settings, cx);
18043 }
18044
18045 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
18046 if let Some(show_line_numbers) = self.show_line_numbers {
18047 return show_line_numbers;
18048 }
18049 EditorSettings::get_global(cx).gutter.line_numbers
18050 }
18051
18052 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
18053 self.use_relative_line_numbers
18054 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
18055 }
18056
18057 pub fn toggle_relative_line_numbers(
18058 &mut self,
18059 _: &ToggleRelativeLineNumbers,
18060 _: &mut Window,
18061 cx: &mut Context<Self>,
18062 ) {
18063 let is_relative = self.should_use_relative_line_numbers(cx);
18064 self.set_relative_line_number(Some(!is_relative), cx)
18065 }
18066
18067 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
18068 self.use_relative_line_numbers = is_relative;
18069 cx.notify();
18070 }
18071
18072 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
18073 self.show_gutter = show_gutter;
18074 cx.notify();
18075 }
18076
18077 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
18078 self.show_scrollbars = ScrollbarAxes {
18079 horizontal: show,
18080 vertical: show,
18081 };
18082 cx.notify();
18083 }
18084
18085 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18086 self.show_scrollbars.vertical = show;
18087 cx.notify();
18088 }
18089
18090 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
18091 self.show_scrollbars.horizontal = show;
18092 cx.notify();
18093 }
18094
18095 pub fn set_minimap_visibility(
18096 &mut self,
18097 minimap_visibility: MinimapVisibility,
18098 window: &mut Window,
18099 cx: &mut Context<Self>,
18100 ) {
18101 if self.minimap_visibility != minimap_visibility {
18102 if minimap_visibility.visible() && self.minimap.is_none() {
18103 let minimap_settings = EditorSettings::get_global(cx).minimap;
18104 self.minimap =
18105 self.create_minimap(minimap_settings.with_show_override(), window, cx);
18106 }
18107 self.minimap_visibility = minimap_visibility;
18108 cx.notify();
18109 }
18110 }
18111
18112 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18113 self.set_show_scrollbars(false, cx);
18114 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
18115 }
18116
18117 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18118 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
18119 }
18120
18121 /// Normally the text in full mode and auto height editors is padded on the
18122 /// left side by roughly half a character width for improved hit testing.
18123 ///
18124 /// Use this method to disable this for cases where this is not wanted (e.g.
18125 /// if you want to align the editor text with some other text above or below)
18126 /// or if you want to add this padding to single-line editors.
18127 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
18128 self.offset_content = offset_content;
18129 cx.notify();
18130 }
18131
18132 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
18133 self.show_line_numbers = Some(show_line_numbers);
18134 cx.notify();
18135 }
18136
18137 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
18138 self.disable_expand_excerpt_buttons = true;
18139 cx.notify();
18140 }
18141
18142 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
18143 self.show_git_diff_gutter = Some(show_git_diff_gutter);
18144 cx.notify();
18145 }
18146
18147 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
18148 self.show_code_actions = Some(show_code_actions);
18149 cx.notify();
18150 }
18151
18152 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
18153 self.show_runnables = Some(show_runnables);
18154 cx.notify();
18155 }
18156
18157 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
18158 self.show_breakpoints = Some(show_breakpoints);
18159 cx.notify();
18160 }
18161
18162 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
18163 if self.display_map.read(cx).masked != masked {
18164 self.display_map.update(cx, |map, _| map.masked = masked);
18165 }
18166 cx.notify()
18167 }
18168
18169 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
18170 self.show_wrap_guides = Some(show_wrap_guides);
18171 cx.notify();
18172 }
18173
18174 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
18175 self.show_indent_guides = Some(show_indent_guides);
18176 cx.notify();
18177 }
18178
18179 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
18180 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
18181 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
18182 if let Some(dir) = file.abs_path(cx).parent() {
18183 return Some(dir.to_owned());
18184 }
18185 }
18186
18187 if let Some(project_path) = buffer.read(cx).project_path(cx) {
18188 return Some(project_path.path.to_path_buf());
18189 }
18190 }
18191
18192 None
18193 }
18194
18195 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
18196 self.active_excerpt(cx)?
18197 .1
18198 .read(cx)
18199 .file()
18200 .and_then(|f| f.as_local())
18201 }
18202
18203 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18204 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18205 let buffer = buffer.read(cx);
18206 if let Some(project_path) = buffer.project_path(cx) {
18207 let project = self.project.as_ref()?.read(cx);
18208 project.absolute_path(&project_path, cx)
18209 } else {
18210 buffer
18211 .file()
18212 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
18213 }
18214 })
18215 }
18216
18217 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
18218 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
18219 let project_path = buffer.read(cx).project_path(cx)?;
18220 let project = self.project.as_ref()?.read(cx);
18221 let entry = project.entry_for_path(&project_path, cx)?;
18222 let path = entry.path.to_path_buf();
18223 Some(path)
18224 })
18225 }
18226
18227 pub fn reveal_in_finder(
18228 &mut self,
18229 _: &RevealInFileManager,
18230 _window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 if let Some(target) = self.target_file(cx) {
18234 cx.reveal_path(&target.abs_path(cx));
18235 }
18236 }
18237
18238 pub fn copy_path(
18239 &mut self,
18240 _: &zed_actions::workspace::CopyPath,
18241 _window: &mut Window,
18242 cx: &mut Context<Self>,
18243 ) {
18244 if let Some(path) = self.target_file_abs_path(cx) {
18245 if let Some(path) = path.to_str() {
18246 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18247 }
18248 }
18249 }
18250
18251 pub fn copy_relative_path(
18252 &mut self,
18253 _: &zed_actions::workspace::CopyRelativePath,
18254 _window: &mut Window,
18255 cx: &mut Context<Self>,
18256 ) {
18257 if let Some(path) = self.target_file_path(cx) {
18258 if let Some(path) = path.to_str() {
18259 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
18260 }
18261 }
18262 }
18263
18264 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
18265 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
18266 buffer.read(cx).project_path(cx)
18267 } else {
18268 None
18269 }
18270 }
18271
18272 // Returns true if the editor handled a go-to-line request
18273 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
18274 maybe!({
18275 let breakpoint_store = self.breakpoint_store.as_ref()?;
18276
18277 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
18278 else {
18279 self.clear_row_highlights::<ActiveDebugLine>();
18280 return None;
18281 };
18282
18283 let position = active_stack_frame.position;
18284 let buffer_id = position.buffer_id?;
18285 let snapshot = self
18286 .project
18287 .as_ref()?
18288 .read(cx)
18289 .buffer_for_id(buffer_id, cx)?
18290 .read(cx)
18291 .snapshot();
18292
18293 let mut handled = false;
18294 for (id, ExcerptRange { context, .. }) in
18295 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
18296 {
18297 if context.start.cmp(&position, &snapshot).is_ge()
18298 || context.end.cmp(&position, &snapshot).is_lt()
18299 {
18300 continue;
18301 }
18302 let snapshot = self.buffer.read(cx).snapshot(cx);
18303 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
18304
18305 handled = true;
18306 self.clear_row_highlights::<ActiveDebugLine>();
18307
18308 self.go_to_line::<ActiveDebugLine>(
18309 multibuffer_anchor,
18310 Some(cx.theme().colors().editor_debugger_active_line_background),
18311 window,
18312 cx,
18313 );
18314
18315 cx.notify();
18316 }
18317
18318 handled.then_some(())
18319 })
18320 .is_some()
18321 }
18322
18323 pub fn copy_file_name_without_extension(
18324 &mut self,
18325 _: &CopyFileNameWithoutExtension,
18326 _: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 if let Some(file) = self.target_file(cx) {
18330 if let Some(file_stem) = file.path().file_stem() {
18331 if let Some(name) = file_stem.to_str() {
18332 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18333 }
18334 }
18335 }
18336 }
18337
18338 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18339 if let Some(file) = self.target_file(cx) {
18340 if let Some(file_name) = file.path().file_name() {
18341 if let Some(name) = file_name.to_str() {
18342 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18343 }
18344 }
18345 }
18346 }
18347
18348 pub fn toggle_git_blame(
18349 &mut self,
18350 _: &::git::Blame,
18351 window: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18355
18356 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18357 self.start_git_blame(true, window, cx);
18358 }
18359
18360 cx.notify();
18361 }
18362
18363 pub fn toggle_git_blame_inline(
18364 &mut self,
18365 _: &ToggleGitBlameInline,
18366 window: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) {
18369 self.toggle_git_blame_inline_internal(true, window, cx);
18370 cx.notify();
18371 }
18372
18373 pub fn open_git_blame_commit(
18374 &mut self,
18375 _: &OpenGitBlameCommit,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 self.open_git_blame_commit_internal(window, cx);
18380 }
18381
18382 fn open_git_blame_commit_internal(
18383 &mut self,
18384 window: &mut Window,
18385 cx: &mut Context<Self>,
18386 ) -> Option<()> {
18387 let blame = self.blame.as_ref()?;
18388 let snapshot = self.snapshot(window, cx);
18389 let cursor = self.selections.newest::<Point>(cx).head();
18390 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18391 let blame_entry = blame
18392 .update(cx, |blame, cx| {
18393 blame
18394 .blame_for_rows(
18395 &[RowInfo {
18396 buffer_id: Some(buffer.remote_id()),
18397 buffer_row: Some(point.row),
18398 ..Default::default()
18399 }],
18400 cx,
18401 )
18402 .next()
18403 })
18404 .flatten()?;
18405 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18406 let repo = blame.read(cx).repository(cx)?;
18407 let workspace = self.workspace()?.downgrade();
18408 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18409 None
18410 }
18411
18412 pub fn git_blame_inline_enabled(&self) -> bool {
18413 self.git_blame_inline_enabled
18414 }
18415
18416 pub fn toggle_selection_menu(
18417 &mut self,
18418 _: &ToggleSelectionMenu,
18419 _: &mut Window,
18420 cx: &mut Context<Self>,
18421 ) {
18422 self.show_selection_menu = self
18423 .show_selection_menu
18424 .map(|show_selections_menu| !show_selections_menu)
18425 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18426
18427 cx.notify();
18428 }
18429
18430 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18431 self.show_selection_menu
18432 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18433 }
18434
18435 fn start_git_blame(
18436 &mut self,
18437 user_triggered: bool,
18438 window: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) {
18441 if let Some(project) = self.project.as_ref() {
18442 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18443 return;
18444 };
18445
18446 if buffer.read(cx).file().is_none() {
18447 return;
18448 }
18449
18450 let focused = self.focus_handle(cx).contains_focused(window, cx);
18451
18452 let project = project.clone();
18453 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18454 self.blame_subscription =
18455 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18456 self.blame = Some(blame);
18457 }
18458 }
18459
18460 fn toggle_git_blame_inline_internal(
18461 &mut self,
18462 user_triggered: bool,
18463 window: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) {
18466 if self.git_blame_inline_enabled {
18467 self.git_blame_inline_enabled = false;
18468 self.show_git_blame_inline = false;
18469 self.show_git_blame_inline_delay_task.take();
18470 } else {
18471 self.git_blame_inline_enabled = true;
18472 self.start_git_blame_inline(user_triggered, window, cx);
18473 }
18474
18475 cx.notify();
18476 }
18477
18478 fn start_git_blame_inline(
18479 &mut self,
18480 user_triggered: bool,
18481 window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 self.start_git_blame(user_triggered, window, cx);
18485
18486 if ProjectSettings::get_global(cx)
18487 .git
18488 .inline_blame_delay()
18489 .is_some()
18490 {
18491 self.start_inline_blame_timer(window, cx);
18492 } else {
18493 self.show_git_blame_inline = true
18494 }
18495 }
18496
18497 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18498 self.blame.as_ref()
18499 }
18500
18501 pub fn show_git_blame_gutter(&self) -> bool {
18502 self.show_git_blame_gutter
18503 }
18504
18505 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18506 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18507 }
18508
18509 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18510 self.show_git_blame_inline
18511 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18512 && !self.newest_selection_head_on_empty_line(cx)
18513 && self.has_blame_entries(cx)
18514 }
18515
18516 fn has_blame_entries(&self, cx: &App) -> bool {
18517 self.blame()
18518 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18519 }
18520
18521 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18522 let cursor_anchor = self.selections.newest_anchor().head();
18523
18524 let snapshot = self.buffer.read(cx).snapshot(cx);
18525 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18526
18527 snapshot.line_len(buffer_row) == 0
18528 }
18529
18530 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18531 let buffer_and_selection = maybe!({
18532 let selection = self.selections.newest::<Point>(cx);
18533 let selection_range = selection.range();
18534
18535 let multi_buffer = self.buffer().read(cx);
18536 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18537 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18538
18539 let (buffer, range, _) = if selection.reversed {
18540 buffer_ranges.first()
18541 } else {
18542 buffer_ranges.last()
18543 }?;
18544
18545 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18546 ..text::ToPoint::to_point(&range.end, &buffer).row;
18547 Some((
18548 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18549 selection,
18550 ))
18551 });
18552
18553 let Some((buffer, selection)) = buffer_and_selection else {
18554 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18555 };
18556
18557 let Some(project) = self.project.as_ref() else {
18558 return Task::ready(Err(anyhow!("editor does not have project")));
18559 };
18560
18561 project.update(cx, |project, cx| {
18562 project.get_permalink_to_line(&buffer, selection, cx)
18563 })
18564 }
18565
18566 pub fn copy_permalink_to_line(
18567 &mut self,
18568 _: &CopyPermalinkToLine,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 let permalink_task = self.get_permalink_to_line(cx);
18573 let workspace = self.workspace();
18574
18575 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18576 Ok(permalink) => {
18577 cx.update(|_, cx| {
18578 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18579 })
18580 .ok();
18581 }
18582 Err(err) => {
18583 let message = format!("Failed to copy permalink: {err}");
18584
18585 anyhow::Result::<()>::Err(err).log_err();
18586
18587 if let Some(workspace) = workspace {
18588 workspace
18589 .update_in(cx, |workspace, _, cx| {
18590 struct CopyPermalinkToLine;
18591
18592 workspace.show_toast(
18593 Toast::new(
18594 NotificationId::unique::<CopyPermalinkToLine>(),
18595 message,
18596 ),
18597 cx,
18598 )
18599 })
18600 .ok();
18601 }
18602 }
18603 })
18604 .detach();
18605 }
18606
18607 pub fn copy_file_location(
18608 &mut self,
18609 _: &CopyFileLocation,
18610 _: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18614 if let Some(file) = self.target_file(cx) {
18615 if let Some(path) = file.path().to_str() {
18616 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18617 }
18618 }
18619 }
18620
18621 pub fn open_permalink_to_line(
18622 &mut self,
18623 _: &OpenPermalinkToLine,
18624 window: &mut Window,
18625 cx: &mut Context<Self>,
18626 ) {
18627 let permalink_task = self.get_permalink_to_line(cx);
18628 let workspace = self.workspace();
18629
18630 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18631 Ok(permalink) => {
18632 cx.update(|_, cx| {
18633 cx.open_url(permalink.as_ref());
18634 })
18635 .ok();
18636 }
18637 Err(err) => {
18638 let message = format!("Failed to open permalink: {err}");
18639
18640 anyhow::Result::<()>::Err(err).log_err();
18641
18642 if let Some(workspace) = workspace {
18643 workspace
18644 .update(cx, |workspace, cx| {
18645 struct OpenPermalinkToLine;
18646
18647 workspace.show_toast(
18648 Toast::new(
18649 NotificationId::unique::<OpenPermalinkToLine>(),
18650 message,
18651 ),
18652 cx,
18653 )
18654 })
18655 .ok();
18656 }
18657 }
18658 })
18659 .detach();
18660 }
18661
18662 pub fn insert_uuid_v4(
18663 &mut self,
18664 _: &InsertUuidV4,
18665 window: &mut Window,
18666 cx: &mut Context<Self>,
18667 ) {
18668 self.insert_uuid(UuidVersion::V4, window, cx);
18669 }
18670
18671 pub fn insert_uuid_v7(
18672 &mut self,
18673 _: &InsertUuidV7,
18674 window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 self.insert_uuid(UuidVersion::V7, window, cx);
18678 }
18679
18680 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18682 self.transact(window, cx, |this, window, cx| {
18683 let edits = this
18684 .selections
18685 .all::<Point>(cx)
18686 .into_iter()
18687 .map(|selection| {
18688 let uuid = match version {
18689 UuidVersion::V4 => uuid::Uuid::new_v4(),
18690 UuidVersion::V7 => uuid::Uuid::now_v7(),
18691 };
18692
18693 (selection.range(), uuid.to_string())
18694 });
18695 this.edit(edits, cx);
18696 this.refresh_inline_completion(true, false, window, cx);
18697 });
18698 }
18699
18700 pub fn open_selections_in_multibuffer(
18701 &mut self,
18702 _: &OpenSelectionsInMultibuffer,
18703 window: &mut Window,
18704 cx: &mut Context<Self>,
18705 ) {
18706 let multibuffer = self.buffer.read(cx);
18707
18708 let Some(buffer) = multibuffer.as_singleton() else {
18709 return;
18710 };
18711
18712 let Some(workspace) = self.workspace() else {
18713 return;
18714 };
18715
18716 let title = multibuffer.title(cx).to_string();
18717
18718 let locations = self
18719 .selections
18720 .all_anchors(cx)
18721 .into_iter()
18722 .map(|selection| Location {
18723 buffer: buffer.clone(),
18724 range: selection.start.text_anchor..selection.end.text_anchor,
18725 })
18726 .collect::<Vec<_>>();
18727
18728 cx.spawn_in(window, async move |_, cx| {
18729 workspace.update_in(cx, |workspace, window, cx| {
18730 Self::open_locations_in_multibuffer(
18731 workspace,
18732 locations,
18733 format!("Selections for '{title}'"),
18734 false,
18735 MultibufferSelectionMode::All,
18736 window,
18737 cx,
18738 );
18739 })
18740 })
18741 .detach();
18742 }
18743
18744 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18745 /// last highlight added will be used.
18746 ///
18747 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18748 pub fn highlight_rows<T: 'static>(
18749 &mut self,
18750 range: Range<Anchor>,
18751 color: Hsla,
18752 options: RowHighlightOptions,
18753 cx: &mut Context<Self>,
18754 ) {
18755 let snapshot = self.buffer().read(cx).snapshot(cx);
18756 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18757 let ix = row_highlights.binary_search_by(|highlight| {
18758 Ordering::Equal
18759 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18760 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18761 });
18762
18763 if let Err(mut ix) = ix {
18764 let index = post_inc(&mut self.highlight_order);
18765
18766 // If this range intersects with the preceding highlight, then merge it with
18767 // the preceding highlight. Otherwise insert a new highlight.
18768 let mut merged = false;
18769 if ix > 0 {
18770 let prev_highlight = &mut row_highlights[ix - 1];
18771 if prev_highlight
18772 .range
18773 .end
18774 .cmp(&range.start, &snapshot)
18775 .is_ge()
18776 {
18777 ix -= 1;
18778 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18779 prev_highlight.range.end = range.end;
18780 }
18781 merged = true;
18782 prev_highlight.index = index;
18783 prev_highlight.color = color;
18784 prev_highlight.options = options;
18785 }
18786 }
18787
18788 if !merged {
18789 row_highlights.insert(
18790 ix,
18791 RowHighlight {
18792 range: range.clone(),
18793 index,
18794 color,
18795 options,
18796 type_id: TypeId::of::<T>(),
18797 },
18798 );
18799 }
18800
18801 // If any of the following highlights intersect with this one, merge them.
18802 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18803 let highlight = &row_highlights[ix];
18804 if next_highlight
18805 .range
18806 .start
18807 .cmp(&highlight.range.end, &snapshot)
18808 .is_le()
18809 {
18810 if next_highlight
18811 .range
18812 .end
18813 .cmp(&highlight.range.end, &snapshot)
18814 .is_gt()
18815 {
18816 row_highlights[ix].range.end = next_highlight.range.end;
18817 }
18818 row_highlights.remove(ix + 1);
18819 } else {
18820 break;
18821 }
18822 }
18823 }
18824 }
18825
18826 /// Remove any highlighted row ranges of the given type that intersect the
18827 /// given ranges.
18828 pub fn remove_highlighted_rows<T: 'static>(
18829 &mut self,
18830 ranges_to_remove: Vec<Range<Anchor>>,
18831 cx: &mut Context<Self>,
18832 ) {
18833 let snapshot = self.buffer().read(cx).snapshot(cx);
18834 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18835 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18836 row_highlights.retain(|highlight| {
18837 while let Some(range_to_remove) = ranges_to_remove.peek() {
18838 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18839 Ordering::Less | Ordering::Equal => {
18840 ranges_to_remove.next();
18841 }
18842 Ordering::Greater => {
18843 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18844 Ordering::Less | Ordering::Equal => {
18845 return false;
18846 }
18847 Ordering::Greater => break,
18848 }
18849 }
18850 }
18851 }
18852
18853 true
18854 })
18855 }
18856
18857 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18858 pub fn clear_row_highlights<T: 'static>(&mut self) {
18859 self.highlighted_rows.remove(&TypeId::of::<T>());
18860 }
18861
18862 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18863 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18864 self.highlighted_rows
18865 .get(&TypeId::of::<T>())
18866 .map_or(&[] as &[_], |vec| vec.as_slice())
18867 .iter()
18868 .map(|highlight| (highlight.range.clone(), highlight.color))
18869 }
18870
18871 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18872 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18873 /// Allows to ignore certain kinds of highlights.
18874 pub fn highlighted_display_rows(
18875 &self,
18876 window: &mut Window,
18877 cx: &mut App,
18878 ) -> BTreeMap<DisplayRow, LineHighlight> {
18879 let snapshot = self.snapshot(window, cx);
18880 let mut used_highlight_orders = HashMap::default();
18881 self.highlighted_rows
18882 .iter()
18883 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18884 .fold(
18885 BTreeMap::<DisplayRow, LineHighlight>::new(),
18886 |mut unique_rows, highlight| {
18887 let start = highlight.range.start.to_display_point(&snapshot);
18888 let end = highlight.range.end.to_display_point(&snapshot);
18889 let start_row = start.row().0;
18890 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18891 && end.column() == 0
18892 {
18893 end.row().0.saturating_sub(1)
18894 } else {
18895 end.row().0
18896 };
18897 for row in start_row..=end_row {
18898 let used_index =
18899 used_highlight_orders.entry(row).or_insert(highlight.index);
18900 if highlight.index >= *used_index {
18901 *used_index = highlight.index;
18902 unique_rows.insert(
18903 DisplayRow(row),
18904 LineHighlight {
18905 include_gutter: highlight.options.include_gutter,
18906 border: None,
18907 background: highlight.color.into(),
18908 type_id: Some(highlight.type_id),
18909 },
18910 );
18911 }
18912 }
18913 unique_rows
18914 },
18915 )
18916 }
18917
18918 pub fn highlighted_display_row_for_autoscroll(
18919 &self,
18920 snapshot: &DisplaySnapshot,
18921 ) -> Option<DisplayRow> {
18922 self.highlighted_rows
18923 .values()
18924 .flat_map(|highlighted_rows| highlighted_rows.iter())
18925 .filter_map(|highlight| {
18926 if highlight.options.autoscroll {
18927 Some(highlight.range.start.to_display_point(snapshot).row())
18928 } else {
18929 None
18930 }
18931 })
18932 .min()
18933 }
18934
18935 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18936 self.highlight_background::<SearchWithinRange>(
18937 ranges,
18938 |colors| colors.colors().editor_document_highlight_read_background,
18939 cx,
18940 )
18941 }
18942
18943 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18944 self.breadcrumb_header = Some(new_header);
18945 }
18946
18947 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18948 self.clear_background_highlights::<SearchWithinRange>(cx);
18949 }
18950
18951 pub fn highlight_background<T: 'static>(
18952 &mut self,
18953 ranges: &[Range<Anchor>],
18954 color_fetcher: fn(&Theme) -> Hsla,
18955 cx: &mut Context<Self>,
18956 ) {
18957 self.background_highlights.insert(
18958 HighlightKey::Type(TypeId::of::<T>()),
18959 (color_fetcher, Arc::from(ranges)),
18960 );
18961 self.scrollbar_marker_state.dirty = true;
18962 cx.notify();
18963 }
18964
18965 pub fn highlight_background_key<T: 'static>(
18966 &mut self,
18967 key: usize,
18968 ranges: &[Range<Anchor>],
18969 color_fetcher: fn(&Theme) -> Hsla,
18970 cx: &mut Context<Self>,
18971 ) {
18972 self.background_highlights.insert(
18973 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18974 (color_fetcher, Arc::from(ranges)),
18975 );
18976 self.scrollbar_marker_state.dirty = true;
18977 cx.notify();
18978 }
18979
18980 pub fn clear_background_highlights<T: 'static>(
18981 &mut self,
18982 cx: &mut Context<Self>,
18983 ) -> Option<BackgroundHighlight> {
18984 let text_highlights = self
18985 .background_highlights
18986 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18987 if !text_highlights.1.is_empty() {
18988 self.scrollbar_marker_state.dirty = true;
18989 cx.notify();
18990 }
18991 Some(text_highlights)
18992 }
18993
18994 pub fn highlight_gutter<T: 'static>(
18995 &mut self,
18996 ranges: impl Into<Vec<Range<Anchor>>>,
18997 color_fetcher: fn(&App) -> Hsla,
18998 cx: &mut Context<Self>,
18999 ) {
19000 self.gutter_highlights
19001 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
19002 cx.notify();
19003 }
19004
19005 pub fn clear_gutter_highlights<T: 'static>(
19006 &mut self,
19007 cx: &mut Context<Self>,
19008 ) -> Option<GutterHighlight> {
19009 cx.notify();
19010 self.gutter_highlights.remove(&TypeId::of::<T>())
19011 }
19012
19013 pub fn insert_gutter_highlight<T: 'static>(
19014 &mut self,
19015 range: Range<Anchor>,
19016 color_fetcher: fn(&App) -> Hsla,
19017 cx: &mut Context<Self>,
19018 ) {
19019 let snapshot = self.buffer().read(cx).snapshot(cx);
19020 let mut highlights = self
19021 .gutter_highlights
19022 .remove(&TypeId::of::<T>())
19023 .map(|(_, highlights)| highlights)
19024 .unwrap_or_default();
19025 let ix = highlights.binary_search_by(|highlight| {
19026 Ordering::Equal
19027 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
19028 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
19029 });
19030 if let Err(ix) = ix {
19031 highlights.insert(ix, range);
19032 }
19033 self.gutter_highlights
19034 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
19035 }
19036
19037 pub fn remove_gutter_highlights<T: 'static>(
19038 &mut self,
19039 ranges_to_remove: Vec<Range<Anchor>>,
19040 cx: &mut Context<Self>,
19041 ) {
19042 let snapshot = self.buffer().read(cx).snapshot(cx);
19043 let Some((color_fetcher, mut gutter_highlights)) =
19044 self.gutter_highlights.remove(&TypeId::of::<T>())
19045 else {
19046 return;
19047 };
19048 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19049 gutter_highlights.retain(|highlight| {
19050 while let Some(range_to_remove) = ranges_to_remove.peek() {
19051 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
19052 Ordering::Less | Ordering::Equal => {
19053 ranges_to_remove.next();
19054 }
19055 Ordering::Greater => {
19056 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
19057 Ordering::Less | Ordering::Equal => {
19058 return false;
19059 }
19060 Ordering::Greater => break,
19061 }
19062 }
19063 }
19064 }
19065
19066 true
19067 });
19068 self.gutter_highlights
19069 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
19070 }
19071
19072 #[cfg(feature = "test-support")]
19073 pub fn all_text_highlights(
19074 &self,
19075 window: &mut Window,
19076 cx: &mut Context<Self>,
19077 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
19078 let snapshot = self.snapshot(window, cx);
19079 self.display_map.update(cx, |display_map, _| {
19080 display_map
19081 .all_text_highlights()
19082 .map(|highlight| {
19083 let (style, ranges) = highlight.as_ref();
19084 (
19085 *style,
19086 ranges
19087 .iter()
19088 .map(|range| range.clone().to_display_points(&snapshot))
19089 .collect(),
19090 )
19091 })
19092 .collect()
19093 })
19094 }
19095
19096 #[cfg(feature = "test-support")]
19097 pub fn all_text_background_highlights(
19098 &self,
19099 window: &mut Window,
19100 cx: &mut Context<Self>,
19101 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19102 let snapshot = self.snapshot(window, cx);
19103 let buffer = &snapshot.buffer_snapshot;
19104 let start = buffer.anchor_before(0);
19105 let end = buffer.anchor_after(buffer.len());
19106 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
19107 }
19108
19109 #[cfg(feature = "test-support")]
19110 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
19111 let snapshot = self.buffer().read(cx).snapshot(cx);
19112
19113 let highlights = self
19114 .background_highlights
19115 .get(&HighlightKey::Type(TypeId::of::<
19116 items::BufferSearchHighlights,
19117 >()));
19118
19119 if let Some((_color, ranges)) = highlights {
19120 ranges
19121 .iter()
19122 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
19123 .collect_vec()
19124 } else {
19125 vec![]
19126 }
19127 }
19128
19129 fn document_highlights_for_position<'a>(
19130 &'a self,
19131 position: Anchor,
19132 buffer: &'a MultiBufferSnapshot,
19133 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
19134 let read_highlights = self
19135 .background_highlights
19136 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
19137 .map(|h| &h.1);
19138 let write_highlights = self
19139 .background_highlights
19140 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
19141 .map(|h| &h.1);
19142 let left_position = position.bias_left(buffer);
19143 let right_position = position.bias_right(buffer);
19144 read_highlights
19145 .into_iter()
19146 .chain(write_highlights)
19147 .flat_map(move |ranges| {
19148 let start_ix = match ranges.binary_search_by(|probe| {
19149 let cmp = probe.end.cmp(&left_position, buffer);
19150 if cmp.is_ge() {
19151 Ordering::Greater
19152 } else {
19153 Ordering::Less
19154 }
19155 }) {
19156 Ok(i) | Err(i) => i,
19157 };
19158
19159 ranges[start_ix..]
19160 .iter()
19161 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
19162 })
19163 }
19164
19165 pub fn has_background_highlights<T: 'static>(&self) -> bool {
19166 self.background_highlights
19167 .get(&HighlightKey::Type(TypeId::of::<T>()))
19168 .map_or(false, |(_, highlights)| !highlights.is_empty())
19169 }
19170
19171 pub fn background_highlights_in_range(
19172 &self,
19173 search_range: Range<Anchor>,
19174 display_snapshot: &DisplaySnapshot,
19175 theme: &Theme,
19176 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19177 let mut results = Vec::new();
19178 for (color_fetcher, ranges) in self.background_highlights.values() {
19179 let color = color_fetcher(theme);
19180 let start_ix = match ranges.binary_search_by(|probe| {
19181 let cmp = probe
19182 .end
19183 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19184 if cmp.is_gt() {
19185 Ordering::Greater
19186 } else {
19187 Ordering::Less
19188 }
19189 }) {
19190 Ok(i) | Err(i) => i,
19191 };
19192 for range in &ranges[start_ix..] {
19193 if range
19194 .start
19195 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19196 .is_ge()
19197 {
19198 break;
19199 }
19200
19201 let start = range.start.to_display_point(display_snapshot);
19202 let end = range.end.to_display_point(display_snapshot);
19203 results.push((start..end, color))
19204 }
19205 }
19206 results
19207 }
19208
19209 pub fn background_highlight_row_ranges<T: 'static>(
19210 &self,
19211 search_range: Range<Anchor>,
19212 display_snapshot: &DisplaySnapshot,
19213 count: usize,
19214 ) -> Vec<RangeInclusive<DisplayPoint>> {
19215 let mut results = Vec::new();
19216 let Some((_, ranges)) = self
19217 .background_highlights
19218 .get(&HighlightKey::Type(TypeId::of::<T>()))
19219 else {
19220 return vec![];
19221 };
19222
19223 let start_ix = match ranges.binary_search_by(|probe| {
19224 let cmp = probe
19225 .end
19226 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19227 if cmp.is_gt() {
19228 Ordering::Greater
19229 } else {
19230 Ordering::Less
19231 }
19232 }) {
19233 Ok(i) | Err(i) => i,
19234 };
19235 let mut push_region = |start: Option<Point>, end: Option<Point>| {
19236 if let (Some(start_display), Some(end_display)) = (start, end) {
19237 results.push(
19238 start_display.to_display_point(display_snapshot)
19239 ..=end_display.to_display_point(display_snapshot),
19240 );
19241 }
19242 };
19243 let mut start_row: Option<Point> = None;
19244 let mut end_row: Option<Point> = None;
19245 if ranges.len() > count {
19246 return Vec::new();
19247 }
19248 for range in &ranges[start_ix..] {
19249 if range
19250 .start
19251 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19252 .is_ge()
19253 {
19254 break;
19255 }
19256 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
19257 if let Some(current_row) = &end_row {
19258 if end.row == current_row.row {
19259 continue;
19260 }
19261 }
19262 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
19263 if start_row.is_none() {
19264 assert_eq!(end_row, None);
19265 start_row = Some(start);
19266 end_row = Some(end);
19267 continue;
19268 }
19269 if let Some(current_end) = end_row.as_mut() {
19270 if start.row > current_end.row + 1 {
19271 push_region(start_row, end_row);
19272 start_row = Some(start);
19273 end_row = Some(end);
19274 } else {
19275 // Merge two hunks.
19276 *current_end = end;
19277 }
19278 } else {
19279 unreachable!();
19280 }
19281 }
19282 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
19283 push_region(start_row, end_row);
19284 results
19285 }
19286
19287 pub fn gutter_highlights_in_range(
19288 &self,
19289 search_range: Range<Anchor>,
19290 display_snapshot: &DisplaySnapshot,
19291 cx: &App,
19292 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
19293 let mut results = Vec::new();
19294 for (color_fetcher, ranges) in self.gutter_highlights.values() {
19295 let color = color_fetcher(cx);
19296 let start_ix = match ranges.binary_search_by(|probe| {
19297 let cmp = probe
19298 .end
19299 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
19300 if cmp.is_gt() {
19301 Ordering::Greater
19302 } else {
19303 Ordering::Less
19304 }
19305 }) {
19306 Ok(i) | Err(i) => i,
19307 };
19308 for range in &ranges[start_ix..] {
19309 if range
19310 .start
19311 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
19312 .is_ge()
19313 {
19314 break;
19315 }
19316
19317 let start = range.start.to_display_point(display_snapshot);
19318 let end = range.end.to_display_point(display_snapshot);
19319 results.push((start..end, color))
19320 }
19321 }
19322 results
19323 }
19324
19325 /// Get the text ranges corresponding to the redaction query
19326 pub fn redacted_ranges(
19327 &self,
19328 search_range: Range<Anchor>,
19329 display_snapshot: &DisplaySnapshot,
19330 cx: &App,
19331 ) -> Vec<Range<DisplayPoint>> {
19332 display_snapshot
19333 .buffer_snapshot
19334 .redacted_ranges(search_range, |file| {
19335 if let Some(file) = file {
19336 file.is_private()
19337 && EditorSettings::get(
19338 Some(SettingsLocation {
19339 worktree_id: file.worktree_id(cx),
19340 path: file.path().as_ref(),
19341 }),
19342 cx,
19343 )
19344 .redact_private_values
19345 } else {
19346 false
19347 }
19348 })
19349 .map(|range| {
19350 range.start.to_display_point(display_snapshot)
19351 ..range.end.to_display_point(display_snapshot)
19352 })
19353 .collect()
19354 }
19355
19356 pub fn highlight_text_key<T: 'static>(
19357 &mut self,
19358 key: usize,
19359 ranges: Vec<Range<Anchor>>,
19360 style: HighlightStyle,
19361 cx: &mut Context<Self>,
19362 ) {
19363 self.display_map.update(cx, |map, _| {
19364 map.highlight_text(
19365 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19366 ranges,
19367 style,
19368 );
19369 });
19370 cx.notify();
19371 }
19372
19373 pub fn highlight_text<T: 'static>(
19374 &mut self,
19375 ranges: Vec<Range<Anchor>>,
19376 style: HighlightStyle,
19377 cx: &mut Context<Self>,
19378 ) {
19379 self.display_map.update(cx, |map, _| {
19380 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19381 });
19382 cx.notify();
19383 }
19384
19385 pub(crate) fn highlight_inlays<T: 'static>(
19386 &mut self,
19387 highlights: Vec<InlayHighlight>,
19388 style: HighlightStyle,
19389 cx: &mut Context<Self>,
19390 ) {
19391 self.display_map.update(cx, |map, _| {
19392 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19393 });
19394 cx.notify();
19395 }
19396
19397 pub fn text_highlights<'a, T: 'static>(
19398 &'a self,
19399 cx: &'a App,
19400 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19401 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19402 }
19403
19404 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19405 let cleared = self
19406 .display_map
19407 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19408 if cleared {
19409 cx.notify();
19410 }
19411 }
19412
19413 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19414 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19415 && self.focus_handle.is_focused(window)
19416 }
19417
19418 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19419 self.show_cursor_when_unfocused = is_enabled;
19420 cx.notify();
19421 }
19422
19423 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19424 cx.notify();
19425 }
19426
19427 fn on_debug_session_event(
19428 &mut self,
19429 _session: Entity<Session>,
19430 event: &SessionEvent,
19431 cx: &mut Context<Self>,
19432 ) {
19433 match event {
19434 SessionEvent::InvalidateInlineValue => {
19435 self.refresh_inline_values(cx);
19436 }
19437 _ => {}
19438 }
19439 }
19440
19441 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19442 let Some(project) = self.project.clone() else {
19443 return;
19444 };
19445
19446 if !self.inline_value_cache.enabled {
19447 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19448 self.splice_inlays(&inlays, Vec::new(), cx);
19449 return;
19450 }
19451
19452 let current_execution_position = self
19453 .highlighted_rows
19454 .get(&TypeId::of::<ActiveDebugLine>())
19455 .and_then(|lines| lines.last().map(|line| line.range.end));
19456
19457 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19458 let inline_values = editor
19459 .update(cx, |editor, cx| {
19460 let Some(current_execution_position) = current_execution_position else {
19461 return Some(Task::ready(Ok(Vec::new())));
19462 };
19463
19464 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19465 let snapshot = buffer.snapshot(cx);
19466
19467 let excerpt = snapshot.excerpt_containing(
19468 current_execution_position..current_execution_position,
19469 )?;
19470
19471 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19472 })?;
19473
19474 let range =
19475 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19476
19477 project.inline_values(buffer, range, cx)
19478 })
19479 .ok()
19480 .flatten()?
19481 .await
19482 .context("refreshing debugger inlays")
19483 .log_err()?;
19484
19485 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19486
19487 for (buffer_id, inline_value) in inline_values
19488 .into_iter()
19489 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19490 {
19491 buffer_inline_values
19492 .entry(buffer_id)
19493 .or_default()
19494 .push(inline_value);
19495 }
19496
19497 editor
19498 .update(cx, |editor, cx| {
19499 let snapshot = editor.buffer.read(cx).snapshot(cx);
19500 let mut new_inlays = Vec::default();
19501
19502 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19503 let buffer_id = buffer_snapshot.remote_id();
19504 buffer_inline_values
19505 .get(&buffer_id)
19506 .into_iter()
19507 .flatten()
19508 .for_each(|hint| {
19509 let inlay = Inlay::debugger(
19510 post_inc(&mut editor.next_inlay_id),
19511 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19512 hint.text(),
19513 );
19514
19515 new_inlays.push(inlay);
19516 });
19517 }
19518
19519 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19520 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19521
19522 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19523 })
19524 .ok()?;
19525 Some(())
19526 });
19527 }
19528
19529 fn on_buffer_event(
19530 &mut self,
19531 multibuffer: &Entity<MultiBuffer>,
19532 event: &multi_buffer::Event,
19533 window: &mut Window,
19534 cx: &mut Context<Self>,
19535 ) {
19536 match event {
19537 multi_buffer::Event::Edited {
19538 singleton_buffer_edited,
19539 edited_buffer,
19540 } => {
19541 self.scrollbar_marker_state.dirty = true;
19542 self.active_indent_guides_state.dirty = true;
19543 self.refresh_active_diagnostics(cx);
19544 self.refresh_code_actions(window, cx);
19545 self.refresh_selected_text_highlights(true, window, cx);
19546 self.refresh_single_line_folds(window, cx);
19547 refresh_matching_bracket_highlights(self, window, cx);
19548 if self.has_active_inline_completion() {
19549 self.update_visible_inline_completion(window, cx);
19550 }
19551 if let Some(project) = self.project.as_ref() {
19552 if let Some(edited_buffer) = edited_buffer {
19553 project.update(cx, |project, cx| {
19554 self.registered_buffers
19555 .entry(edited_buffer.read(cx).remote_id())
19556 .or_insert_with(|| {
19557 project
19558 .register_buffer_with_language_servers(&edited_buffer, cx)
19559 });
19560 });
19561 }
19562 }
19563 cx.emit(EditorEvent::BufferEdited);
19564 cx.emit(SearchEvent::MatchesInvalidated);
19565
19566 if let Some(buffer) = edited_buffer {
19567 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
19568 }
19569
19570 if *singleton_buffer_edited {
19571 if let Some(buffer) = edited_buffer {
19572 if buffer.read(cx).file().is_none() {
19573 cx.emit(EditorEvent::TitleChanged);
19574 }
19575 }
19576 if let Some(project) = &self.project {
19577 #[allow(clippy::mutable_key_type)]
19578 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19579 multibuffer
19580 .all_buffers()
19581 .into_iter()
19582 .filter_map(|buffer| {
19583 buffer.update(cx, |buffer, cx| {
19584 let language = buffer.language()?;
19585 let should_discard = project.update(cx, |project, cx| {
19586 project.is_local()
19587 && !project.has_language_servers_for(buffer, cx)
19588 });
19589 should_discard.not().then_some(language.clone())
19590 })
19591 })
19592 .collect::<HashSet<_>>()
19593 });
19594 if !languages_affected.is_empty() {
19595 self.refresh_inlay_hints(
19596 InlayHintRefreshReason::BufferEdited(languages_affected),
19597 cx,
19598 );
19599 }
19600 }
19601 }
19602
19603 let Some(project) = &self.project else { return };
19604 let (telemetry, is_via_ssh) = {
19605 let project = project.read(cx);
19606 let telemetry = project.client().telemetry().clone();
19607 let is_via_ssh = project.is_via_ssh();
19608 (telemetry, is_via_ssh)
19609 };
19610 refresh_linked_ranges(self, window, cx);
19611 telemetry.log_edit_event("editor", is_via_ssh);
19612 }
19613 multi_buffer::Event::ExcerptsAdded {
19614 buffer,
19615 predecessor,
19616 excerpts,
19617 } => {
19618 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19619 let buffer_id = buffer.read(cx).remote_id();
19620 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19621 if let Some(project) = &self.project {
19622 update_uncommitted_diff_for_buffer(
19623 cx.entity(),
19624 project,
19625 [buffer.clone()],
19626 self.buffer.clone(),
19627 cx,
19628 )
19629 .detach();
19630 }
19631 }
19632 self.update_lsp_data(false, Some(buffer_id), window, cx);
19633 cx.emit(EditorEvent::ExcerptsAdded {
19634 buffer: buffer.clone(),
19635 predecessor: *predecessor,
19636 excerpts: excerpts.clone(),
19637 });
19638 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19639 }
19640 multi_buffer::Event::ExcerptsRemoved {
19641 ids,
19642 removed_buffer_ids,
19643 } => {
19644 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19645 let buffer = self.buffer.read(cx);
19646 self.registered_buffers
19647 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19648 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19649 cx.emit(EditorEvent::ExcerptsRemoved {
19650 ids: ids.clone(),
19651 removed_buffer_ids: removed_buffer_ids.clone(),
19652 });
19653 }
19654 multi_buffer::Event::ExcerptsEdited {
19655 excerpt_ids,
19656 buffer_ids,
19657 } => {
19658 self.display_map.update(cx, |map, cx| {
19659 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19660 });
19661 cx.emit(EditorEvent::ExcerptsEdited {
19662 ids: excerpt_ids.clone(),
19663 });
19664 }
19665 multi_buffer::Event::ExcerptsExpanded { ids } => {
19666 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19667 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19668 }
19669 multi_buffer::Event::Reparsed(buffer_id) => {
19670 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19671 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19672
19673 cx.emit(EditorEvent::Reparsed(*buffer_id));
19674 }
19675 multi_buffer::Event::DiffHunksToggled => {
19676 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19677 }
19678 multi_buffer::Event::LanguageChanged(buffer_id) => {
19679 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19680 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19681 cx.emit(EditorEvent::Reparsed(*buffer_id));
19682 cx.notify();
19683 }
19684 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19685 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19686 multi_buffer::Event::FileHandleChanged
19687 | multi_buffer::Event::Reloaded
19688 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19689 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19690 multi_buffer::Event::DiagnosticsUpdated => {
19691 self.update_diagnostics_state(window, cx);
19692 }
19693 _ => {}
19694 };
19695 }
19696
19697 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19698 if !self.diagnostics_enabled() {
19699 return;
19700 }
19701 self.refresh_active_diagnostics(cx);
19702 self.refresh_inline_diagnostics(true, window, cx);
19703 self.scrollbar_marker_state.dirty = true;
19704 cx.notify();
19705 }
19706
19707 pub fn start_temporary_diff_override(&mut self) {
19708 self.load_diff_task.take();
19709 self.temporary_diff_override = true;
19710 }
19711
19712 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19713 self.temporary_diff_override = false;
19714 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19715 self.buffer.update(cx, |buffer, cx| {
19716 buffer.set_all_diff_hunks_collapsed(cx);
19717 });
19718
19719 if let Some(project) = self.project.clone() {
19720 self.load_diff_task = Some(
19721 update_uncommitted_diff_for_buffer(
19722 cx.entity(),
19723 &project,
19724 self.buffer.read(cx).all_buffers(),
19725 self.buffer.clone(),
19726 cx,
19727 )
19728 .shared(),
19729 );
19730 }
19731 }
19732
19733 fn on_display_map_changed(
19734 &mut self,
19735 _: Entity<DisplayMap>,
19736 _: &mut Window,
19737 cx: &mut Context<Self>,
19738 ) {
19739 cx.notify();
19740 }
19741
19742 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19743 let new_severity = if self.diagnostics_enabled() {
19744 EditorSettings::get_global(cx)
19745 .diagnostics_max_severity
19746 .unwrap_or(DiagnosticSeverity::Hint)
19747 } else {
19748 DiagnosticSeverity::Off
19749 };
19750 self.set_max_diagnostics_severity(new_severity, cx);
19751 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19752 self.update_edit_prediction_settings(cx);
19753 self.refresh_inline_completion(true, false, window, cx);
19754 self.refresh_inlay_hints(
19755 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19756 self.selections.newest_anchor().head(),
19757 &self.buffer.read(cx).snapshot(cx),
19758 cx,
19759 )),
19760 cx,
19761 );
19762
19763 let old_cursor_shape = self.cursor_shape;
19764
19765 {
19766 let editor_settings = EditorSettings::get_global(cx);
19767 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19768 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19769 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19770 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19771 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19772 }
19773
19774 if old_cursor_shape != self.cursor_shape {
19775 cx.emit(EditorEvent::CursorShapeChanged);
19776 }
19777
19778 let project_settings = ProjectSettings::get_global(cx);
19779 self.serialize_dirty_buffers =
19780 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19781
19782 if self.mode.is_full() {
19783 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19784 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19785 if self.show_inline_diagnostics != show_inline_diagnostics {
19786 self.show_inline_diagnostics = show_inline_diagnostics;
19787 self.refresh_inline_diagnostics(false, window, cx);
19788 }
19789
19790 if self.git_blame_inline_enabled != inline_blame_enabled {
19791 self.toggle_git_blame_inline_internal(false, window, cx);
19792 }
19793
19794 let minimap_settings = EditorSettings::get_global(cx).minimap;
19795 if self.minimap_visibility != MinimapVisibility::Disabled {
19796 if self.minimap_visibility.settings_visibility()
19797 != minimap_settings.minimap_enabled()
19798 {
19799 self.set_minimap_visibility(
19800 MinimapVisibility::for_mode(self.mode(), cx),
19801 window,
19802 cx,
19803 );
19804 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19805 minimap_entity.update(cx, |minimap_editor, cx| {
19806 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19807 })
19808 }
19809 }
19810 }
19811
19812 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19813 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19814 }) {
19815 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19816 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19817 }
19818 self.refresh_colors(false, None, window, cx);
19819 }
19820
19821 cx.notify();
19822 }
19823
19824 pub fn set_searchable(&mut self, searchable: bool) {
19825 self.searchable = searchable;
19826 }
19827
19828 pub fn searchable(&self) -> bool {
19829 self.searchable
19830 }
19831
19832 fn open_proposed_changes_editor(
19833 &mut self,
19834 _: &OpenProposedChangesEditor,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 let Some(workspace) = self.workspace() else {
19839 cx.propagate();
19840 return;
19841 };
19842
19843 let selections = self.selections.all::<usize>(cx);
19844 let multi_buffer = self.buffer.read(cx);
19845 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19846 let mut new_selections_by_buffer = HashMap::default();
19847 for selection in selections {
19848 for (buffer, range, _) in
19849 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19850 {
19851 let mut range = range.to_point(buffer);
19852 range.start.column = 0;
19853 range.end.column = buffer.line_len(range.end.row);
19854 new_selections_by_buffer
19855 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19856 .or_insert(Vec::new())
19857 .push(range)
19858 }
19859 }
19860
19861 let proposed_changes_buffers = new_selections_by_buffer
19862 .into_iter()
19863 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19864 .collect::<Vec<_>>();
19865 let proposed_changes_editor = cx.new(|cx| {
19866 ProposedChangesEditor::new(
19867 "Proposed changes",
19868 proposed_changes_buffers,
19869 self.project.clone(),
19870 window,
19871 cx,
19872 )
19873 });
19874
19875 window.defer(cx, move |window, cx| {
19876 workspace.update(cx, |workspace, cx| {
19877 workspace.active_pane().update(cx, |pane, cx| {
19878 pane.add_item(
19879 Box::new(proposed_changes_editor),
19880 true,
19881 true,
19882 None,
19883 window,
19884 cx,
19885 );
19886 });
19887 });
19888 });
19889 }
19890
19891 pub fn open_excerpts_in_split(
19892 &mut self,
19893 _: &OpenExcerptsSplit,
19894 window: &mut Window,
19895 cx: &mut Context<Self>,
19896 ) {
19897 self.open_excerpts_common(None, true, window, cx)
19898 }
19899
19900 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19901 self.open_excerpts_common(None, false, window, cx)
19902 }
19903
19904 fn open_excerpts_common(
19905 &mut self,
19906 jump_data: Option<JumpData>,
19907 split: bool,
19908 window: &mut Window,
19909 cx: &mut Context<Self>,
19910 ) {
19911 let Some(workspace) = self.workspace() else {
19912 cx.propagate();
19913 return;
19914 };
19915
19916 if self.buffer.read(cx).is_singleton() {
19917 cx.propagate();
19918 return;
19919 }
19920
19921 let mut new_selections_by_buffer = HashMap::default();
19922 match &jump_data {
19923 Some(JumpData::MultiBufferPoint {
19924 excerpt_id,
19925 position,
19926 anchor,
19927 line_offset_from_top,
19928 }) => {
19929 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19930 if let Some(buffer) = multi_buffer_snapshot
19931 .buffer_id_for_excerpt(*excerpt_id)
19932 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19933 {
19934 let buffer_snapshot = buffer.read(cx).snapshot();
19935 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19936 language::ToPoint::to_point(anchor, &buffer_snapshot)
19937 } else {
19938 buffer_snapshot.clip_point(*position, Bias::Left)
19939 };
19940 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19941 new_selections_by_buffer.insert(
19942 buffer,
19943 (
19944 vec![jump_to_offset..jump_to_offset],
19945 Some(*line_offset_from_top),
19946 ),
19947 );
19948 }
19949 }
19950 Some(JumpData::MultiBufferRow {
19951 row,
19952 line_offset_from_top,
19953 }) => {
19954 let point = MultiBufferPoint::new(row.0, 0);
19955 if let Some((buffer, buffer_point, _)) =
19956 self.buffer.read(cx).point_to_buffer_point(point, cx)
19957 {
19958 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19959 new_selections_by_buffer
19960 .entry(buffer)
19961 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19962 .0
19963 .push(buffer_offset..buffer_offset)
19964 }
19965 }
19966 None => {
19967 let selections = self.selections.all::<usize>(cx);
19968 let multi_buffer = self.buffer.read(cx);
19969 for selection in selections {
19970 for (snapshot, range, _, anchor) in multi_buffer
19971 .snapshot(cx)
19972 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19973 {
19974 if let Some(anchor) = anchor {
19975 // selection is in a deleted hunk
19976 let Some(buffer_id) = anchor.buffer_id else {
19977 continue;
19978 };
19979 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19980 continue;
19981 };
19982 let offset = text::ToOffset::to_offset(
19983 &anchor.text_anchor,
19984 &buffer_handle.read(cx).snapshot(),
19985 );
19986 let range = offset..offset;
19987 new_selections_by_buffer
19988 .entry(buffer_handle)
19989 .or_insert((Vec::new(), None))
19990 .0
19991 .push(range)
19992 } else {
19993 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19994 else {
19995 continue;
19996 };
19997 new_selections_by_buffer
19998 .entry(buffer_handle)
19999 .or_insert((Vec::new(), None))
20000 .0
20001 .push(range)
20002 }
20003 }
20004 }
20005 }
20006 }
20007
20008 new_selections_by_buffer
20009 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
20010
20011 if new_selections_by_buffer.is_empty() {
20012 return;
20013 }
20014
20015 // We defer the pane interaction because we ourselves are a workspace item
20016 // and activating a new item causes the pane to call a method on us reentrantly,
20017 // which panics if we're on the stack.
20018 window.defer(cx, move |window, cx| {
20019 workspace.update(cx, |workspace, cx| {
20020 let pane = if split {
20021 workspace.adjacent_pane(window, cx)
20022 } else {
20023 workspace.active_pane().clone()
20024 };
20025
20026 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
20027 let editor = buffer
20028 .read(cx)
20029 .file()
20030 .is_none()
20031 .then(|| {
20032 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
20033 // so `workspace.open_project_item` will never find them, always opening a new editor.
20034 // Instead, we try to activate the existing editor in the pane first.
20035 let (editor, pane_item_index) =
20036 pane.read(cx).items().enumerate().find_map(|(i, item)| {
20037 let editor = item.downcast::<Editor>()?;
20038 let singleton_buffer =
20039 editor.read(cx).buffer().read(cx).as_singleton()?;
20040 if singleton_buffer == buffer {
20041 Some((editor, i))
20042 } else {
20043 None
20044 }
20045 })?;
20046 pane.update(cx, |pane, cx| {
20047 pane.activate_item(pane_item_index, true, true, window, cx)
20048 });
20049 Some(editor)
20050 })
20051 .flatten()
20052 .unwrap_or_else(|| {
20053 workspace.open_project_item::<Self>(
20054 pane.clone(),
20055 buffer,
20056 true,
20057 true,
20058 window,
20059 cx,
20060 )
20061 });
20062
20063 editor.update(cx, |editor, cx| {
20064 let autoscroll = match scroll_offset {
20065 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
20066 None => Autoscroll::newest(),
20067 };
20068 let nav_history = editor.nav_history.take();
20069 editor.change_selections(
20070 SelectionEffects::scroll(autoscroll),
20071 window,
20072 cx,
20073 |s| {
20074 s.select_ranges(ranges);
20075 },
20076 );
20077 editor.nav_history = nav_history;
20078 });
20079 }
20080 })
20081 });
20082 }
20083
20084 // For now, don't allow opening excerpts in buffers that aren't backed by
20085 // regular project files.
20086 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
20087 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
20088 }
20089
20090 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
20091 let snapshot = self.buffer.read(cx).read(cx);
20092 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
20093 Some(
20094 ranges
20095 .iter()
20096 .map(move |range| {
20097 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
20098 })
20099 .collect(),
20100 )
20101 }
20102
20103 fn selection_replacement_ranges(
20104 &self,
20105 range: Range<OffsetUtf16>,
20106 cx: &mut App,
20107 ) -> Vec<Range<OffsetUtf16>> {
20108 let selections = self.selections.all::<OffsetUtf16>(cx);
20109 let newest_selection = selections
20110 .iter()
20111 .max_by_key(|selection| selection.id)
20112 .unwrap();
20113 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
20114 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
20115 let snapshot = self.buffer.read(cx).read(cx);
20116 selections
20117 .into_iter()
20118 .map(|mut selection| {
20119 selection.start.0 =
20120 (selection.start.0 as isize).saturating_add(start_delta) as usize;
20121 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
20122 snapshot.clip_offset_utf16(selection.start, Bias::Left)
20123 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
20124 })
20125 .collect()
20126 }
20127
20128 fn report_editor_event(
20129 &self,
20130 event_type: &'static str,
20131 file_extension: Option<String>,
20132 cx: &App,
20133 ) {
20134 if cfg!(any(test, feature = "test-support")) {
20135 return;
20136 }
20137
20138 let Some(project) = &self.project else { return };
20139
20140 // If None, we are in a file without an extension
20141 let file = self
20142 .buffer
20143 .read(cx)
20144 .as_singleton()
20145 .and_then(|b| b.read(cx).file());
20146 let file_extension = file_extension.or(file
20147 .as_ref()
20148 .and_then(|file| Path::new(file.file_name(cx)).extension())
20149 .and_then(|e| e.to_str())
20150 .map(|a| a.to_string()));
20151
20152 let vim_mode = vim_enabled(cx);
20153
20154 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
20155 let copilot_enabled = edit_predictions_provider
20156 == language::language_settings::EditPredictionProvider::Copilot;
20157 let copilot_enabled_for_language = self
20158 .buffer
20159 .read(cx)
20160 .language_settings(cx)
20161 .show_edit_predictions;
20162
20163 let project = project.read(cx);
20164 telemetry::event!(
20165 event_type,
20166 file_extension,
20167 vim_mode,
20168 copilot_enabled,
20169 copilot_enabled_for_language,
20170 edit_predictions_provider,
20171 is_via_ssh = project.is_via_ssh(),
20172 );
20173 }
20174
20175 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
20176 /// with each line being an array of {text, highlight} objects.
20177 fn copy_highlight_json(
20178 &mut self,
20179 _: &CopyHighlightJson,
20180 window: &mut Window,
20181 cx: &mut Context<Self>,
20182 ) {
20183 #[derive(Serialize)]
20184 struct Chunk<'a> {
20185 text: String,
20186 highlight: Option<&'a str>,
20187 }
20188
20189 let snapshot = self.buffer.read(cx).snapshot(cx);
20190 let range = self
20191 .selected_text_range(false, window, cx)
20192 .and_then(|selection| {
20193 if selection.range.is_empty() {
20194 None
20195 } else {
20196 Some(selection.range)
20197 }
20198 })
20199 .unwrap_or_else(|| 0..snapshot.len());
20200
20201 let chunks = snapshot.chunks(range, true);
20202 let mut lines = Vec::new();
20203 let mut line: VecDeque<Chunk> = VecDeque::new();
20204
20205 let Some(style) = self.style.as_ref() else {
20206 return;
20207 };
20208
20209 for chunk in chunks {
20210 let highlight = chunk
20211 .syntax_highlight_id
20212 .and_then(|id| id.name(&style.syntax));
20213 let mut chunk_lines = chunk.text.split('\n').peekable();
20214 while let Some(text) = chunk_lines.next() {
20215 let mut merged_with_last_token = false;
20216 if let Some(last_token) = line.back_mut() {
20217 if last_token.highlight == highlight {
20218 last_token.text.push_str(text);
20219 merged_with_last_token = true;
20220 }
20221 }
20222
20223 if !merged_with_last_token {
20224 line.push_back(Chunk {
20225 text: text.into(),
20226 highlight,
20227 });
20228 }
20229
20230 if chunk_lines.peek().is_some() {
20231 if line.len() > 1 && line.front().unwrap().text.is_empty() {
20232 line.pop_front();
20233 }
20234 if line.len() > 1 && line.back().unwrap().text.is_empty() {
20235 line.pop_back();
20236 }
20237
20238 lines.push(mem::take(&mut line));
20239 }
20240 }
20241 }
20242
20243 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
20244 return;
20245 };
20246 cx.write_to_clipboard(ClipboardItem::new_string(lines));
20247 }
20248
20249 pub fn open_context_menu(
20250 &mut self,
20251 _: &OpenContextMenu,
20252 window: &mut Window,
20253 cx: &mut Context<Self>,
20254 ) {
20255 self.request_autoscroll(Autoscroll::newest(), cx);
20256 let position = self.selections.newest_display(cx).start;
20257 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
20258 }
20259
20260 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
20261 &self.inlay_hint_cache
20262 }
20263
20264 pub fn replay_insert_event(
20265 &mut self,
20266 text: &str,
20267 relative_utf16_range: Option<Range<isize>>,
20268 window: &mut Window,
20269 cx: &mut Context<Self>,
20270 ) {
20271 if !self.input_enabled {
20272 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20273 return;
20274 }
20275 if let Some(relative_utf16_range) = relative_utf16_range {
20276 let selections = self.selections.all::<OffsetUtf16>(cx);
20277 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20278 let new_ranges = selections.into_iter().map(|range| {
20279 let start = OffsetUtf16(
20280 range
20281 .head()
20282 .0
20283 .saturating_add_signed(relative_utf16_range.start),
20284 );
20285 let end = OffsetUtf16(
20286 range
20287 .head()
20288 .0
20289 .saturating_add_signed(relative_utf16_range.end),
20290 );
20291 start..end
20292 });
20293 s.select_ranges(new_ranges);
20294 });
20295 }
20296
20297 self.handle_input(text, window, cx);
20298 }
20299
20300 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
20301 let Some(provider) = self.semantics_provider.as_ref() else {
20302 return false;
20303 };
20304
20305 let mut supports = false;
20306 self.buffer().update(cx, |this, cx| {
20307 this.for_each_buffer(|buffer| {
20308 supports |= provider.supports_inlay_hints(buffer, cx);
20309 });
20310 });
20311
20312 supports
20313 }
20314
20315 pub fn is_focused(&self, window: &Window) -> bool {
20316 self.focus_handle.is_focused(window)
20317 }
20318
20319 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20320 cx.emit(EditorEvent::Focused);
20321
20322 if let Some(descendant) = self
20323 .last_focused_descendant
20324 .take()
20325 .and_then(|descendant| descendant.upgrade())
20326 {
20327 window.focus(&descendant);
20328 } else {
20329 if let Some(blame) = self.blame.as_ref() {
20330 blame.update(cx, GitBlame::focus)
20331 }
20332
20333 self.blink_manager.update(cx, BlinkManager::enable);
20334 self.show_cursor_names(window, cx);
20335 self.buffer.update(cx, |buffer, cx| {
20336 buffer.finalize_last_transaction(cx);
20337 if self.leader_id.is_none() {
20338 buffer.set_active_selections(
20339 &self.selections.disjoint_anchors(),
20340 self.selections.line_mode,
20341 self.cursor_shape,
20342 cx,
20343 );
20344 }
20345 });
20346 }
20347 }
20348
20349 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20350 cx.emit(EditorEvent::FocusedIn)
20351 }
20352
20353 fn handle_focus_out(
20354 &mut self,
20355 event: FocusOutEvent,
20356 _window: &mut Window,
20357 cx: &mut Context<Self>,
20358 ) {
20359 if event.blurred != self.focus_handle {
20360 self.last_focused_descendant = Some(event.blurred);
20361 }
20362 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20363 }
20364
20365 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20366 self.blink_manager.update(cx, BlinkManager::disable);
20367 self.buffer
20368 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20369
20370 if let Some(blame) = self.blame.as_ref() {
20371 blame.update(cx, GitBlame::blur)
20372 }
20373 if !self.hover_state.focused(window, cx) {
20374 hide_hover(self, cx);
20375 }
20376 if !self
20377 .context_menu
20378 .borrow()
20379 .as_ref()
20380 .is_some_and(|context_menu| context_menu.focused(window, cx))
20381 {
20382 self.hide_context_menu(window, cx);
20383 }
20384 self.discard_inline_completion(false, cx);
20385 cx.emit(EditorEvent::Blurred);
20386 cx.notify();
20387 }
20388
20389 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20390 let mut pending: String = window
20391 .pending_input_keystrokes()
20392 .into_iter()
20393 .flatten()
20394 .filter_map(|keystroke| {
20395 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20396 keystroke.key_char.clone()
20397 } else {
20398 None
20399 }
20400 })
20401 .collect();
20402
20403 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20404 pending = "".to_string();
20405 }
20406
20407 let existing_pending = self
20408 .text_highlights::<PendingInput>(cx)
20409 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20410 if existing_pending.is_none() && pending.is_empty() {
20411 return;
20412 }
20413 let transaction =
20414 self.transact(window, cx, |this, window, cx| {
20415 let selections = this.selections.all::<usize>(cx);
20416 let edits = selections
20417 .iter()
20418 .map(|selection| (selection.end..selection.end, pending.clone()));
20419 this.edit(edits, cx);
20420 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20421 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20422 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20423 }));
20424 });
20425 if let Some(existing_ranges) = existing_pending {
20426 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20427 this.edit(edits, cx);
20428 }
20429 });
20430
20431 let snapshot = self.snapshot(window, cx);
20432 let ranges = self
20433 .selections
20434 .all::<usize>(cx)
20435 .into_iter()
20436 .map(|selection| {
20437 snapshot.buffer_snapshot.anchor_after(selection.end)
20438 ..snapshot
20439 .buffer_snapshot
20440 .anchor_before(selection.end + pending.len())
20441 })
20442 .collect();
20443
20444 if pending.is_empty() {
20445 self.clear_highlights::<PendingInput>(cx);
20446 } else {
20447 self.highlight_text::<PendingInput>(
20448 ranges,
20449 HighlightStyle {
20450 underline: Some(UnderlineStyle {
20451 thickness: px(1.),
20452 color: None,
20453 wavy: false,
20454 }),
20455 ..Default::default()
20456 },
20457 cx,
20458 );
20459 }
20460
20461 self.ime_transaction = self.ime_transaction.or(transaction);
20462 if let Some(transaction) = self.ime_transaction {
20463 self.buffer.update(cx, |buffer, cx| {
20464 buffer.group_until_transaction(transaction, cx);
20465 });
20466 }
20467
20468 if self.text_highlights::<PendingInput>(cx).is_none() {
20469 self.ime_transaction.take();
20470 }
20471 }
20472
20473 pub fn register_action_renderer(
20474 &mut self,
20475 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20476 ) -> Subscription {
20477 let id = self.next_editor_action_id.post_inc();
20478 self.editor_actions
20479 .borrow_mut()
20480 .insert(id, Box::new(listener));
20481
20482 let editor_actions = self.editor_actions.clone();
20483 Subscription::new(move || {
20484 editor_actions.borrow_mut().remove(&id);
20485 })
20486 }
20487
20488 pub fn register_action<A: Action>(
20489 &mut self,
20490 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20491 ) -> Subscription {
20492 let id = self.next_editor_action_id.post_inc();
20493 let listener = Arc::new(listener);
20494 self.editor_actions.borrow_mut().insert(
20495 id,
20496 Box::new(move |_, window, _| {
20497 let listener = listener.clone();
20498 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20499 let action = action.downcast_ref().unwrap();
20500 if phase == DispatchPhase::Bubble {
20501 listener(action, window, cx)
20502 }
20503 })
20504 }),
20505 );
20506
20507 let editor_actions = self.editor_actions.clone();
20508 Subscription::new(move || {
20509 editor_actions.borrow_mut().remove(&id);
20510 })
20511 }
20512
20513 pub fn file_header_size(&self) -> u32 {
20514 FILE_HEADER_HEIGHT
20515 }
20516
20517 pub fn restore(
20518 &mut self,
20519 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20520 window: &mut Window,
20521 cx: &mut Context<Self>,
20522 ) {
20523 let workspace = self.workspace();
20524 let project = self.project.as_ref();
20525 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20526 let mut tasks = Vec::new();
20527 for (buffer_id, changes) in revert_changes {
20528 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20529 buffer.update(cx, |buffer, cx| {
20530 buffer.edit(
20531 changes
20532 .into_iter()
20533 .map(|(range, text)| (range, text.to_string())),
20534 None,
20535 cx,
20536 );
20537 });
20538
20539 if let Some(project) =
20540 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20541 {
20542 project.update(cx, |project, cx| {
20543 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20544 })
20545 }
20546 }
20547 }
20548 tasks
20549 });
20550 cx.spawn_in(window, async move |_, cx| {
20551 for (buffer, task) in save_tasks {
20552 let result = task.await;
20553 if result.is_err() {
20554 let Some(path) = buffer
20555 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20556 .ok()
20557 else {
20558 continue;
20559 };
20560 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20561 let Some(task) = cx
20562 .update_window_entity(&workspace, |workspace, window, cx| {
20563 workspace
20564 .open_path_preview(path, None, false, false, false, window, cx)
20565 })
20566 .ok()
20567 else {
20568 continue;
20569 };
20570 task.await.log_err();
20571 }
20572 }
20573 }
20574 })
20575 .detach();
20576 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20577 selections.refresh()
20578 });
20579 }
20580
20581 pub fn to_pixel_point(
20582 &self,
20583 source: multi_buffer::Anchor,
20584 editor_snapshot: &EditorSnapshot,
20585 window: &mut Window,
20586 ) -> Option<gpui::Point<Pixels>> {
20587 let source_point = source.to_display_point(editor_snapshot);
20588 self.display_to_pixel_point(source_point, editor_snapshot, window)
20589 }
20590
20591 pub fn display_to_pixel_point(
20592 &self,
20593 source: DisplayPoint,
20594 editor_snapshot: &EditorSnapshot,
20595 window: &mut Window,
20596 ) -> Option<gpui::Point<Pixels>> {
20597 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20598 let text_layout_details = self.text_layout_details(window);
20599 let scroll_top = text_layout_details
20600 .scroll_anchor
20601 .scroll_position(editor_snapshot)
20602 .y;
20603
20604 if source.row().as_f32() < scroll_top.floor() {
20605 return None;
20606 }
20607 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20608 let source_y = line_height * (source.row().as_f32() - scroll_top);
20609 Some(gpui::Point::new(source_x, source_y))
20610 }
20611
20612 pub fn has_visible_completions_menu(&self) -> bool {
20613 !self.edit_prediction_preview_is_active()
20614 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20615 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20616 })
20617 }
20618
20619 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20620 if self.mode.is_minimap() {
20621 return;
20622 }
20623 self.addons
20624 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20625 }
20626
20627 pub fn unregister_addon<T: Addon>(&mut self) {
20628 self.addons.remove(&std::any::TypeId::of::<T>());
20629 }
20630
20631 pub fn addon<T: Addon>(&self) -> Option<&T> {
20632 let type_id = std::any::TypeId::of::<T>();
20633 self.addons
20634 .get(&type_id)
20635 .and_then(|item| item.to_any().downcast_ref::<T>())
20636 }
20637
20638 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20639 let type_id = std::any::TypeId::of::<T>();
20640 self.addons
20641 .get_mut(&type_id)
20642 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20643 }
20644
20645 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
20646 let text_layout_details = self.text_layout_details(window);
20647 let style = &text_layout_details.editor_style;
20648 let font_id = window.text_system().resolve_font(&style.text.font());
20649 let font_size = style.text.font_size.to_pixels(window.rem_size());
20650 let line_height = style.text.line_height_in_pixels(window.rem_size());
20651 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20652 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
20653
20654 CharacterDimensions {
20655 em_width,
20656 em_advance,
20657 line_height,
20658 }
20659 }
20660
20661 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20662 self.load_diff_task.clone()
20663 }
20664
20665 fn read_metadata_from_db(
20666 &mut self,
20667 item_id: u64,
20668 workspace_id: WorkspaceId,
20669 window: &mut Window,
20670 cx: &mut Context<Editor>,
20671 ) {
20672 if self.is_singleton(cx)
20673 && !self.mode.is_minimap()
20674 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20675 {
20676 let buffer_snapshot = OnceCell::new();
20677
20678 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20679 if !folds.is_empty() {
20680 let snapshot =
20681 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20682 self.fold_ranges(
20683 folds
20684 .into_iter()
20685 .map(|(start, end)| {
20686 snapshot.clip_offset(start, Bias::Left)
20687 ..snapshot.clip_offset(end, Bias::Right)
20688 })
20689 .collect(),
20690 false,
20691 window,
20692 cx,
20693 );
20694 }
20695 }
20696
20697 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20698 if !selections.is_empty() {
20699 let snapshot =
20700 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20701 // skip adding the initial selection to selection history
20702 self.selection_history.mode = SelectionHistoryMode::Skipping;
20703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20704 s.select_ranges(selections.into_iter().map(|(start, end)| {
20705 snapshot.clip_offset(start, Bias::Left)
20706 ..snapshot.clip_offset(end, Bias::Right)
20707 }));
20708 });
20709 self.selection_history.mode = SelectionHistoryMode::Normal;
20710 }
20711 };
20712 }
20713
20714 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20715 }
20716
20717 fn update_lsp_data(
20718 &mut self,
20719 ignore_cache: bool,
20720 for_buffer: Option<BufferId>,
20721 window: &mut Window,
20722 cx: &mut Context<'_, Self>,
20723 ) {
20724 self.pull_diagnostics(for_buffer, window, cx);
20725 self.refresh_colors(ignore_cache, for_buffer, window, cx);
20726 }
20727}
20728
20729fn vim_enabled(cx: &App) -> bool {
20730 cx.global::<SettingsStore>()
20731 .raw_user_settings()
20732 .get("vim_mode")
20733 == Some(&serde_json::Value::Bool(true))
20734}
20735
20736fn process_completion_for_edit(
20737 completion: &Completion,
20738 intent: CompletionIntent,
20739 buffer: &Entity<Buffer>,
20740 cursor_position: &text::Anchor,
20741 cx: &mut Context<Editor>,
20742) -> CompletionEdit {
20743 let buffer = buffer.read(cx);
20744 let buffer_snapshot = buffer.snapshot();
20745 let (snippet, new_text) = if completion.is_snippet() {
20746 // Workaround for typescript language server issues so that methods don't expand within
20747 // strings and functions with type expressions. The previous point is used because the query
20748 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20749 let mut snippet_source = completion.new_text.clone();
20750 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20751 previous_point.column = previous_point.column.saturating_sub(1);
20752 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20753 if scope.prefers_label_for_snippet_in_completion() {
20754 if let Some(label) = completion.label() {
20755 if matches!(
20756 completion.kind(),
20757 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20758 ) {
20759 snippet_source = label;
20760 }
20761 }
20762 }
20763 }
20764 match Snippet::parse(&snippet_source).log_err() {
20765 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20766 None => (None, completion.new_text.clone()),
20767 }
20768 } else {
20769 (None, completion.new_text.clone())
20770 };
20771
20772 let mut range_to_replace = {
20773 let replace_range = &completion.replace_range;
20774 if let CompletionSource::Lsp {
20775 insert_range: Some(insert_range),
20776 ..
20777 } = &completion.source
20778 {
20779 debug_assert_eq!(
20780 insert_range.start, replace_range.start,
20781 "insert_range and replace_range should start at the same position"
20782 );
20783 debug_assert!(
20784 insert_range
20785 .start
20786 .cmp(&cursor_position, &buffer_snapshot)
20787 .is_le(),
20788 "insert_range should start before or at cursor position"
20789 );
20790 debug_assert!(
20791 replace_range
20792 .start
20793 .cmp(&cursor_position, &buffer_snapshot)
20794 .is_le(),
20795 "replace_range should start before or at cursor position"
20796 );
20797 debug_assert!(
20798 insert_range
20799 .end
20800 .cmp(&cursor_position, &buffer_snapshot)
20801 .is_le(),
20802 "insert_range should end before or at cursor position"
20803 );
20804
20805 let should_replace = match intent {
20806 CompletionIntent::CompleteWithInsert => false,
20807 CompletionIntent::CompleteWithReplace => true,
20808 CompletionIntent::Complete | CompletionIntent::Compose => {
20809 let insert_mode =
20810 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20811 .completions
20812 .lsp_insert_mode;
20813 match insert_mode {
20814 LspInsertMode::Insert => false,
20815 LspInsertMode::Replace => true,
20816 LspInsertMode::ReplaceSubsequence => {
20817 let mut text_to_replace = buffer.chars_for_range(
20818 buffer.anchor_before(replace_range.start)
20819 ..buffer.anchor_after(replace_range.end),
20820 );
20821 let mut current_needle = text_to_replace.next();
20822 for haystack_ch in completion.label.text.chars() {
20823 if let Some(needle_ch) = current_needle {
20824 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20825 current_needle = text_to_replace.next();
20826 }
20827 }
20828 }
20829 current_needle.is_none()
20830 }
20831 LspInsertMode::ReplaceSuffix => {
20832 if replace_range
20833 .end
20834 .cmp(&cursor_position, &buffer_snapshot)
20835 .is_gt()
20836 {
20837 let range_after_cursor = *cursor_position..replace_range.end;
20838 let text_after_cursor = buffer
20839 .text_for_range(
20840 buffer.anchor_before(range_after_cursor.start)
20841 ..buffer.anchor_after(range_after_cursor.end),
20842 )
20843 .collect::<String>()
20844 .to_ascii_lowercase();
20845 completion
20846 .label
20847 .text
20848 .to_ascii_lowercase()
20849 .ends_with(&text_after_cursor)
20850 } else {
20851 true
20852 }
20853 }
20854 }
20855 }
20856 };
20857
20858 if should_replace {
20859 replace_range.clone()
20860 } else {
20861 insert_range.clone()
20862 }
20863 } else {
20864 replace_range.clone()
20865 }
20866 };
20867
20868 if range_to_replace
20869 .end
20870 .cmp(&cursor_position, &buffer_snapshot)
20871 .is_lt()
20872 {
20873 range_to_replace.end = *cursor_position;
20874 }
20875
20876 CompletionEdit {
20877 new_text,
20878 replace_range: range_to_replace.to_offset(&buffer),
20879 snippet,
20880 }
20881}
20882
20883struct CompletionEdit {
20884 new_text: String,
20885 replace_range: Range<usize>,
20886 snippet: Option<Snippet>,
20887}
20888
20889fn insert_extra_newline_brackets(
20890 buffer: &MultiBufferSnapshot,
20891 range: Range<usize>,
20892 language: &language::LanguageScope,
20893) -> bool {
20894 let leading_whitespace_len = buffer
20895 .reversed_chars_at(range.start)
20896 .take_while(|c| c.is_whitespace() && *c != '\n')
20897 .map(|c| c.len_utf8())
20898 .sum::<usize>();
20899 let trailing_whitespace_len = buffer
20900 .chars_at(range.end)
20901 .take_while(|c| c.is_whitespace() && *c != '\n')
20902 .map(|c| c.len_utf8())
20903 .sum::<usize>();
20904 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20905
20906 language.brackets().any(|(pair, enabled)| {
20907 let pair_start = pair.start.trim_end();
20908 let pair_end = pair.end.trim_start();
20909
20910 enabled
20911 && pair.newline
20912 && buffer.contains_str_at(range.end, pair_end)
20913 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20914 })
20915}
20916
20917fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20918 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20919 [(buffer, range, _)] => (*buffer, range.clone()),
20920 _ => return false,
20921 };
20922 let pair = {
20923 let mut result: Option<BracketMatch> = None;
20924
20925 for pair in buffer
20926 .all_bracket_ranges(range.clone())
20927 .filter(move |pair| {
20928 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20929 })
20930 {
20931 let len = pair.close_range.end - pair.open_range.start;
20932
20933 if let Some(existing) = &result {
20934 let existing_len = existing.close_range.end - existing.open_range.start;
20935 if len > existing_len {
20936 continue;
20937 }
20938 }
20939
20940 result = Some(pair);
20941 }
20942
20943 result
20944 };
20945 let Some(pair) = pair else {
20946 return false;
20947 };
20948 pair.newline_only
20949 && buffer
20950 .chars_for_range(pair.open_range.end..range.start)
20951 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20952 .all(|c| c.is_whitespace() && c != '\n')
20953}
20954
20955fn update_uncommitted_diff_for_buffer(
20956 editor: Entity<Editor>,
20957 project: &Entity<Project>,
20958 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20959 buffer: Entity<MultiBuffer>,
20960 cx: &mut App,
20961) -> Task<()> {
20962 let mut tasks = Vec::new();
20963 project.update(cx, |project, cx| {
20964 for buffer in buffers {
20965 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20966 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20967 }
20968 }
20969 });
20970 cx.spawn(async move |cx| {
20971 let diffs = future::join_all(tasks).await;
20972 if editor
20973 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20974 .unwrap_or(false)
20975 {
20976 return;
20977 }
20978
20979 buffer
20980 .update(cx, |buffer, cx| {
20981 for diff in diffs.into_iter().flatten() {
20982 buffer.add_diff(diff, cx);
20983 }
20984 })
20985 .ok();
20986 })
20987}
20988
20989fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20990 let tab_size = tab_size.get() as usize;
20991 let mut width = offset;
20992
20993 for ch in text.chars() {
20994 width += if ch == '\t' {
20995 tab_size - (width % tab_size)
20996 } else {
20997 1
20998 };
20999 }
21000
21001 width - offset
21002}
21003
21004#[cfg(test)]
21005mod tests {
21006 use super::*;
21007
21008 #[test]
21009 fn test_string_size_with_expanded_tabs() {
21010 let nz = |val| NonZeroU32::new(val).unwrap();
21011 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
21012 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
21013 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
21014 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
21015 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
21016 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
21017 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
21018 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
21019 }
21020}
21021
21022/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
21023struct WordBreakingTokenizer<'a> {
21024 input: &'a str,
21025}
21026
21027impl<'a> WordBreakingTokenizer<'a> {
21028 fn new(input: &'a str) -> Self {
21029 Self { input }
21030 }
21031}
21032
21033fn is_char_ideographic(ch: char) -> bool {
21034 use unicode_script::Script::*;
21035 use unicode_script::UnicodeScript;
21036 matches!(ch.script(), Han | Tangut | Yi)
21037}
21038
21039fn is_grapheme_ideographic(text: &str) -> bool {
21040 text.chars().any(is_char_ideographic)
21041}
21042
21043fn is_grapheme_whitespace(text: &str) -> bool {
21044 text.chars().any(|x| x.is_whitespace())
21045}
21046
21047fn should_stay_with_preceding_ideograph(text: &str) -> bool {
21048 text.chars().next().map_or(false, |ch| {
21049 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
21050 })
21051}
21052
21053#[derive(PartialEq, Eq, Debug, Clone, Copy)]
21054enum WordBreakToken<'a> {
21055 Word { token: &'a str, grapheme_len: usize },
21056 InlineWhitespace { token: &'a str, grapheme_len: usize },
21057 Newline,
21058}
21059
21060impl<'a> Iterator for WordBreakingTokenizer<'a> {
21061 /// Yields a span, the count of graphemes in the token, and whether it was
21062 /// whitespace. Note that it also breaks at word boundaries.
21063 type Item = WordBreakToken<'a>;
21064
21065 fn next(&mut self) -> Option<Self::Item> {
21066 use unicode_segmentation::UnicodeSegmentation;
21067 if self.input.is_empty() {
21068 return None;
21069 }
21070
21071 let mut iter = self.input.graphemes(true).peekable();
21072 let mut offset = 0;
21073 let mut grapheme_len = 0;
21074 if let Some(first_grapheme) = iter.next() {
21075 let is_newline = first_grapheme == "\n";
21076 let is_whitespace = is_grapheme_whitespace(first_grapheme);
21077 offset += first_grapheme.len();
21078 grapheme_len += 1;
21079 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
21080 if let Some(grapheme) = iter.peek().copied() {
21081 if should_stay_with_preceding_ideograph(grapheme) {
21082 offset += grapheme.len();
21083 grapheme_len += 1;
21084 }
21085 }
21086 } else {
21087 let mut words = self.input[offset..].split_word_bound_indices().peekable();
21088 let mut next_word_bound = words.peek().copied();
21089 if next_word_bound.map_or(false, |(i, _)| i == 0) {
21090 next_word_bound = words.next();
21091 }
21092 while let Some(grapheme) = iter.peek().copied() {
21093 if next_word_bound.map_or(false, |(i, _)| i == offset) {
21094 break;
21095 };
21096 if is_grapheme_whitespace(grapheme) != is_whitespace
21097 || (grapheme == "\n") != is_newline
21098 {
21099 break;
21100 };
21101 offset += grapheme.len();
21102 grapheme_len += 1;
21103 iter.next();
21104 }
21105 }
21106 let token = &self.input[..offset];
21107 self.input = &self.input[offset..];
21108 if token == "\n" {
21109 Some(WordBreakToken::Newline)
21110 } else if is_whitespace {
21111 Some(WordBreakToken::InlineWhitespace {
21112 token,
21113 grapheme_len,
21114 })
21115 } else {
21116 Some(WordBreakToken::Word {
21117 token,
21118 grapheme_len,
21119 })
21120 }
21121 } else {
21122 None
21123 }
21124 }
21125}
21126
21127#[test]
21128fn test_word_breaking_tokenizer() {
21129 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
21130 ("", &[]),
21131 (" ", &[whitespace(" ", 2)]),
21132 ("Ʒ", &[word("Ʒ", 1)]),
21133 ("Ǽ", &[word("Ǽ", 1)]),
21134 ("⋑", &[word("⋑", 1)]),
21135 ("⋑⋑", &[word("⋑⋑", 2)]),
21136 (
21137 "原理,进而",
21138 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
21139 ),
21140 (
21141 "hello world",
21142 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
21143 ),
21144 (
21145 "hello, world",
21146 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
21147 ),
21148 (
21149 " hello world",
21150 &[
21151 whitespace(" ", 2),
21152 word("hello", 5),
21153 whitespace(" ", 1),
21154 word("world", 5),
21155 ],
21156 ),
21157 (
21158 "这是什么 \n 钢笔",
21159 &[
21160 word("这", 1),
21161 word("是", 1),
21162 word("什", 1),
21163 word("么", 1),
21164 whitespace(" ", 1),
21165 newline(),
21166 whitespace(" ", 1),
21167 word("钢", 1),
21168 word("笔", 1),
21169 ],
21170 ),
21171 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
21172 ];
21173
21174 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21175 WordBreakToken::Word {
21176 token,
21177 grapheme_len,
21178 }
21179 }
21180
21181 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
21182 WordBreakToken::InlineWhitespace {
21183 token,
21184 grapheme_len,
21185 }
21186 }
21187
21188 fn newline() -> WordBreakToken<'static> {
21189 WordBreakToken::Newline
21190 }
21191
21192 for (input, result) in tests {
21193 assert_eq!(
21194 WordBreakingTokenizer::new(input)
21195 .collect::<Vec<_>>()
21196 .as_slice(),
21197 *result,
21198 );
21199 }
21200}
21201
21202fn wrap_with_prefix(
21203 line_prefix: String,
21204 unwrapped_text: String,
21205 wrap_column: usize,
21206 tab_size: NonZeroU32,
21207 preserve_existing_whitespace: bool,
21208) -> String {
21209 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
21210 let mut wrapped_text = String::new();
21211 let mut current_line = line_prefix.clone();
21212
21213 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
21214 let mut current_line_len = line_prefix_len;
21215 let mut in_whitespace = false;
21216 for token in tokenizer {
21217 let have_preceding_whitespace = in_whitespace;
21218 match token {
21219 WordBreakToken::Word {
21220 token,
21221 grapheme_len,
21222 } => {
21223 in_whitespace = false;
21224 if current_line_len + grapheme_len > wrap_column
21225 && current_line_len != line_prefix_len
21226 {
21227 wrapped_text.push_str(current_line.trim_end());
21228 wrapped_text.push('\n');
21229 current_line.truncate(line_prefix.len());
21230 current_line_len = line_prefix_len;
21231 }
21232 current_line.push_str(token);
21233 current_line_len += grapheme_len;
21234 }
21235 WordBreakToken::InlineWhitespace {
21236 mut token,
21237 mut grapheme_len,
21238 } => {
21239 in_whitespace = true;
21240 if have_preceding_whitespace && !preserve_existing_whitespace {
21241 continue;
21242 }
21243 if !preserve_existing_whitespace {
21244 token = " ";
21245 grapheme_len = 1;
21246 }
21247 if current_line_len + grapheme_len > wrap_column {
21248 wrapped_text.push_str(current_line.trim_end());
21249 wrapped_text.push('\n');
21250 current_line.truncate(line_prefix.len());
21251 current_line_len = line_prefix_len;
21252 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
21253 current_line.push_str(token);
21254 current_line_len += grapheme_len;
21255 }
21256 }
21257 WordBreakToken::Newline => {
21258 in_whitespace = true;
21259 if preserve_existing_whitespace {
21260 wrapped_text.push_str(current_line.trim_end());
21261 wrapped_text.push('\n');
21262 current_line.truncate(line_prefix.len());
21263 current_line_len = line_prefix_len;
21264 } else if have_preceding_whitespace {
21265 continue;
21266 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
21267 {
21268 wrapped_text.push_str(current_line.trim_end());
21269 wrapped_text.push('\n');
21270 current_line.truncate(line_prefix.len());
21271 current_line_len = line_prefix_len;
21272 } else if current_line_len != line_prefix_len {
21273 current_line.push(' ');
21274 current_line_len += 1;
21275 }
21276 }
21277 }
21278 }
21279
21280 if !current_line.is_empty() {
21281 wrapped_text.push_str(¤t_line);
21282 }
21283 wrapped_text
21284}
21285
21286#[test]
21287fn test_wrap_with_prefix() {
21288 assert_eq!(
21289 wrap_with_prefix(
21290 "# ".to_string(),
21291 "abcdefg".to_string(),
21292 4,
21293 NonZeroU32::new(4).unwrap(),
21294 false,
21295 ),
21296 "# abcdefg"
21297 );
21298 assert_eq!(
21299 wrap_with_prefix(
21300 "".to_string(),
21301 "\thello world".to_string(),
21302 8,
21303 NonZeroU32::new(4).unwrap(),
21304 false,
21305 ),
21306 "hello\nworld"
21307 );
21308 assert_eq!(
21309 wrap_with_prefix(
21310 "// ".to_string(),
21311 "xx \nyy zz aa bb cc".to_string(),
21312 12,
21313 NonZeroU32::new(4).unwrap(),
21314 false,
21315 ),
21316 "// xx yy zz\n// aa bb cc"
21317 );
21318 assert_eq!(
21319 wrap_with_prefix(
21320 String::new(),
21321 "这是什么 \n 钢笔".to_string(),
21322 3,
21323 NonZeroU32::new(4).unwrap(),
21324 false,
21325 ),
21326 "这是什\n么 钢\n笔"
21327 );
21328}
21329
21330pub trait CollaborationHub {
21331 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
21332 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
21333 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
21334}
21335
21336impl CollaborationHub for Entity<Project> {
21337 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
21338 self.read(cx).collaborators()
21339 }
21340
21341 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21342 self.read(cx).user_store().read(cx).participant_indices()
21343 }
21344
21345 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21346 let this = self.read(cx);
21347 let user_ids = this.collaborators().values().map(|c| c.user_id);
21348 this.user_store().read(cx).participant_names(user_ids, cx)
21349 }
21350}
21351
21352pub trait SemanticsProvider {
21353 fn hover(
21354 &self,
21355 buffer: &Entity<Buffer>,
21356 position: text::Anchor,
21357 cx: &mut App,
21358 ) -> Option<Task<Vec<project::Hover>>>;
21359
21360 fn inline_values(
21361 &self,
21362 buffer_handle: Entity<Buffer>,
21363 range: Range<text::Anchor>,
21364 cx: &mut App,
21365 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21366
21367 fn inlay_hints(
21368 &self,
21369 buffer_handle: Entity<Buffer>,
21370 range: Range<text::Anchor>,
21371 cx: &mut App,
21372 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21373
21374 fn resolve_inlay_hint(
21375 &self,
21376 hint: InlayHint,
21377 buffer_handle: Entity<Buffer>,
21378 server_id: LanguageServerId,
21379 cx: &mut App,
21380 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21381
21382 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21383
21384 fn document_highlights(
21385 &self,
21386 buffer: &Entity<Buffer>,
21387 position: text::Anchor,
21388 cx: &mut App,
21389 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21390
21391 fn definitions(
21392 &self,
21393 buffer: &Entity<Buffer>,
21394 position: text::Anchor,
21395 kind: GotoDefinitionKind,
21396 cx: &mut App,
21397 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21398
21399 fn range_for_rename(
21400 &self,
21401 buffer: &Entity<Buffer>,
21402 position: text::Anchor,
21403 cx: &mut App,
21404 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21405
21406 fn perform_rename(
21407 &self,
21408 buffer: &Entity<Buffer>,
21409 position: text::Anchor,
21410 new_name: String,
21411 cx: &mut App,
21412 ) -> Option<Task<Result<ProjectTransaction>>>;
21413}
21414
21415pub trait CompletionProvider {
21416 fn completions(
21417 &self,
21418 excerpt_id: ExcerptId,
21419 buffer: &Entity<Buffer>,
21420 buffer_position: text::Anchor,
21421 trigger: CompletionContext,
21422 window: &mut Window,
21423 cx: &mut Context<Editor>,
21424 ) -> Task<Result<Vec<CompletionResponse>>>;
21425
21426 fn resolve_completions(
21427 &self,
21428 _buffer: Entity<Buffer>,
21429 _completion_indices: Vec<usize>,
21430 _completions: Rc<RefCell<Box<[Completion]>>>,
21431 _cx: &mut Context<Editor>,
21432 ) -> Task<Result<bool>> {
21433 Task::ready(Ok(false))
21434 }
21435
21436 fn apply_additional_edits_for_completion(
21437 &self,
21438 _buffer: Entity<Buffer>,
21439 _completions: Rc<RefCell<Box<[Completion]>>>,
21440 _completion_index: usize,
21441 _push_to_history: bool,
21442 _cx: &mut Context<Editor>,
21443 ) -> Task<Result<Option<language::Transaction>>> {
21444 Task::ready(Ok(None))
21445 }
21446
21447 fn is_completion_trigger(
21448 &self,
21449 buffer: &Entity<Buffer>,
21450 position: language::Anchor,
21451 text: &str,
21452 trigger_in_words: bool,
21453 menu_is_open: bool,
21454 cx: &mut Context<Editor>,
21455 ) -> bool;
21456
21457 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21458
21459 fn sort_completions(&self) -> bool {
21460 true
21461 }
21462
21463 fn filter_completions(&self) -> bool {
21464 true
21465 }
21466}
21467
21468pub trait CodeActionProvider {
21469 fn id(&self) -> Arc<str>;
21470
21471 fn code_actions(
21472 &self,
21473 buffer: &Entity<Buffer>,
21474 range: Range<text::Anchor>,
21475 window: &mut Window,
21476 cx: &mut App,
21477 ) -> Task<Result<Vec<CodeAction>>>;
21478
21479 fn apply_code_action(
21480 &self,
21481 buffer_handle: Entity<Buffer>,
21482 action: CodeAction,
21483 excerpt_id: ExcerptId,
21484 push_to_history: bool,
21485 window: &mut Window,
21486 cx: &mut App,
21487 ) -> Task<Result<ProjectTransaction>>;
21488}
21489
21490impl CodeActionProvider for Entity<Project> {
21491 fn id(&self) -> Arc<str> {
21492 "project".into()
21493 }
21494
21495 fn code_actions(
21496 &self,
21497 buffer: &Entity<Buffer>,
21498 range: Range<text::Anchor>,
21499 _window: &mut Window,
21500 cx: &mut App,
21501 ) -> Task<Result<Vec<CodeAction>>> {
21502 self.update(cx, |project, cx| {
21503 let code_lens = project.code_lens(buffer, range.clone(), cx);
21504 let code_actions = project.code_actions(buffer, range, None, cx);
21505 cx.background_spawn(async move {
21506 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21507 Ok(code_lens
21508 .context("code lens fetch")?
21509 .into_iter()
21510 .chain(code_actions.context("code action fetch")?)
21511 .collect())
21512 })
21513 })
21514 }
21515
21516 fn apply_code_action(
21517 &self,
21518 buffer_handle: Entity<Buffer>,
21519 action: CodeAction,
21520 _excerpt_id: ExcerptId,
21521 push_to_history: bool,
21522 _window: &mut Window,
21523 cx: &mut App,
21524 ) -> Task<Result<ProjectTransaction>> {
21525 self.update(cx, |project, cx| {
21526 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21527 })
21528 }
21529}
21530
21531fn snippet_completions(
21532 project: &Project,
21533 buffer: &Entity<Buffer>,
21534 buffer_position: text::Anchor,
21535 cx: &mut App,
21536) -> Task<Result<CompletionResponse>> {
21537 let languages = buffer.read(cx).languages_at(buffer_position);
21538 let snippet_store = project.snippets().read(cx);
21539
21540 let scopes: Vec<_> = languages
21541 .iter()
21542 .filter_map(|language| {
21543 let language_name = language.lsp_id();
21544 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21545
21546 if snippets.is_empty() {
21547 None
21548 } else {
21549 Some((language.default_scope(), snippets))
21550 }
21551 })
21552 .collect();
21553
21554 if scopes.is_empty() {
21555 return Task::ready(Ok(CompletionResponse {
21556 completions: vec![],
21557 is_incomplete: false,
21558 }));
21559 }
21560
21561 let snapshot = buffer.read(cx).text_snapshot();
21562 let chars: String = snapshot
21563 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21564 .collect();
21565 let executor = cx.background_executor().clone();
21566
21567 cx.background_spawn(async move {
21568 let mut is_incomplete = false;
21569 let mut completions: Vec<Completion> = Vec::new();
21570 for (scope, snippets) in scopes.into_iter() {
21571 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21572 let mut last_word = chars
21573 .chars()
21574 .take_while(|c| classifier.is_word(*c))
21575 .collect::<String>();
21576 last_word = last_word.chars().rev().collect();
21577
21578 if last_word.is_empty() {
21579 return Ok(CompletionResponse {
21580 completions: vec![],
21581 is_incomplete: true,
21582 });
21583 }
21584
21585 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21586 let to_lsp = |point: &text::Anchor| {
21587 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21588 point_to_lsp(end)
21589 };
21590 let lsp_end = to_lsp(&buffer_position);
21591
21592 let candidates = snippets
21593 .iter()
21594 .enumerate()
21595 .flat_map(|(ix, snippet)| {
21596 snippet
21597 .prefix
21598 .iter()
21599 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21600 })
21601 .collect::<Vec<StringMatchCandidate>>();
21602
21603 const MAX_RESULTS: usize = 100;
21604 let mut matches = fuzzy::match_strings(
21605 &candidates,
21606 &last_word,
21607 last_word.chars().any(|c| c.is_uppercase()),
21608 true,
21609 MAX_RESULTS,
21610 &Default::default(),
21611 executor.clone(),
21612 )
21613 .await;
21614
21615 if matches.len() >= MAX_RESULTS {
21616 is_incomplete = true;
21617 }
21618
21619 // Remove all candidates where the query's start does not match the start of any word in the candidate
21620 if let Some(query_start) = last_word.chars().next() {
21621 matches.retain(|string_match| {
21622 split_words(&string_match.string).any(|word| {
21623 // Check that the first codepoint of the word as lowercase matches the first
21624 // codepoint of the query as lowercase
21625 word.chars()
21626 .flat_map(|codepoint| codepoint.to_lowercase())
21627 .zip(query_start.to_lowercase())
21628 .all(|(word_cp, query_cp)| word_cp == query_cp)
21629 })
21630 });
21631 }
21632
21633 let matched_strings = matches
21634 .into_iter()
21635 .map(|m| m.string)
21636 .collect::<HashSet<_>>();
21637
21638 completions.extend(snippets.iter().filter_map(|snippet| {
21639 let matching_prefix = snippet
21640 .prefix
21641 .iter()
21642 .find(|prefix| matched_strings.contains(*prefix))?;
21643 let start = as_offset - last_word.len();
21644 let start = snapshot.anchor_before(start);
21645 let range = start..buffer_position;
21646 let lsp_start = to_lsp(&start);
21647 let lsp_range = lsp::Range {
21648 start: lsp_start,
21649 end: lsp_end,
21650 };
21651 Some(Completion {
21652 replace_range: range,
21653 new_text: snippet.body.clone(),
21654 source: CompletionSource::Lsp {
21655 insert_range: None,
21656 server_id: LanguageServerId(usize::MAX),
21657 resolved: true,
21658 lsp_completion: Box::new(lsp::CompletionItem {
21659 label: snippet.prefix.first().unwrap().clone(),
21660 kind: Some(CompletionItemKind::SNIPPET),
21661 label_details: snippet.description.as_ref().map(|description| {
21662 lsp::CompletionItemLabelDetails {
21663 detail: Some(description.clone()),
21664 description: None,
21665 }
21666 }),
21667 insert_text_format: Some(InsertTextFormat::SNIPPET),
21668 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21669 lsp::InsertReplaceEdit {
21670 new_text: snippet.body.clone(),
21671 insert: lsp_range,
21672 replace: lsp_range,
21673 },
21674 )),
21675 filter_text: Some(snippet.body.clone()),
21676 sort_text: Some(char::MAX.to_string()),
21677 ..lsp::CompletionItem::default()
21678 }),
21679 lsp_defaults: None,
21680 },
21681 label: CodeLabel {
21682 text: matching_prefix.clone(),
21683 runs: Vec::new(),
21684 filter_range: 0..matching_prefix.len(),
21685 },
21686 icon_path: None,
21687 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21688 single_line: snippet.name.clone().into(),
21689 plain_text: snippet
21690 .description
21691 .clone()
21692 .map(|description| description.into()),
21693 }),
21694 insert_text_mode: None,
21695 confirm: None,
21696 })
21697 }))
21698 }
21699
21700 Ok(CompletionResponse {
21701 completions,
21702 is_incomplete,
21703 })
21704 })
21705}
21706
21707impl CompletionProvider for Entity<Project> {
21708 fn completions(
21709 &self,
21710 _excerpt_id: ExcerptId,
21711 buffer: &Entity<Buffer>,
21712 buffer_position: text::Anchor,
21713 options: CompletionContext,
21714 _window: &mut Window,
21715 cx: &mut Context<Editor>,
21716 ) -> Task<Result<Vec<CompletionResponse>>> {
21717 self.update(cx, |project, cx| {
21718 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21719 let project_completions = project.completions(buffer, buffer_position, options, cx);
21720 cx.background_spawn(async move {
21721 let mut responses = project_completions.await?;
21722 let snippets = snippets.await?;
21723 if !snippets.completions.is_empty() {
21724 responses.push(snippets);
21725 }
21726 Ok(responses)
21727 })
21728 })
21729 }
21730
21731 fn resolve_completions(
21732 &self,
21733 buffer: Entity<Buffer>,
21734 completion_indices: Vec<usize>,
21735 completions: Rc<RefCell<Box<[Completion]>>>,
21736 cx: &mut Context<Editor>,
21737 ) -> Task<Result<bool>> {
21738 self.update(cx, |project, cx| {
21739 project.lsp_store().update(cx, |lsp_store, cx| {
21740 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21741 })
21742 })
21743 }
21744
21745 fn apply_additional_edits_for_completion(
21746 &self,
21747 buffer: Entity<Buffer>,
21748 completions: Rc<RefCell<Box<[Completion]>>>,
21749 completion_index: usize,
21750 push_to_history: bool,
21751 cx: &mut Context<Editor>,
21752 ) -> Task<Result<Option<language::Transaction>>> {
21753 self.update(cx, |project, cx| {
21754 project.lsp_store().update(cx, |lsp_store, cx| {
21755 lsp_store.apply_additional_edits_for_completion(
21756 buffer,
21757 completions,
21758 completion_index,
21759 push_to_history,
21760 cx,
21761 )
21762 })
21763 })
21764 }
21765
21766 fn is_completion_trigger(
21767 &self,
21768 buffer: &Entity<Buffer>,
21769 position: language::Anchor,
21770 text: &str,
21771 trigger_in_words: bool,
21772 menu_is_open: bool,
21773 cx: &mut Context<Editor>,
21774 ) -> bool {
21775 let mut chars = text.chars();
21776 let char = if let Some(char) = chars.next() {
21777 char
21778 } else {
21779 return false;
21780 };
21781 if chars.next().is_some() {
21782 return false;
21783 }
21784
21785 let buffer = buffer.read(cx);
21786 let snapshot = buffer.snapshot();
21787 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21788 return false;
21789 }
21790 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21791 if trigger_in_words && classifier.is_word(char) {
21792 return true;
21793 }
21794
21795 buffer.completion_triggers().contains(text)
21796 }
21797}
21798
21799impl SemanticsProvider for Entity<Project> {
21800 fn hover(
21801 &self,
21802 buffer: &Entity<Buffer>,
21803 position: text::Anchor,
21804 cx: &mut App,
21805 ) -> Option<Task<Vec<project::Hover>>> {
21806 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21807 }
21808
21809 fn document_highlights(
21810 &self,
21811 buffer: &Entity<Buffer>,
21812 position: text::Anchor,
21813 cx: &mut App,
21814 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21815 Some(self.update(cx, |project, cx| {
21816 project.document_highlights(buffer, position, cx)
21817 }))
21818 }
21819
21820 fn definitions(
21821 &self,
21822 buffer: &Entity<Buffer>,
21823 position: text::Anchor,
21824 kind: GotoDefinitionKind,
21825 cx: &mut App,
21826 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21827 Some(self.update(cx, |project, cx| match kind {
21828 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21829 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21830 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21831 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21832 }))
21833 }
21834
21835 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21836 // TODO: make this work for remote projects
21837 self.update(cx, |project, cx| {
21838 if project
21839 .active_debug_session(cx)
21840 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21841 {
21842 return true;
21843 }
21844
21845 buffer.update(cx, |buffer, cx| {
21846 project.any_language_server_supports_inlay_hints(buffer, cx)
21847 })
21848 })
21849 }
21850
21851 fn inline_values(
21852 &self,
21853 buffer_handle: Entity<Buffer>,
21854 range: Range<text::Anchor>,
21855 cx: &mut App,
21856 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21857 self.update(cx, |project, cx| {
21858 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21859
21860 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21861 })
21862 }
21863
21864 fn inlay_hints(
21865 &self,
21866 buffer_handle: Entity<Buffer>,
21867 range: Range<text::Anchor>,
21868 cx: &mut App,
21869 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21870 Some(self.update(cx, |project, cx| {
21871 project.inlay_hints(buffer_handle, range, cx)
21872 }))
21873 }
21874
21875 fn resolve_inlay_hint(
21876 &self,
21877 hint: InlayHint,
21878 buffer_handle: Entity<Buffer>,
21879 server_id: LanguageServerId,
21880 cx: &mut App,
21881 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21882 Some(self.update(cx, |project, cx| {
21883 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21884 }))
21885 }
21886
21887 fn range_for_rename(
21888 &self,
21889 buffer: &Entity<Buffer>,
21890 position: text::Anchor,
21891 cx: &mut App,
21892 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21893 Some(self.update(cx, |project, cx| {
21894 let buffer = buffer.clone();
21895 let task = project.prepare_rename(buffer.clone(), position, cx);
21896 cx.spawn(async move |_, cx| {
21897 Ok(match task.await? {
21898 PrepareRenameResponse::Success(range) => Some(range),
21899 PrepareRenameResponse::InvalidPosition => None,
21900 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21901 // Fallback on using TreeSitter info to determine identifier range
21902 buffer.read_with(cx, |buffer, _| {
21903 let snapshot = buffer.snapshot();
21904 let (range, kind) = snapshot.surrounding_word(position);
21905 if kind != Some(CharKind::Word) {
21906 return None;
21907 }
21908 Some(
21909 snapshot.anchor_before(range.start)
21910 ..snapshot.anchor_after(range.end),
21911 )
21912 })?
21913 }
21914 })
21915 })
21916 }))
21917 }
21918
21919 fn perform_rename(
21920 &self,
21921 buffer: &Entity<Buffer>,
21922 position: text::Anchor,
21923 new_name: String,
21924 cx: &mut App,
21925 ) -> Option<Task<Result<ProjectTransaction>>> {
21926 Some(self.update(cx, |project, cx| {
21927 project.perform_rename(buffer.clone(), position, new_name, cx)
21928 }))
21929 }
21930}
21931
21932fn inlay_hint_settings(
21933 location: Anchor,
21934 snapshot: &MultiBufferSnapshot,
21935 cx: &mut Context<Editor>,
21936) -> InlayHintSettings {
21937 let file = snapshot.file_at(location);
21938 let language = snapshot.language_at(location).map(|l| l.name());
21939 language_settings(language, file, cx).inlay_hints
21940}
21941
21942fn consume_contiguous_rows(
21943 contiguous_row_selections: &mut Vec<Selection<Point>>,
21944 selection: &Selection<Point>,
21945 display_map: &DisplaySnapshot,
21946 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21947) -> (MultiBufferRow, MultiBufferRow) {
21948 contiguous_row_selections.push(selection.clone());
21949 let start_row = MultiBufferRow(selection.start.row);
21950 let mut end_row = ending_row(selection, display_map);
21951
21952 while let Some(next_selection) = selections.peek() {
21953 if next_selection.start.row <= end_row.0 {
21954 end_row = ending_row(next_selection, display_map);
21955 contiguous_row_selections.push(selections.next().unwrap().clone());
21956 } else {
21957 break;
21958 }
21959 }
21960 (start_row, end_row)
21961}
21962
21963fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21964 if next_selection.end.column > 0 || next_selection.is_empty() {
21965 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21966 } else {
21967 MultiBufferRow(next_selection.end.row)
21968 }
21969}
21970
21971impl EditorSnapshot {
21972 pub fn remote_selections_in_range<'a>(
21973 &'a self,
21974 range: &'a Range<Anchor>,
21975 collaboration_hub: &dyn CollaborationHub,
21976 cx: &'a App,
21977 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21978 let participant_names = collaboration_hub.user_names(cx);
21979 let participant_indices = collaboration_hub.user_participant_indices(cx);
21980 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21981 let collaborators_by_replica_id = collaborators_by_peer_id
21982 .values()
21983 .map(|collaborator| (collaborator.replica_id, collaborator))
21984 .collect::<HashMap<_, _>>();
21985 self.buffer_snapshot
21986 .selections_in_range(range, false)
21987 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21988 if replica_id == AGENT_REPLICA_ID {
21989 Some(RemoteSelection {
21990 replica_id,
21991 selection,
21992 cursor_shape,
21993 line_mode,
21994 collaborator_id: CollaboratorId::Agent,
21995 user_name: Some("Agent".into()),
21996 color: cx.theme().players().agent(),
21997 })
21998 } else {
21999 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
22000 let participant_index = participant_indices.get(&collaborator.user_id).copied();
22001 let user_name = participant_names.get(&collaborator.user_id).cloned();
22002 Some(RemoteSelection {
22003 replica_id,
22004 selection,
22005 cursor_shape,
22006 line_mode,
22007 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
22008 user_name,
22009 color: if let Some(index) = participant_index {
22010 cx.theme().players().color_for_participant(index.0)
22011 } else {
22012 cx.theme().players().absent()
22013 },
22014 })
22015 }
22016 })
22017 }
22018
22019 pub fn hunks_for_ranges(
22020 &self,
22021 ranges: impl IntoIterator<Item = Range<Point>>,
22022 ) -> Vec<MultiBufferDiffHunk> {
22023 let mut hunks = Vec::new();
22024 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
22025 HashMap::default();
22026 for query_range in ranges {
22027 let query_rows =
22028 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
22029 for hunk in self.buffer_snapshot.diff_hunks_in_range(
22030 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
22031 ) {
22032 // Include deleted hunks that are adjacent to the query range, because
22033 // otherwise they would be missed.
22034 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
22035 if hunk.status().is_deleted() {
22036 intersects_range |= hunk.row_range.start == query_rows.end;
22037 intersects_range |= hunk.row_range.end == query_rows.start;
22038 }
22039 if intersects_range {
22040 if !processed_buffer_rows
22041 .entry(hunk.buffer_id)
22042 .or_default()
22043 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
22044 {
22045 continue;
22046 }
22047 hunks.push(hunk);
22048 }
22049 }
22050 }
22051
22052 hunks
22053 }
22054
22055 fn display_diff_hunks_for_rows<'a>(
22056 &'a self,
22057 display_rows: Range<DisplayRow>,
22058 folded_buffers: &'a HashSet<BufferId>,
22059 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
22060 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
22061 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
22062
22063 self.buffer_snapshot
22064 .diff_hunks_in_range(buffer_start..buffer_end)
22065 .filter_map(|hunk| {
22066 if folded_buffers.contains(&hunk.buffer_id) {
22067 return None;
22068 }
22069
22070 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
22071 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
22072
22073 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
22074 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
22075
22076 let display_hunk = if hunk_display_start.column() != 0 {
22077 DisplayDiffHunk::Folded {
22078 display_row: hunk_display_start.row(),
22079 }
22080 } else {
22081 let mut end_row = hunk_display_end.row();
22082 if hunk_display_end.column() > 0 {
22083 end_row.0 += 1;
22084 }
22085 let is_created_file = hunk.is_created_file();
22086 DisplayDiffHunk::Unfolded {
22087 status: hunk.status(),
22088 diff_base_byte_range: hunk.diff_base_byte_range,
22089 display_row_range: hunk_display_start.row()..end_row,
22090 multi_buffer_range: Anchor::range_in_buffer(
22091 hunk.excerpt_id,
22092 hunk.buffer_id,
22093 hunk.buffer_range,
22094 ),
22095 is_created_file,
22096 }
22097 };
22098
22099 Some(display_hunk)
22100 })
22101 }
22102
22103 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
22104 self.display_snapshot.buffer_snapshot.language_at(position)
22105 }
22106
22107 pub fn is_focused(&self) -> bool {
22108 self.is_focused
22109 }
22110
22111 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
22112 self.placeholder_text.as_ref()
22113 }
22114
22115 pub fn scroll_position(&self) -> gpui::Point<f32> {
22116 self.scroll_anchor.scroll_position(&self.display_snapshot)
22117 }
22118
22119 fn gutter_dimensions(
22120 &self,
22121 font_id: FontId,
22122 font_size: Pixels,
22123 max_line_number_width: Pixels,
22124 cx: &App,
22125 ) -> Option<GutterDimensions> {
22126 if !self.show_gutter {
22127 return None;
22128 }
22129
22130 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
22131 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
22132
22133 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
22134 matches!(
22135 ProjectSettings::get_global(cx).git.git_gutter,
22136 Some(GitGutterSetting::TrackedFiles)
22137 )
22138 });
22139 let gutter_settings = EditorSettings::get_global(cx).gutter;
22140 let show_line_numbers = self
22141 .show_line_numbers
22142 .unwrap_or(gutter_settings.line_numbers);
22143 let line_gutter_width = if show_line_numbers {
22144 // Avoid flicker-like gutter resizes when the line number gains another digit by
22145 // only resizing the gutter on files with > 10**min_line_number_digits lines.
22146 let min_width_for_number_on_gutter =
22147 ch_advance * gutter_settings.min_line_number_digits as f32;
22148 max_line_number_width.max(min_width_for_number_on_gutter)
22149 } else {
22150 0.0.into()
22151 };
22152
22153 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
22154 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
22155
22156 let git_blame_entries_width =
22157 self.git_blame_gutter_max_author_length
22158 .map(|max_author_length| {
22159 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
22160 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
22161
22162 /// The number of characters to dedicate to gaps and margins.
22163 const SPACING_WIDTH: usize = 4;
22164
22165 let max_char_count = max_author_length.min(renderer.max_author_length())
22166 + ::git::SHORT_SHA_LENGTH
22167 + MAX_RELATIVE_TIMESTAMP.len()
22168 + SPACING_WIDTH;
22169
22170 ch_advance * max_char_count
22171 });
22172
22173 let is_singleton = self.buffer_snapshot.is_singleton();
22174
22175 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
22176 left_padding += if !is_singleton {
22177 ch_width * 4.0
22178 } else if show_runnables || show_breakpoints {
22179 ch_width * 3.0
22180 } else if show_git_gutter && show_line_numbers {
22181 ch_width * 2.0
22182 } else if show_git_gutter || show_line_numbers {
22183 ch_width
22184 } else {
22185 px(0.)
22186 };
22187
22188 let shows_folds = is_singleton && gutter_settings.folds;
22189
22190 let right_padding = if shows_folds && show_line_numbers {
22191 ch_width * 4.0
22192 } else if shows_folds || (!is_singleton && show_line_numbers) {
22193 ch_width * 3.0
22194 } else if show_line_numbers {
22195 ch_width
22196 } else {
22197 px(0.)
22198 };
22199
22200 Some(GutterDimensions {
22201 left_padding,
22202 right_padding,
22203 width: line_gutter_width + left_padding + right_padding,
22204 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
22205 git_blame_entries_width,
22206 })
22207 }
22208
22209 pub fn render_crease_toggle(
22210 &self,
22211 buffer_row: MultiBufferRow,
22212 row_contains_cursor: bool,
22213 editor: Entity<Editor>,
22214 window: &mut Window,
22215 cx: &mut App,
22216 ) -> Option<AnyElement> {
22217 let folded = self.is_line_folded(buffer_row);
22218 let mut is_foldable = false;
22219
22220 if let Some(crease) = self
22221 .crease_snapshot
22222 .query_row(buffer_row, &self.buffer_snapshot)
22223 {
22224 is_foldable = true;
22225 match crease {
22226 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
22227 if let Some(render_toggle) = render_toggle {
22228 let toggle_callback =
22229 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
22230 if folded {
22231 editor.update(cx, |editor, cx| {
22232 editor.fold_at(buffer_row, window, cx)
22233 });
22234 } else {
22235 editor.update(cx, |editor, cx| {
22236 editor.unfold_at(buffer_row, window, cx)
22237 });
22238 }
22239 });
22240 return Some((render_toggle)(
22241 buffer_row,
22242 folded,
22243 toggle_callback,
22244 window,
22245 cx,
22246 ));
22247 }
22248 }
22249 }
22250 }
22251
22252 is_foldable |= self.starts_indent(buffer_row);
22253
22254 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
22255 Some(
22256 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
22257 .toggle_state(folded)
22258 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
22259 if folded {
22260 this.unfold_at(buffer_row, window, cx);
22261 } else {
22262 this.fold_at(buffer_row, window, cx);
22263 }
22264 }))
22265 .into_any_element(),
22266 )
22267 } else {
22268 None
22269 }
22270 }
22271
22272 pub fn render_crease_trailer(
22273 &self,
22274 buffer_row: MultiBufferRow,
22275 window: &mut Window,
22276 cx: &mut App,
22277 ) -> Option<AnyElement> {
22278 let folded = self.is_line_folded(buffer_row);
22279 if let Crease::Inline { render_trailer, .. } = self
22280 .crease_snapshot
22281 .query_row(buffer_row, &self.buffer_snapshot)?
22282 {
22283 let render_trailer = render_trailer.as_ref()?;
22284 Some(render_trailer(buffer_row, folded, window, cx))
22285 } else {
22286 None
22287 }
22288 }
22289}
22290
22291impl Deref for EditorSnapshot {
22292 type Target = DisplaySnapshot;
22293
22294 fn deref(&self) -> &Self::Target {
22295 &self.display_snapshot
22296 }
22297}
22298
22299#[derive(Clone, Debug, PartialEq, Eq)]
22300pub enum EditorEvent {
22301 InputIgnored {
22302 text: Arc<str>,
22303 },
22304 InputHandled {
22305 utf16_range_to_replace: Option<Range<isize>>,
22306 text: Arc<str>,
22307 },
22308 ExcerptsAdded {
22309 buffer: Entity<Buffer>,
22310 predecessor: ExcerptId,
22311 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
22312 },
22313 ExcerptsRemoved {
22314 ids: Vec<ExcerptId>,
22315 removed_buffer_ids: Vec<BufferId>,
22316 },
22317 BufferFoldToggled {
22318 ids: Vec<ExcerptId>,
22319 folded: bool,
22320 },
22321 ExcerptsEdited {
22322 ids: Vec<ExcerptId>,
22323 },
22324 ExcerptsExpanded {
22325 ids: Vec<ExcerptId>,
22326 },
22327 BufferEdited,
22328 Edited {
22329 transaction_id: clock::Lamport,
22330 },
22331 Reparsed(BufferId),
22332 Focused,
22333 FocusedIn,
22334 Blurred,
22335 DirtyChanged,
22336 Saved,
22337 TitleChanged,
22338 DiffBaseChanged,
22339 SelectionsChanged {
22340 local: bool,
22341 },
22342 ScrollPositionChanged {
22343 local: bool,
22344 autoscroll: bool,
22345 },
22346 Closed,
22347 TransactionUndone {
22348 transaction_id: clock::Lamport,
22349 },
22350 TransactionBegun {
22351 transaction_id: clock::Lamport,
22352 },
22353 Reloaded,
22354 CursorShapeChanged,
22355 PushedToNavHistory {
22356 anchor: Anchor,
22357 is_deactivate: bool,
22358 },
22359}
22360
22361impl EventEmitter<EditorEvent> for Editor {}
22362
22363impl Focusable for Editor {
22364 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22365 self.focus_handle.clone()
22366 }
22367}
22368
22369impl Render for Editor {
22370 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22371 let settings = ThemeSettings::get_global(cx);
22372
22373 let mut text_style = match self.mode {
22374 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22375 color: cx.theme().colors().editor_foreground,
22376 font_family: settings.ui_font.family.clone(),
22377 font_features: settings.ui_font.features.clone(),
22378 font_fallbacks: settings.ui_font.fallbacks.clone(),
22379 font_size: rems(0.875).into(),
22380 font_weight: settings.ui_font.weight,
22381 line_height: relative(settings.buffer_line_height.value()),
22382 ..Default::default()
22383 },
22384 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22385 color: cx.theme().colors().editor_foreground,
22386 font_family: settings.buffer_font.family.clone(),
22387 font_features: settings.buffer_font.features.clone(),
22388 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22389 font_size: settings.buffer_font_size(cx).into(),
22390 font_weight: settings.buffer_font.weight,
22391 line_height: relative(settings.buffer_line_height.value()),
22392 ..Default::default()
22393 },
22394 };
22395 if let Some(text_style_refinement) = &self.text_style_refinement {
22396 text_style.refine(text_style_refinement)
22397 }
22398
22399 let background = match self.mode {
22400 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22401 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22402 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22403 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22404 };
22405
22406 EditorElement::new(
22407 &cx.entity(),
22408 EditorStyle {
22409 background,
22410 border: cx.theme().colors().border,
22411 local_player: cx.theme().players().local(),
22412 text: text_style,
22413 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22414 syntax: cx.theme().syntax().clone(),
22415 status: cx.theme().status().clone(),
22416 inlay_hints_style: make_inlay_hints_style(cx),
22417 inline_completion_styles: make_suggestion_styles(cx),
22418 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22419 show_underlines: self.diagnostics_enabled(),
22420 },
22421 )
22422 }
22423}
22424
22425impl EntityInputHandler for Editor {
22426 fn text_for_range(
22427 &mut self,
22428 range_utf16: Range<usize>,
22429 adjusted_range: &mut Option<Range<usize>>,
22430 _: &mut Window,
22431 cx: &mut Context<Self>,
22432 ) -> Option<String> {
22433 let snapshot = self.buffer.read(cx).read(cx);
22434 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22435 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22436 if (start.0..end.0) != range_utf16 {
22437 adjusted_range.replace(start.0..end.0);
22438 }
22439 Some(snapshot.text_for_range(start..end).collect())
22440 }
22441
22442 fn selected_text_range(
22443 &mut self,
22444 ignore_disabled_input: bool,
22445 _: &mut Window,
22446 cx: &mut Context<Self>,
22447 ) -> Option<UTF16Selection> {
22448 // Prevent the IME menu from appearing when holding down an alphabetic key
22449 // while input is disabled.
22450 if !ignore_disabled_input && !self.input_enabled {
22451 return None;
22452 }
22453
22454 let selection = self.selections.newest::<OffsetUtf16>(cx);
22455 let range = selection.range();
22456
22457 Some(UTF16Selection {
22458 range: range.start.0..range.end.0,
22459 reversed: selection.reversed,
22460 })
22461 }
22462
22463 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22464 let snapshot = self.buffer.read(cx).read(cx);
22465 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22466 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22467 }
22468
22469 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22470 self.clear_highlights::<InputComposition>(cx);
22471 self.ime_transaction.take();
22472 }
22473
22474 fn replace_text_in_range(
22475 &mut self,
22476 range_utf16: Option<Range<usize>>,
22477 text: &str,
22478 window: &mut Window,
22479 cx: &mut Context<Self>,
22480 ) {
22481 if !self.input_enabled {
22482 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22483 return;
22484 }
22485
22486 self.transact(window, cx, |this, window, cx| {
22487 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22488 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22489 Some(this.selection_replacement_ranges(range_utf16, cx))
22490 } else {
22491 this.marked_text_ranges(cx)
22492 };
22493
22494 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22495 let newest_selection_id = this.selections.newest_anchor().id;
22496 this.selections
22497 .all::<OffsetUtf16>(cx)
22498 .iter()
22499 .zip(ranges_to_replace.iter())
22500 .find_map(|(selection, range)| {
22501 if selection.id == newest_selection_id {
22502 Some(
22503 (range.start.0 as isize - selection.head().0 as isize)
22504 ..(range.end.0 as isize - selection.head().0 as isize),
22505 )
22506 } else {
22507 None
22508 }
22509 })
22510 });
22511
22512 cx.emit(EditorEvent::InputHandled {
22513 utf16_range_to_replace: range_to_replace,
22514 text: text.into(),
22515 });
22516
22517 if let Some(new_selected_ranges) = new_selected_ranges {
22518 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22519 selections.select_ranges(new_selected_ranges)
22520 });
22521 this.backspace(&Default::default(), window, cx);
22522 }
22523
22524 this.handle_input(text, window, cx);
22525 });
22526
22527 if let Some(transaction) = self.ime_transaction {
22528 self.buffer.update(cx, |buffer, cx| {
22529 buffer.group_until_transaction(transaction, cx);
22530 });
22531 }
22532
22533 self.unmark_text(window, cx);
22534 }
22535
22536 fn replace_and_mark_text_in_range(
22537 &mut self,
22538 range_utf16: Option<Range<usize>>,
22539 text: &str,
22540 new_selected_range_utf16: Option<Range<usize>>,
22541 window: &mut Window,
22542 cx: &mut Context<Self>,
22543 ) {
22544 if !self.input_enabled {
22545 return;
22546 }
22547
22548 let transaction = self.transact(window, cx, |this, window, cx| {
22549 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22550 let snapshot = this.buffer.read(cx).read(cx);
22551 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22552 for marked_range in &mut marked_ranges {
22553 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22554 marked_range.start.0 += relative_range_utf16.start;
22555 marked_range.start =
22556 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22557 marked_range.end =
22558 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22559 }
22560 }
22561 Some(marked_ranges)
22562 } else if let Some(range_utf16) = range_utf16 {
22563 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22564 Some(this.selection_replacement_ranges(range_utf16, cx))
22565 } else {
22566 None
22567 };
22568
22569 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22570 let newest_selection_id = this.selections.newest_anchor().id;
22571 this.selections
22572 .all::<OffsetUtf16>(cx)
22573 .iter()
22574 .zip(ranges_to_replace.iter())
22575 .find_map(|(selection, range)| {
22576 if selection.id == newest_selection_id {
22577 Some(
22578 (range.start.0 as isize - selection.head().0 as isize)
22579 ..(range.end.0 as isize - selection.head().0 as isize),
22580 )
22581 } else {
22582 None
22583 }
22584 })
22585 });
22586
22587 cx.emit(EditorEvent::InputHandled {
22588 utf16_range_to_replace: range_to_replace,
22589 text: text.into(),
22590 });
22591
22592 if let Some(ranges) = ranges_to_replace {
22593 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22594 s.select_ranges(ranges)
22595 });
22596 }
22597
22598 let marked_ranges = {
22599 let snapshot = this.buffer.read(cx).read(cx);
22600 this.selections
22601 .disjoint_anchors()
22602 .iter()
22603 .map(|selection| {
22604 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22605 })
22606 .collect::<Vec<_>>()
22607 };
22608
22609 if text.is_empty() {
22610 this.unmark_text(window, cx);
22611 } else {
22612 this.highlight_text::<InputComposition>(
22613 marked_ranges.clone(),
22614 HighlightStyle {
22615 underline: Some(UnderlineStyle {
22616 thickness: px(1.),
22617 color: None,
22618 wavy: false,
22619 }),
22620 ..Default::default()
22621 },
22622 cx,
22623 );
22624 }
22625
22626 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22627 let use_autoclose = this.use_autoclose;
22628 let use_auto_surround = this.use_auto_surround;
22629 this.set_use_autoclose(false);
22630 this.set_use_auto_surround(false);
22631 this.handle_input(text, window, cx);
22632 this.set_use_autoclose(use_autoclose);
22633 this.set_use_auto_surround(use_auto_surround);
22634
22635 if let Some(new_selected_range) = new_selected_range_utf16 {
22636 let snapshot = this.buffer.read(cx).read(cx);
22637 let new_selected_ranges = marked_ranges
22638 .into_iter()
22639 .map(|marked_range| {
22640 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22641 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22642 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22643 snapshot.clip_offset_utf16(new_start, Bias::Left)
22644 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22645 })
22646 .collect::<Vec<_>>();
22647
22648 drop(snapshot);
22649 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22650 selections.select_ranges(new_selected_ranges)
22651 });
22652 }
22653 });
22654
22655 self.ime_transaction = self.ime_transaction.or(transaction);
22656 if let Some(transaction) = self.ime_transaction {
22657 self.buffer.update(cx, |buffer, cx| {
22658 buffer.group_until_transaction(transaction, cx);
22659 });
22660 }
22661
22662 if self.text_highlights::<InputComposition>(cx).is_none() {
22663 self.ime_transaction.take();
22664 }
22665 }
22666
22667 fn bounds_for_range(
22668 &mut self,
22669 range_utf16: Range<usize>,
22670 element_bounds: gpui::Bounds<Pixels>,
22671 window: &mut Window,
22672 cx: &mut Context<Self>,
22673 ) -> Option<gpui::Bounds<Pixels>> {
22674 let text_layout_details = self.text_layout_details(window);
22675 let CharacterDimensions {
22676 em_width,
22677 em_advance,
22678 line_height,
22679 } = self.character_dimensions(window);
22680
22681 let snapshot = self.snapshot(window, cx);
22682 let scroll_position = snapshot.scroll_position();
22683 let scroll_left = scroll_position.x * em_advance;
22684
22685 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22686 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22687 + self.gutter_dimensions.full_width();
22688 let y = line_height * (start.row().as_f32() - scroll_position.y);
22689
22690 Some(Bounds {
22691 origin: element_bounds.origin + point(x, y),
22692 size: size(em_width, line_height),
22693 })
22694 }
22695
22696 fn character_index_for_point(
22697 &mut self,
22698 point: gpui::Point<Pixels>,
22699 _window: &mut Window,
22700 _cx: &mut Context<Self>,
22701 ) -> Option<usize> {
22702 let position_map = self.last_position_map.as_ref()?;
22703 if !position_map.text_hitbox.contains(&point) {
22704 return None;
22705 }
22706 let display_point = position_map.point_for_position(point).previous_valid;
22707 let anchor = position_map
22708 .snapshot
22709 .display_point_to_anchor(display_point, Bias::Left);
22710 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22711 Some(utf16_offset.0)
22712 }
22713}
22714
22715trait SelectionExt {
22716 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22717 fn spanned_rows(
22718 &self,
22719 include_end_if_at_line_start: bool,
22720 map: &DisplaySnapshot,
22721 ) -> Range<MultiBufferRow>;
22722}
22723
22724impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22725 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22726 let start = self
22727 .start
22728 .to_point(&map.buffer_snapshot)
22729 .to_display_point(map);
22730 let end = self
22731 .end
22732 .to_point(&map.buffer_snapshot)
22733 .to_display_point(map);
22734 if self.reversed {
22735 end..start
22736 } else {
22737 start..end
22738 }
22739 }
22740
22741 fn spanned_rows(
22742 &self,
22743 include_end_if_at_line_start: bool,
22744 map: &DisplaySnapshot,
22745 ) -> Range<MultiBufferRow> {
22746 let start = self.start.to_point(&map.buffer_snapshot);
22747 let mut end = self.end.to_point(&map.buffer_snapshot);
22748 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22749 end.row -= 1;
22750 }
22751
22752 let buffer_start = map.prev_line_boundary(start).0;
22753 let buffer_end = map.next_line_boundary(end).0;
22754 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22755 }
22756}
22757
22758impl<T: InvalidationRegion> InvalidationStack<T> {
22759 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22760 where
22761 S: Clone + ToOffset,
22762 {
22763 while let Some(region) = self.last() {
22764 let all_selections_inside_invalidation_ranges =
22765 if selections.len() == region.ranges().len() {
22766 selections
22767 .iter()
22768 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22769 .all(|(selection, invalidation_range)| {
22770 let head = selection.head().to_offset(buffer);
22771 invalidation_range.start <= head && invalidation_range.end >= head
22772 })
22773 } else {
22774 false
22775 };
22776
22777 if all_selections_inside_invalidation_ranges {
22778 break;
22779 } else {
22780 self.pop();
22781 }
22782 }
22783 }
22784}
22785
22786impl<T> Default for InvalidationStack<T> {
22787 fn default() -> Self {
22788 Self(Default::default())
22789 }
22790}
22791
22792impl<T> Deref for InvalidationStack<T> {
22793 type Target = Vec<T>;
22794
22795 fn deref(&self) -> &Self::Target {
22796 &self.0
22797 }
22798}
22799
22800impl<T> DerefMut for InvalidationStack<T> {
22801 fn deref_mut(&mut self) -> &mut Self::Target {
22802 &mut self.0
22803 }
22804}
22805
22806impl InvalidationRegion for SnippetState {
22807 fn ranges(&self) -> &[Range<Anchor>] {
22808 &self.ranges[self.active_index]
22809 }
22810}
22811
22812fn inline_completion_edit_text(
22813 current_snapshot: &BufferSnapshot,
22814 edits: &[(Range<Anchor>, String)],
22815 edit_preview: &EditPreview,
22816 include_deletions: bool,
22817 cx: &App,
22818) -> HighlightedText {
22819 let edits = edits
22820 .iter()
22821 .map(|(anchor, text)| {
22822 (
22823 anchor.start.text_anchor..anchor.end.text_anchor,
22824 text.clone(),
22825 )
22826 })
22827 .collect::<Vec<_>>();
22828
22829 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22830}
22831
22832pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22833 match severity {
22834 lsp::DiagnosticSeverity::ERROR => colors.error,
22835 lsp::DiagnosticSeverity::WARNING => colors.warning,
22836 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22837 lsp::DiagnosticSeverity::HINT => colors.info,
22838 _ => colors.ignored,
22839 }
22840}
22841
22842pub fn styled_runs_for_code_label<'a>(
22843 label: &'a CodeLabel,
22844 syntax_theme: &'a theme::SyntaxTheme,
22845) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22846 let fade_out = HighlightStyle {
22847 fade_out: Some(0.35),
22848 ..Default::default()
22849 };
22850
22851 let mut prev_end = label.filter_range.end;
22852 label
22853 .runs
22854 .iter()
22855 .enumerate()
22856 .flat_map(move |(ix, (range, highlight_id))| {
22857 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22858 style
22859 } else {
22860 return Default::default();
22861 };
22862 let mut muted_style = style;
22863 muted_style.highlight(fade_out);
22864
22865 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22866 if range.start >= label.filter_range.end {
22867 if range.start > prev_end {
22868 runs.push((prev_end..range.start, fade_out));
22869 }
22870 runs.push((range.clone(), muted_style));
22871 } else if range.end <= label.filter_range.end {
22872 runs.push((range.clone(), style));
22873 } else {
22874 runs.push((range.start..label.filter_range.end, style));
22875 runs.push((label.filter_range.end..range.end, muted_style));
22876 }
22877 prev_end = cmp::max(prev_end, range.end);
22878
22879 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22880 runs.push((prev_end..label.text.len(), fade_out));
22881 }
22882
22883 runs
22884 })
22885}
22886
22887pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22888 let mut prev_index = 0;
22889 let mut prev_codepoint: Option<char> = None;
22890 text.char_indices()
22891 .chain([(text.len(), '\0')])
22892 .filter_map(move |(index, codepoint)| {
22893 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22894 let is_boundary = index == text.len()
22895 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22896 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22897 if is_boundary {
22898 let chunk = &text[prev_index..index];
22899 prev_index = index;
22900 Some(chunk)
22901 } else {
22902 None
22903 }
22904 })
22905}
22906
22907pub trait RangeToAnchorExt: Sized {
22908 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22909
22910 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22911 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22912 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22913 }
22914}
22915
22916impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22917 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22918 let start_offset = self.start.to_offset(snapshot);
22919 let end_offset = self.end.to_offset(snapshot);
22920 if start_offset == end_offset {
22921 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22922 } else {
22923 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22924 }
22925 }
22926}
22927
22928pub trait RowExt {
22929 fn as_f32(&self) -> f32;
22930
22931 fn next_row(&self) -> Self;
22932
22933 fn previous_row(&self) -> Self;
22934
22935 fn minus(&self, other: Self) -> u32;
22936}
22937
22938impl RowExt for DisplayRow {
22939 fn as_f32(&self) -> f32 {
22940 self.0 as f32
22941 }
22942
22943 fn next_row(&self) -> Self {
22944 Self(self.0 + 1)
22945 }
22946
22947 fn previous_row(&self) -> Self {
22948 Self(self.0.saturating_sub(1))
22949 }
22950
22951 fn minus(&self, other: Self) -> u32 {
22952 self.0 - other.0
22953 }
22954}
22955
22956impl RowExt for MultiBufferRow {
22957 fn as_f32(&self) -> f32 {
22958 self.0 as f32
22959 }
22960
22961 fn next_row(&self) -> Self {
22962 Self(self.0 + 1)
22963 }
22964
22965 fn previous_row(&self) -> Self {
22966 Self(self.0.saturating_sub(1))
22967 }
22968
22969 fn minus(&self, other: Self) -> u32 {
22970 self.0 - other.0
22971 }
22972}
22973
22974trait RowRangeExt {
22975 type Row;
22976
22977 fn len(&self) -> usize;
22978
22979 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22980}
22981
22982impl RowRangeExt for Range<MultiBufferRow> {
22983 type Row = MultiBufferRow;
22984
22985 fn len(&self) -> usize {
22986 (self.end.0 - self.start.0) as usize
22987 }
22988
22989 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22990 (self.start.0..self.end.0).map(MultiBufferRow)
22991 }
22992}
22993
22994impl RowRangeExt for Range<DisplayRow> {
22995 type Row = DisplayRow;
22996
22997 fn len(&self) -> usize {
22998 (self.end.0 - self.start.0) as usize
22999 }
23000
23001 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
23002 (self.start.0..self.end.0).map(DisplayRow)
23003 }
23004}
23005
23006/// If select range has more than one line, we
23007/// just point the cursor to range.start.
23008fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
23009 if range.start.row == range.end.row {
23010 range
23011 } else {
23012 range.start..range.start
23013 }
23014}
23015pub struct KillRing(ClipboardItem);
23016impl Global for KillRing {}
23017
23018const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
23019
23020enum BreakpointPromptEditAction {
23021 Log,
23022 Condition,
23023 HitCondition,
23024}
23025
23026struct BreakpointPromptEditor {
23027 pub(crate) prompt: Entity<Editor>,
23028 editor: WeakEntity<Editor>,
23029 breakpoint_anchor: Anchor,
23030 breakpoint: Breakpoint,
23031 edit_action: BreakpointPromptEditAction,
23032 block_ids: HashSet<CustomBlockId>,
23033 editor_margins: Arc<Mutex<EditorMargins>>,
23034 _subscriptions: Vec<Subscription>,
23035}
23036
23037impl BreakpointPromptEditor {
23038 const MAX_LINES: u8 = 4;
23039
23040 fn new(
23041 editor: WeakEntity<Editor>,
23042 breakpoint_anchor: Anchor,
23043 breakpoint: Breakpoint,
23044 edit_action: BreakpointPromptEditAction,
23045 window: &mut Window,
23046 cx: &mut Context<Self>,
23047 ) -> Self {
23048 let base_text = match edit_action {
23049 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
23050 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
23051 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
23052 }
23053 .map(|msg| msg.to_string())
23054 .unwrap_or_default();
23055
23056 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
23057 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
23058
23059 let prompt = cx.new(|cx| {
23060 let mut prompt = Editor::new(
23061 EditorMode::AutoHeight {
23062 min_lines: 1,
23063 max_lines: Some(Self::MAX_LINES as usize),
23064 },
23065 buffer,
23066 None,
23067 window,
23068 cx,
23069 );
23070 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
23071 prompt.set_show_cursor_when_unfocused(false, cx);
23072 prompt.set_placeholder_text(
23073 match edit_action {
23074 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
23075 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
23076 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
23077 },
23078 cx,
23079 );
23080
23081 prompt
23082 });
23083
23084 Self {
23085 prompt,
23086 editor,
23087 breakpoint_anchor,
23088 breakpoint,
23089 edit_action,
23090 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
23091 block_ids: Default::default(),
23092 _subscriptions: vec![],
23093 }
23094 }
23095
23096 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
23097 self.block_ids.extend(block_ids)
23098 }
23099
23100 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
23101 if let Some(editor) = self.editor.upgrade() {
23102 let message = self
23103 .prompt
23104 .read(cx)
23105 .buffer
23106 .read(cx)
23107 .as_singleton()
23108 .expect("A multi buffer in breakpoint prompt isn't possible")
23109 .read(cx)
23110 .as_rope()
23111 .to_string();
23112
23113 editor.update(cx, |editor, cx| {
23114 editor.edit_breakpoint_at_anchor(
23115 self.breakpoint_anchor,
23116 self.breakpoint.clone(),
23117 match self.edit_action {
23118 BreakpointPromptEditAction::Log => {
23119 BreakpointEditAction::EditLogMessage(message.into())
23120 }
23121 BreakpointPromptEditAction::Condition => {
23122 BreakpointEditAction::EditCondition(message.into())
23123 }
23124 BreakpointPromptEditAction::HitCondition => {
23125 BreakpointEditAction::EditHitCondition(message.into())
23126 }
23127 },
23128 cx,
23129 );
23130
23131 editor.remove_blocks(self.block_ids.clone(), None, cx);
23132 cx.focus_self(window);
23133 });
23134 }
23135 }
23136
23137 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
23138 self.editor
23139 .update(cx, |editor, cx| {
23140 editor.remove_blocks(self.block_ids.clone(), None, cx);
23141 window.focus(&editor.focus_handle);
23142 })
23143 .log_err();
23144 }
23145
23146 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
23147 let settings = ThemeSettings::get_global(cx);
23148 let text_style = TextStyle {
23149 color: if self.prompt.read(cx).read_only(cx) {
23150 cx.theme().colors().text_disabled
23151 } else {
23152 cx.theme().colors().text
23153 },
23154 font_family: settings.buffer_font.family.clone(),
23155 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23156 font_size: settings.buffer_font_size(cx).into(),
23157 font_weight: settings.buffer_font.weight,
23158 line_height: relative(settings.buffer_line_height.value()),
23159 ..Default::default()
23160 };
23161 EditorElement::new(
23162 &self.prompt,
23163 EditorStyle {
23164 background: cx.theme().colors().editor_background,
23165 local_player: cx.theme().players().local(),
23166 text: text_style,
23167 ..Default::default()
23168 },
23169 )
23170 }
23171}
23172
23173impl Render for BreakpointPromptEditor {
23174 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23175 let editor_margins = *self.editor_margins.lock();
23176 let gutter_dimensions = editor_margins.gutter;
23177 h_flex()
23178 .key_context("Editor")
23179 .bg(cx.theme().colors().editor_background)
23180 .border_y_1()
23181 .border_color(cx.theme().status().info_border)
23182 .size_full()
23183 .py(window.line_height() / 2.5)
23184 .on_action(cx.listener(Self::confirm))
23185 .on_action(cx.listener(Self::cancel))
23186 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
23187 .child(div().flex_1().child(self.render_prompt_editor(cx)))
23188 }
23189}
23190
23191impl Focusable for BreakpointPromptEditor {
23192 fn focus_handle(&self, cx: &App) -> FocusHandle {
23193 self.prompt.focus_handle(cx)
23194 }
23195}
23196
23197fn all_edits_insertions_or_deletions(
23198 edits: &Vec<(Range<Anchor>, String)>,
23199 snapshot: &MultiBufferSnapshot,
23200) -> bool {
23201 let mut all_insertions = true;
23202 let mut all_deletions = true;
23203
23204 for (range, new_text) in edits.iter() {
23205 let range_is_empty = range.to_offset(&snapshot).is_empty();
23206 let text_is_empty = new_text.is_empty();
23207
23208 if range_is_empty != text_is_empty {
23209 if range_is_empty {
23210 all_deletions = false;
23211 } else {
23212 all_insertions = false;
23213 }
23214 } else {
23215 return false;
23216 }
23217
23218 if !all_insertions && !all_deletions {
23219 return false;
23220 }
23221 }
23222 all_insertions || all_deletions
23223}
23224
23225struct MissingEditPredictionKeybindingTooltip;
23226
23227impl Render for MissingEditPredictionKeybindingTooltip {
23228 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23229 ui::tooltip_container(window, cx, |container, _, cx| {
23230 container
23231 .flex_shrink_0()
23232 .max_w_80()
23233 .min_h(rems_from_px(124.))
23234 .justify_between()
23235 .child(
23236 v_flex()
23237 .flex_1()
23238 .text_ui_sm(cx)
23239 .child(Label::new("Conflict with Accept Keybinding"))
23240 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
23241 )
23242 .child(
23243 h_flex()
23244 .pb_1()
23245 .gap_1()
23246 .items_end()
23247 .w_full()
23248 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
23249 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
23250 }))
23251 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
23252 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
23253 })),
23254 )
23255 })
23256 }
23257}
23258
23259#[derive(Debug, Clone, Copy, PartialEq)]
23260pub struct LineHighlight {
23261 pub background: Background,
23262 pub border: Option<gpui::Hsla>,
23263 pub include_gutter: bool,
23264 pub type_id: Option<TypeId>,
23265}
23266
23267struct LineManipulationResult {
23268 pub new_text: String,
23269 pub line_count_before: usize,
23270 pub line_count_after: usize,
23271}
23272
23273fn render_diff_hunk_controls(
23274 row: u32,
23275 status: &DiffHunkStatus,
23276 hunk_range: Range<Anchor>,
23277 is_created_file: bool,
23278 line_height: Pixels,
23279 editor: &Entity<Editor>,
23280 _window: &mut Window,
23281 cx: &mut App,
23282) -> AnyElement {
23283 h_flex()
23284 .h(line_height)
23285 .mr_1()
23286 .gap_1()
23287 .px_0p5()
23288 .pb_1()
23289 .border_x_1()
23290 .border_b_1()
23291 .border_color(cx.theme().colors().border_variant)
23292 .rounded_b_lg()
23293 .bg(cx.theme().colors().editor_background)
23294 .gap_1()
23295 .block_mouse_except_scroll()
23296 .shadow_md()
23297 .child(if status.has_secondary_hunk() {
23298 Button::new(("stage", row as u64), "Stage")
23299 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23300 .tooltip({
23301 let focus_handle = editor.focus_handle(cx);
23302 move |window, cx| {
23303 Tooltip::for_action_in(
23304 "Stage Hunk",
23305 &::git::ToggleStaged,
23306 &focus_handle,
23307 window,
23308 cx,
23309 )
23310 }
23311 })
23312 .on_click({
23313 let editor = editor.clone();
23314 move |_event, _window, cx| {
23315 editor.update(cx, |editor, cx| {
23316 editor.stage_or_unstage_diff_hunks(
23317 true,
23318 vec![hunk_range.start..hunk_range.start],
23319 cx,
23320 );
23321 });
23322 }
23323 })
23324 } else {
23325 Button::new(("unstage", row as u64), "Unstage")
23326 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
23327 .tooltip({
23328 let focus_handle = editor.focus_handle(cx);
23329 move |window, cx| {
23330 Tooltip::for_action_in(
23331 "Unstage Hunk",
23332 &::git::ToggleStaged,
23333 &focus_handle,
23334 window,
23335 cx,
23336 )
23337 }
23338 })
23339 .on_click({
23340 let editor = editor.clone();
23341 move |_event, _window, cx| {
23342 editor.update(cx, |editor, cx| {
23343 editor.stage_or_unstage_diff_hunks(
23344 false,
23345 vec![hunk_range.start..hunk_range.start],
23346 cx,
23347 );
23348 });
23349 }
23350 })
23351 })
23352 .child(
23353 Button::new(("restore", row as u64), "Restore")
23354 .tooltip({
23355 let focus_handle = editor.focus_handle(cx);
23356 move |window, cx| {
23357 Tooltip::for_action_in(
23358 "Restore Hunk",
23359 &::git::Restore,
23360 &focus_handle,
23361 window,
23362 cx,
23363 )
23364 }
23365 })
23366 .on_click({
23367 let editor = editor.clone();
23368 move |_event, window, cx| {
23369 editor.update(cx, |editor, cx| {
23370 let snapshot = editor.snapshot(window, cx);
23371 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23372 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23373 });
23374 }
23375 })
23376 .disabled(is_created_file),
23377 )
23378 .when(
23379 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23380 |el| {
23381 el.child(
23382 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23383 .shape(IconButtonShape::Square)
23384 .icon_size(IconSize::Small)
23385 // .disabled(!has_multiple_hunks)
23386 .tooltip({
23387 let focus_handle = editor.focus_handle(cx);
23388 move |window, cx| {
23389 Tooltip::for_action_in(
23390 "Next Hunk",
23391 &GoToHunk,
23392 &focus_handle,
23393 window,
23394 cx,
23395 )
23396 }
23397 })
23398 .on_click({
23399 let editor = editor.clone();
23400 move |_event, window, cx| {
23401 editor.update(cx, |editor, cx| {
23402 let snapshot = editor.snapshot(window, cx);
23403 let position =
23404 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23405 editor.go_to_hunk_before_or_after_position(
23406 &snapshot,
23407 position,
23408 Direction::Next,
23409 window,
23410 cx,
23411 );
23412 editor.expand_selected_diff_hunks(cx);
23413 });
23414 }
23415 }),
23416 )
23417 .child(
23418 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23419 .shape(IconButtonShape::Square)
23420 .icon_size(IconSize::Small)
23421 // .disabled(!has_multiple_hunks)
23422 .tooltip({
23423 let focus_handle = editor.focus_handle(cx);
23424 move |window, cx| {
23425 Tooltip::for_action_in(
23426 "Previous Hunk",
23427 &GoToPreviousHunk,
23428 &focus_handle,
23429 window,
23430 cx,
23431 )
23432 }
23433 })
23434 .on_click({
23435 let editor = editor.clone();
23436 move |_event, window, cx| {
23437 editor.update(cx, |editor, cx| {
23438 let snapshot = editor.snapshot(window, cx);
23439 let point =
23440 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23441 editor.go_to_hunk_before_or_after_position(
23442 &snapshot,
23443 point,
23444 Direction::Prev,
23445 window,
23446 cx,
23447 );
23448 editor.expand_selected_diff_hunks(cx);
23449 });
23450 }
23451 }),
23452 )
23453 },
23454 )
23455 .into_any_element()
23456}