1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::StringMatchCandidate;
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use smallvec::smallvec;
142use std::{cell::OnceCell, iter::Peekable, ops::Not};
143use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
144
145pub use lsp::CompletionContext;
146use lsp::{
147 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
148 LanguageServerId, LanguageServerName,
149};
150
151use language::BufferSnapshot;
152pub use lsp_ext::lsp_tasks;
153use movement::TextLayoutDetails;
154pub use multi_buffer::{
155 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
156 RowInfo, ToOffset, ToPoint,
157};
158use multi_buffer::{
159 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
160 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
161};
162use parking_lot::Mutex;
163use project::{
164 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
165 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
166 TaskSourceKind,
167 debugger::breakpoint_store::Breakpoint,
168 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
169 project_settings::{GitGutterSetting, ProjectSettings},
170};
171use rand::prelude::*;
172use rpc::{ErrorExt, proto::*};
173use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
174use selections_collection::{
175 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
176};
177use serde::{Deserialize, Serialize};
178use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
179use smallvec::SmallVec;
180use snippet::Snippet;
181use std::sync::Arc;
182use std::{
183 any::TypeId,
184 borrow::Cow,
185 cell::RefCell,
186 cmp::{self, Ordering, Reverse},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 time::{Duration, Instant},
193};
194pub use sum_tree::Bias;
195use sum_tree::TreeMap;
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
197use theme::{
198 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
199 observe_buffer_font_size_adjustment,
200};
201use ui::{
202 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
203 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
204};
205use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
206use workspace::{
207 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
208 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
209 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
210 item::{ItemHandle, PreviewTabsSettings},
211 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
212 searchable::SearchEvent,
213};
214
215use crate::hover_links::{find_url, find_url_from_range};
216use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233
234pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
235pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
236pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
253 alt: true,
254 shift: true,
255 control: false,
256 platform: false,
257 function: false,
258};
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 Hint(usize),
280 DebuggerValue(usize),
281}
282
283impl InlayId {
284 fn id(&self) -> usize {
285 match self {
286 Self::InlineCompletion(id) => *id,
287 Self::Hint(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 }
290 }
291}
292
293pub enum ActiveDebugLine {}
294pub enum DebugStackFrameLine {}
295enum DocumentHighlightRead {}
296enum DocumentHighlightWrite {}
297enum InputComposition {}
298enum SelectedTextHighlight {}
299
300pub enum ConflictsOuter {}
301pub enum ConflictsOurs {}
302pub enum ConflictsTheirs {}
303pub enum ConflictsOursMarker {}
304pub enum ConflictsTheirsMarker {}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 },
330}
331
332pub enum HideMouseCursorOrigin {
333 TypingAction,
334 MovementAction,
335}
336
337pub fn init_settings(cx: &mut App) {
338 EditorSettings::register(cx);
339}
340
341pub fn init(cx: &mut App) {
342 init_settings(cx);
343
344 cx.set_global(GlobalBlameRenderer(Arc::new(())));
345
346 workspace::register_project_item::<Editor>(cx);
347 workspace::FollowableViewRegistry::register::<Editor>(cx);
348 workspace::register_serializable_item::<Editor>(cx);
349
350 cx.observe_new(
351 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
352 workspace.register_action(Editor::new_file);
353 workspace.register_action(Editor::new_file_vertical);
354 workspace.register_action(Editor::new_file_horizontal);
355 workspace.register_action(Editor::cancel_language_server_work);
356 },
357 )
358 .detach();
359
360 cx.on_action(move |_: &workspace::NewFile, cx| {
361 let app_state = workspace::AppState::global(cx);
362 if let Some(app_state) = app_state.upgrade() {
363 workspace::open_new(
364 Default::default(),
365 app_state,
366 cx,
367 |workspace, window, cx| {
368 Editor::new_file(workspace, &Default::default(), window, cx)
369 },
370 )
371 .detach();
372 }
373 });
374 cx.on_action(move |_: &workspace::NewWindow, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 cx.activate(true);
383 Editor::new_file(workspace, &Default::default(), window, cx)
384 },
385 )
386 .detach();
387 }
388 });
389}
390
391pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
392 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
393}
394
395pub trait DiagnosticRenderer {
396 fn render_group(
397 &self,
398 diagnostic_group: Vec<DiagnosticEntry<Point>>,
399 buffer_id: BufferId,
400 snapshot: EditorSnapshot,
401 editor: WeakEntity<Editor>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug)]
466pub enum SelectMode {
467 Character,
468 Word(Range<Anchor>),
469 Line(Range<Anchor>),
470 All,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine {
476 auto_width: bool,
477 },
478 AutoHeight {
479 max_lines: usize,
480 },
481 Full {
482 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
483 scale_ui_elements_with_buffer_font_size: bool,
484 /// When set to `true`, the editor will render a background for the active line.
485 show_active_line_background: bool,
486 /// When set to `true`, the editor's height will be determined by its content.
487 sized_by_content: bool,
488 },
489 Minimap {
490 parent: WeakEntity<Editor>,
491 },
492}
493
494impl EditorMode {
495 pub fn full() -> Self {
496 Self::Full {
497 scale_ui_elements_with_buffer_font_size: true,
498 show_active_line_background: true,
499 sized_by_content: false,
500 }
501 }
502
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 fn is_minimap(&self) -> bool {
508 matches!(self, Self::Minimap { .. })
509 }
510}
511
512#[derive(Copy, Clone, Debug)]
513pub enum SoftWrap {
514 /// Prefer not to wrap at all.
515 ///
516 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
517 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
518 GitDiff,
519 /// Prefer a single line generally, unless an overly long line is encountered.
520 None,
521 /// Soft wrap lines that exceed the editor width.
522 EditorWidth,
523 /// Soft wrap lines at the preferred line length.
524 Column(u32),
525 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
526 Bounded(u32),
527}
528
529#[derive(Clone)]
530pub struct EditorStyle {
531 pub background: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub inline_completion_styles: InlineCompletionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 local_player: PlayerColor::default(),
548 text: TextStyle::default(),
549 scrollbar_width: Pixels::default(),
550 syntax: Default::default(),
551 // HACK: Status colors don't have a real default.
552 // We should look into removing the status colors from the editor
553 // style and retrieve them directly from the theme.
554 status: StatusColors::dark(),
555 inlay_hints_style: HighlightStyle::default(),
556 inline_completion_styles: InlineCompletionStyles {
557 insertion: HighlightStyle::default(),
558 whitespace: HighlightStyle::default(),
559 },
560 unnecessary_code_fade: Default::default(),
561 show_underlines: true,
562 }
563 }
564}
565
566pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
567 let show_background = language_settings::language_settings(None, None, cx)
568 .inlay_hints
569 .show_background;
570
571 HighlightStyle {
572 color: Some(cx.theme().status().hint),
573 background_color: show_background.then(|| cx.theme().status().hint_background),
574 ..HighlightStyle::default()
575 }
576}
577
578pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
579 InlineCompletionStyles {
580 insertion: HighlightStyle {
581 color: Some(cx.theme().status().predictive),
582 ..HighlightStyle::default()
583 },
584 whitespace: HighlightStyle {
585 background_color: Some(cx.theme().status().created_background),
586 ..HighlightStyle::default()
587 },
588 }
589}
590
591type CompletionId = usize;
592
593pub(crate) enum EditDisplayMode {
594 TabAccept,
595 DiffPopover,
596 Inline,
597}
598
599enum InlineCompletion {
600 Edit {
601 edits: Vec<(Range<Anchor>, String)>,
602 edit_preview: Option<EditPreview>,
603 display_mode: EditDisplayMode,
604 snapshot: BufferSnapshot,
605 },
606 Move {
607 target: Anchor,
608 snapshot: BufferSnapshot,
609 },
610}
611
612struct InlineCompletionState {
613 inlay_ids: Vec<InlayId>,
614 completion: InlineCompletion,
615 completion_id: Option<SharedString>,
616 invalidation_range: Range<Anchor>,
617}
618
619enum EditPredictionSettings {
620 Disabled,
621 Enabled {
622 show_in_menu: bool,
623 preview_requires_modifier: bool,
624 },
625}
626
627enum InlineCompletionHighlight {}
628
629#[derive(Debug, Clone)]
630struct InlineDiagnostic {
631 message: SharedString,
632 group_id: usize,
633 is_primary: bool,
634 start: Point,
635 severity: lsp::DiagnosticSeverity,
636}
637
638pub enum MenuInlineCompletionsPolicy {
639 Never,
640 ByProvider,
641}
642
643pub enum EditPredictionPreview {
644 /// Modifier is not pressed
645 Inactive { released_too_fast: bool },
646 /// Modifier pressed
647 Active {
648 since: Instant,
649 previous_scroll_position: Option<ScrollAnchor>,
650 },
651}
652
653impl EditPredictionPreview {
654 pub fn released_too_fast(&self) -> bool {
655 match self {
656 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
657 EditPredictionPreview::Active { .. } => false,
658 }
659 }
660
661 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
662 if let EditPredictionPreview::Active {
663 previous_scroll_position,
664 ..
665 } = self
666 {
667 *previous_scroll_position = scroll_position;
668 }
669 }
670}
671
672pub struct ContextMenuOptions {
673 pub min_entries_visible: usize,
674 pub max_entries_visible: usize,
675 pub placement: Option<ContextMenuPlacement>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq)]
679pub enum ContextMenuPlacement {
680 Above,
681 Below,
682}
683
684#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
685struct EditorActionId(usize);
686
687impl EditorActionId {
688 pub fn post_inc(&mut self) -> Self {
689 let answer = self.0;
690
691 *self = Self(answer + 1);
692
693 Self(answer)
694 }
695}
696
697// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
698// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
699
700type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
701type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
702
703#[derive(Default)]
704struct ScrollbarMarkerState {
705 scrollbar_size: Size<Pixels>,
706 dirty: bool,
707 markers: Arc<[PaintQuad]>,
708 pending_refresh: Option<Task<Result<()>>>,
709}
710
711impl ScrollbarMarkerState {
712 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
713 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
714 }
715}
716
717#[derive(Clone, Copy, PartialEq, Eq)]
718pub enum MinimapVisibility {
719 Disabled,
720 Enabled(bool),
721}
722
723impl MinimapVisibility {
724 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
725 if mode.is_full() {
726 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
727 } else {
728 Self::Disabled
729 }
730 }
731
732 fn disabled(&self) -> bool {
733 match *self {
734 Self::Disabled => true,
735 _ => false,
736 }
737 }
738
739 fn visible(&self) -> bool {
740 match *self {
741 Self::Enabled(visible) => visible,
742 _ => false,
743 }
744 }
745
746 fn toggle_visibility(&self) -> Self {
747 match *self {
748 Self::Enabled(visible) => Self::Enabled(!visible),
749 Self::Disabled => Self::Disabled,
750 }
751 }
752}
753
754#[derive(Clone, Debug)]
755struct RunnableTasks {
756 templates: Vec<(TaskSourceKind, TaskTemplate)>,
757 offset: multi_buffer::Anchor,
758 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
759 column: u32,
760 // Values of all named captures, including those starting with '_'
761 extra_variables: HashMap<String, String>,
762 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
763 context_range: Range<BufferOffset>,
764}
765
766impl RunnableTasks {
767 fn resolve<'a>(
768 &'a self,
769 cx: &'a task::TaskContext,
770 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
771 self.templates.iter().filter_map(|(kind, template)| {
772 template
773 .resolve_task(&kind.to_id_base(), cx)
774 .map(|task| (kind.clone(), task))
775 })
776 }
777}
778
779#[derive(Clone)]
780struct ResolvedTasks {
781 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
782 position: Anchor,
783}
784
785#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
786struct BufferOffset(usize);
787
788// Addons allow storing per-editor state in other crates (e.g. Vim)
789pub trait Addon: 'static {
790 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
791
792 fn render_buffer_header_controls(
793 &self,
794 _: &ExcerptInfo,
795 _: &Window,
796 _: &App,
797 ) -> Option<AnyElement> {
798 None
799 }
800
801 fn to_any(&self) -> &dyn std::any::Any;
802
803 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
804 None
805 }
806}
807
808/// A set of caret positions, registered when the editor was edited.
809pub struct ChangeList {
810 changes: Vec<Vec<Anchor>>,
811 /// Currently "selected" change.
812 position: Option<usize>,
813}
814
815impl ChangeList {
816 pub fn new() -> Self {
817 Self {
818 changes: Vec::new(),
819 position: None,
820 }
821 }
822
823 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
824 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
825 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
826 if self.changes.is_empty() {
827 return None;
828 }
829
830 let prev = self.position.unwrap_or(self.changes.len());
831 let next = if direction == Direction::Prev {
832 prev.saturating_sub(count)
833 } else {
834 (prev + count).min(self.changes.len() - 1)
835 };
836 self.position = Some(next);
837 self.changes.get(next).map(|anchors| anchors.as_slice())
838 }
839
840 /// Adds a new change to the list, resetting the change list position.
841 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
842 self.position.take();
843 if pop_state {
844 self.changes.pop();
845 }
846 self.changes.push(new_positions.clone());
847 }
848
849 pub fn last(&self) -> Option<&[Anchor]> {
850 self.changes.last().map(|anchors| anchors.as_slice())
851 }
852}
853
854#[derive(Clone)]
855struct InlineBlamePopoverState {
856 scroll_handle: ScrollHandle,
857 commit_message: Option<ParsedCommitMessage>,
858 markdown: Entity<Markdown>,
859}
860
861struct InlineBlamePopover {
862 position: gpui::Point<Pixels>,
863 show_task: Option<Task<()>>,
864 hide_task: Option<Task<()>>,
865 popover_bounds: Option<Bounds<Pixels>>,
866 popover_state: InlineBlamePopoverState,
867}
868
869/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
870/// a breakpoint on them.
871#[derive(Clone, Copy, Debug)]
872struct PhantomBreakpointIndicator {
873 display_row: DisplayRow,
874 /// There's a small debounce between hovering over the line and showing the indicator.
875 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
876 is_active: bool,
877 collides_with_existing_breakpoint: bool,
878}
879/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
880///
881/// See the [module level documentation](self) for more information.
882pub struct Editor {
883 focus_handle: FocusHandle,
884 last_focused_descendant: Option<WeakFocusHandle>,
885 /// The text buffer being edited
886 buffer: Entity<MultiBuffer>,
887 /// Map of how text in the buffer should be displayed.
888 /// Handles soft wraps, folds, fake inlay text insertions, etc.
889 pub display_map: Entity<DisplayMap>,
890 pub selections: SelectionsCollection,
891 pub scroll_manager: ScrollManager,
892 /// When inline assist editors are linked, they all render cursors because
893 /// typing enters text into each of them, even the ones that aren't focused.
894 pub(crate) show_cursor_when_unfocused: bool,
895 columnar_selection_tail: Option<Anchor>,
896 add_selections_state: Option<AddSelectionsState>,
897 select_next_state: Option<SelectNextState>,
898 select_prev_state: Option<SelectNextState>,
899 selection_history: SelectionHistory,
900 autoclose_regions: Vec<AutocloseRegion>,
901 snippet_stack: InvalidationStack<SnippetState>,
902 select_syntax_node_history: SelectSyntaxNodeHistory,
903 ime_transaction: Option<TransactionId>,
904 pub diagnostics_max_severity: DiagnosticSeverity,
905 active_diagnostics: ActiveDiagnostic,
906 show_inline_diagnostics: bool,
907 inline_diagnostics_update: Task<()>,
908 inline_diagnostics_enabled: bool,
909 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
910 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
911 hard_wrap: Option<usize>,
912
913 // TODO: make this a access method
914 pub project: Option<Entity<Project>>,
915 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
916 completion_provider: Option<Box<dyn CompletionProvider>>,
917 collaboration_hub: Option<Box<dyn CollaborationHub>>,
918 blink_manager: Entity<BlinkManager>,
919 show_cursor_names: bool,
920 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
921 pub show_local_selections: bool,
922 mode: EditorMode,
923 show_breadcrumbs: bool,
924 show_gutter: bool,
925 show_scrollbars: bool,
926 minimap_visibility: MinimapVisibility,
927 disable_expand_excerpt_buttons: bool,
928 show_line_numbers: Option<bool>,
929 use_relative_line_numbers: Option<bool>,
930 show_git_diff_gutter: Option<bool>,
931 show_code_actions: Option<bool>,
932 show_runnables: Option<bool>,
933 show_breakpoints: Option<bool>,
934 show_wrap_guides: Option<bool>,
935 show_indent_guides: Option<bool>,
936 placeholder_text: Option<Arc<str>>,
937 highlight_order: usize,
938 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
939 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
940 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
941 scrollbar_marker_state: ScrollbarMarkerState,
942 active_indent_guides_state: ActiveIndentGuidesState,
943 nav_history: Option<ItemNavHistory>,
944 context_menu: RefCell<Option<CodeContextMenu>>,
945 context_menu_options: Option<ContextMenuOptions>,
946 mouse_context_menu: Option<MouseContextMenu>,
947 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
948 inline_blame_popover: Option<InlineBlamePopover>,
949 signature_help_state: SignatureHelpState,
950 auto_signature_help: Option<bool>,
951 find_all_references_task_sources: Vec<Anchor>,
952 next_completion_id: CompletionId,
953 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
954 code_actions_task: Option<Task<Result<()>>>,
955 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
957 document_highlights_task: Option<Task<()>>,
958 linked_editing_range_task: Option<Task<Option<()>>>,
959 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
960 pending_rename: Option<RenameState>,
961 searchable: bool,
962 cursor_shape: CursorShape,
963 current_line_highlight: Option<CurrentLineHighlight>,
964 collapse_matches: bool,
965 autoindent_mode: Option<AutoindentMode>,
966 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
967 input_enabled: bool,
968 use_modal_editing: bool,
969 read_only: bool,
970 leader_id: Option<CollaboratorId>,
971 remote_id: Option<ViewId>,
972 pub hover_state: HoverState,
973 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
974 gutter_hovered: bool,
975 hovered_link_state: Option<HoveredLinkState>,
976 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
977 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
978 active_inline_completion: Option<InlineCompletionState>,
979 /// Used to prevent flickering as the user types while the menu is open
980 stale_inline_completion_in_menu: Option<InlineCompletionState>,
981 edit_prediction_settings: EditPredictionSettings,
982 inline_completions_hidden_for_vim_mode: bool,
983 show_inline_completions_override: Option<bool>,
984 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
985 edit_prediction_preview: EditPredictionPreview,
986 edit_prediction_indent_conflict: bool,
987 edit_prediction_requires_modifier_in_indent_conflict: bool,
988 inlay_hint_cache: InlayHintCache,
989 next_inlay_id: usize,
990 _subscriptions: Vec<Subscription>,
991 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
992 gutter_dimensions: GutterDimensions,
993 style: Option<EditorStyle>,
994 text_style_refinement: Option<TextStyleRefinement>,
995 next_editor_action_id: EditorActionId,
996 editor_actions:
997 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
998 use_autoclose: bool,
999 use_auto_surround: bool,
1000 auto_replace_emoji_shortcode: bool,
1001 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1002 show_git_blame_gutter: bool,
1003 show_git_blame_inline: bool,
1004 show_git_blame_inline_delay_task: Option<Task<()>>,
1005 git_blame_inline_enabled: bool,
1006 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1007 serialize_dirty_buffers: bool,
1008 show_selection_menu: Option<bool>,
1009 blame: Option<Entity<GitBlame>>,
1010 blame_subscription: Option<Subscription>,
1011 custom_context_menu: Option<
1012 Box<
1013 dyn 'static
1014 + Fn(
1015 &mut Self,
1016 DisplayPoint,
1017 &mut Window,
1018 &mut Context<Self>,
1019 ) -> Option<Entity<ui::ContextMenu>>,
1020 >,
1021 >,
1022 last_bounds: Option<Bounds<Pixels>>,
1023 last_position_map: Option<Rc<PositionMap>>,
1024 expect_bounds_change: Option<Bounds<Pixels>>,
1025 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1026 tasks_update_task: Option<Task<()>>,
1027 breakpoint_store: Option<Entity<BreakpointStore>>,
1028 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1029 in_project_search: bool,
1030 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1031 breadcrumb_header: Option<String>,
1032 focused_block: Option<FocusedBlock>,
1033 next_scroll_position: NextScrollCursorCenterTopBottom,
1034 addons: HashMap<TypeId, Box<dyn Addon>>,
1035 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1036 load_diff_task: Option<Shared<Task<()>>>,
1037 /// Whether we are temporarily displaying a diff other than git's
1038 temporary_diff_override: bool,
1039 selection_mark_mode: bool,
1040 toggle_fold_multiple_buffers: Task<()>,
1041 _scroll_cursor_center_top_bottom_task: Task<()>,
1042 serialize_selections: Task<()>,
1043 serialize_folds: Task<()>,
1044 mouse_cursor_hidden: bool,
1045 minimap: Option<Entity<Self>>,
1046 hide_mouse_mode: HideMouseMode,
1047 pub change_list: ChangeList,
1048 inline_value_cache: InlineValueCache,
1049}
1050
1051#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1052enum NextScrollCursorCenterTopBottom {
1053 #[default]
1054 Center,
1055 Top,
1056 Bottom,
1057}
1058
1059impl NextScrollCursorCenterTopBottom {
1060 fn next(&self) -> Self {
1061 match self {
1062 Self::Center => Self::Top,
1063 Self::Top => Self::Bottom,
1064 Self::Bottom => Self::Center,
1065 }
1066 }
1067}
1068
1069#[derive(Clone)]
1070pub struct EditorSnapshot {
1071 pub mode: EditorMode,
1072 show_gutter: bool,
1073 show_line_numbers: Option<bool>,
1074 show_git_diff_gutter: Option<bool>,
1075 show_runnables: Option<bool>,
1076 show_breakpoints: Option<bool>,
1077 git_blame_gutter_max_author_length: Option<usize>,
1078 pub display_snapshot: DisplaySnapshot,
1079 pub placeholder_text: Option<Arc<str>>,
1080 is_focused: bool,
1081 scroll_anchor: ScrollAnchor,
1082 ongoing_scroll: OngoingScroll,
1083 current_line_highlight: CurrentLineHighlight,
1084 gutter_hovered: bool,
1085}
1086
1087#[derive(Default, Debug, Clone, Copy)]
1088pub struct GutterDimensions {
1089 pub left_padding: Pixels,
1090 pub right_padding: Pixels,
1091 pub width: Pixels,
1092 pub margin: Pixels,
1093 pub git_blame_entries_width: Option<Pixels>,
1094}
1095
1096impl GutterDimensions {
1097 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1098 Self {
1099 margin: Self::default_gutter_margin(font_id, font_size, cx),
1100 ..Default::default()
1101 }
1102 }
1103
1104 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1105 -cx.text_system().descent(font_id, font_size)
1106 }
1107 /// The full width of the space taken up by the gutter.
1108 pub fn full_width(&self) -> Pixels {
1109 self.margin + self.width
1110 }
1111
1112 /// The width of the space reserved for the fold indicators,
1113 /// use alongside 'justify_end' and `gutter_width` to
1114 /// right align content with the line numbers
1115 pub fn fold_area_width(&self) -> Pixels {
1116 self.margin + self.right_padding
1117 }
1118}
1119
1120#[derive(Debug)]
1121pub struct RemoteSelection {
1122 pub replica_id: ReplicaId,
1123 pub selection: Selection<Anchor>,
1124 pub cursor_shape: CursorShape,
1125 pub collaborator_id: CollaboratorId,
1126 pub line_mode: bool,
1127 pub user_name: Option<SharedString>,
1128 pub color: PlayerColor,
1129}
1130
1131#[derive(Clone, Debug)]
1132struct SelectionHistoryEntry {
1133 selections: Arc<[Selection<Anchor>]>,
1134 select_next_state: Option<SelectNextState>,
1135 select_prev_state: Option<SelectNextState>,
1136 add_selections_state: Option<AddSelectionsState>,
1137}
1138
1139enum SelectionHistoryMode {
1140 Normal,
1141 Undoing,
1142 Redoing,
1143}
1144
1145#[derive(Clone, PartialEq, Eq, Hash)]
1146struct HoveredCursor {
1147 replica_id: u16,
1148 selection_id: usize,
1149}
1150
1151impl Default for SelectionHistoryMode {
1152 fn default() -> Self {
1153 Self::Normal
1154 }
1155}
1156
1157#[derive(Default)]
1158struct SelectionHistory {
1159 #[allow(clippy::type_complexity)]
1160 selections_by_transaction:
1161 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1162 mode: SelectionHistoryMode,
1163 undo_stack: VecDeque<SelectionHistoryEntry>,
1164 redo_stack: VecDeque<SelectionHistoryEntry>,
1165}
1166
1167impl SelectionHistory {
1168 fn insert_transaction(
1169 &mut self,
1170 transaction_id: TransactionId,
1171 selections: Arc<[Selection<Anchor>]>,
1172 ) {
1173 self.selections_by_transaction
1174 .insert(transaction_id, (selections, None));
1175 }
1176
1177 #[allow(clippy::type_complexity)]
1178 fn transaction(
1179 &self,
1180 transaction_id: TransactionId,
1181 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1182 self.selections_by_transaction.get(&transaction_id)
1183 }
1184
1185 #[allow(clippy::type_complexity)]
1186 fn transaction_mut(
1187 &mut self,
1188 transaction_id: TransactionId,
1189 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1190 self.selections_by_transaction.get_mut(&transaction_id)
1191 }
1192
1193 fn push(&mut self, entry: SelectionHistoryEntry) {
1194 if !entry.selections.is_empty() {
1195 match self.mode {
1196 SelectionHistoryMode::Normal => {
1197 self.push_undo(entry);
1198 self.redo_stack.clear();
1199 }
1200 SelectionHistoryMode::Undoing => self.push_redo(entry),
1201 SelectionHistoryMode::Redoing => self.push_undo(entry),
1202 }
1203 }
1204 }
1205
1206 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1207 if self
1208 .undo_stack
1209 .back()
1210 .map_or(true, |e| e.selections != entry.selections)
1211 {
1212 self.undo_stack.push_back(entry);
1213 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1214 self.undo_stack.pop_front();
1215 }
1216 }
1217 }
1218
1219 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1220 if self
1221 .redo_stack
1222 .back()
1223 .map_or(true, |e| e.selections != entry.selections)
1224 {
1225 self.redo_stack.push_back(entry);
1226 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1227 self.redo_stack.pop_front();
1228 }
1229 }
1230 }
1231}
1232
1233#[derive(Clone, Copy)]
1234pub struct RowHighlightOptions {
1235 pub autoscroll: bool,
1236 pub include_gutter: bool,
1237}
1238
1239impl Default for RowHighlightOptions {
1240 fn default() -> Self {
1241 Self {
1242 autoscroll: Default::default(),
1243 include_gutter: true,
1244 }
1245 }
1246}
1247
1248struct RowHighlight {
1249 index: usize,
1250 range: Range<Anchor>,
1251 color: Hsla,
1252 options: RowHighlightOptions,
1253 type_id: TypeId,
1254}
1255
1256#[derive(Clone, Debug)]
1257struct AddSelectionsState {
1258 above: bool,
1259 stack: Vec<usize>,
1260}
1261
1262#[derive(Clone)]
1263struct SelectNextState {
1264 query: AhoCorasick,
1265 wordwise: bool,
1266 done: bool,
1267}
1268
1269impl std::fmt::Debug for SelectNextState {
1270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1271 f.debug_struct(std::any::type_name::<Self>())
1272 .field("wordwise", &self.wordwise)
1273 .field("done", &self.done)
1274 .finish()
1275 }
1276}
1277
1278#[derive(Debug)]
1279struct AutocloseRegion {
1280 selection_id: usize,
1281 range: Range<Anchor>,
1282 pair: BracketPair,
1283}
1284
1285#[derive(Debug)]
1286struct SnippetState {
1287 ranges: Vec<Vec<Range<Anchor>>>,
1288 active_index: usize,
1289 choices: Vec<Option<Vec<String>>>,
1290}
1291
1292#[doc(hidden)]
1293pub struct RenameState {
1294 pub range: Range<Anchor>,
1295 pub old_name: Arc<str>,
1296 pub editor: Entity<Editor>,
1297 block_id: CustomBlockId,
1298}
1299
1300struct InvalidationStack<T>(Vec<T>);
1301
1302struct RegisteredInlineCompletionProvider {
1303 provider: Arc<dyn InlineCompletionProviderHandle>,
1304 _subscription: Subscription,
1305}
1306
1307#[derive(Debug, PartialEq, Eq)]
1308pub struct ActiveDiagnosticGroup {
1309 pub active_range: Range<Anchor>,
1310 pub active_message: String,
1311 pub group_id: usize,
1312 pub blocks: HashSet<CustomBlockId>,
1313}
1314
1315#[derive(Debug, PartialEq, Eq)]
1316
1317pub(crate) enum ActiveDiagnostic {
1318 None,
1319 All,
1320 Group(ActiveDiagnosticGroup),
1321}
1322
1323#[derive(Serialize, Deserialize, Clone, Debug)]
1324pub struct ClipboardSelection {
1325 /// The number of bytes in this selection.
1326 pub len: usize,
1327 /// Whether this was a full-line selection.
1328 pub is_entire_line: bool,
1329 /// The indentation of the first line when this content was originally copied.
1330 pub first_line_indent: u32,
1331}
1332
1333// selections, scroll behavior, was newest selection reversed
1334type SelectSyntaxNodeHistoryState = (
1335 Box<[Selection<usize>]>,
1336 SelectSyntaxNodeScrollBehavior,
1337 bool,
1338);
1339
1340#[derive(Default)]
1341struct SelectSyntaxNodeHistory {
1342 stack: Vec<SelectSyntaxNodeHistoryState>,
1343 // disable temporarily to allow changing selections without losing the stack
1344 pub disable_clearing: bool,
1345}
1346
1347impl SelectSyntaxNodeHistory {
1348 pub fn try_clear(&mut self) {
1349 if !self.disable_clearing {
1350 self.stack.clear();
1351 }
1352 }
1353
1354 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1355 self.stack.push(selection);
1356 }
1357
1358 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1359 self.stack.pop()
1360 }
1361}
1362
1363enum SelectSyntaxNodeScrollBehavior {
1364 CursorTop,
1365 FitSelection,
1366 CursorBottom,
1367}
1368
1369#[derive(Debug)]
1370pub(crate) struct NavigationData {
1371 cursor_anchor: Anchor,
1372 cursor_position: Point,
1373 scroll_anchor: ScrollAnchor,
1374 scroll_top_row: u32,
1375}
1376
1377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1378pub enum GotoDefinitionKind {
1379 Symbol,
1380 Declaration,
1381 Type,
1382 Implementation,
1383}
1384
1385#[derive(Debug, Clone)]
1386enum InlayHintRefreshReason {
1387 ModifiersChanged(bool),
1388 Toggle(bool),
1389 SettingsChange(InlayHintSettings),
1390 NewLinesShown,
1391 BufferEdited(HashSet<Arc<Language>>),
1392 RefreshRequested,
1393 ExcerptsRemoved(Vec<ExcerptId>),
1394}
1395
1396impl InlayHintRefreshReason {
1397 fn description(&self) -> &'static str {
1398 match self {
1399 Self::ModifiersChanged(_) => "modifiers changed",
1400 Self::Toggle(_) => "toggle",
1401 Self::SettingsChange(_) => "settings change",
1402 Self::NewLinesShown => "new lines shown",
1403 Self::BufferEdited(_) => "buffer edited",
1404 Self::RefreshRequested => "refresh requested",
1405 Self::ExcerptsRemoved(_) => "excerpts removed",
1406 }
1407 }
1408}
1409
1410pub enum FormatTarget {
1411 Buffers,
1412 Ranges(Vec<Range<MultiBufferPoint>>),
1413}
1414
1415pub(crate) struct FocusedBlock {
1416 id: BlockId,
1417 focus_handle: WeakFocusHandle,
1418}
1419
1420#[derive(Clone)]
1421enum JumpData {
1422 MultiBufferRow {
1423 row: MultiBufferRow,
1424 line_offset_from_top: u32,
1425 },
1426 MultiBufferPoint {
1427 excerpt_id: ExcerptId,
1428 position: Point,
1429 anchor: text::Anchor,
1430 line_offset_from_top: u32,
1431 },
1432}
1433
1434pub enum MultibufferSelectionMode {
1435 First,
1436 All,
1437}
1438
1439#[derive(Clone, Copy, Debug, Default)]
1440pub struct RewrapOptions {
1441 pub override_language_settings: bool,
1442 pub preserve_existing_whitespace: bool,
1443}
1444
1445impl Editor {
1446 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1447 let buffer = cx.new(|cx| Buffer::local("", cx));
1448 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1449 Self::new(
1450 EditorMode::SingleLine { auto_width: false },
1451 buffer,
1452 None,
1453 window,
1454 cx,
1455 )
1456 }
1457
1458 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1459 let buffer = cx.new(|cx| Buffer::local("", cx));
1460 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1461 Self::new(EditorMode::full(), buffer, None, window, cx)
1462 }
1463
1464 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1465 let buffer = cx.new(|cx| Buffer::local("", cx));
1466 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1467 Self::new(
1468 EditorMode::SingleLine { auto_width: true },
1469 buffer,
1470 None,
1471 window,
1472 cx,
1473 )
1474 }
1475
1476 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1477 let buffer = cx.new(|cx| Buffer::local("", cx));
1478 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1479 Self::new(
1480 EditorMode::AutoHeight { max_lines },
1481 buffer,
1482 None,
1483 window,
1484 cx,
1485 )
1486 }
1487
1488 pub fn for_buffer(
1489 buffer: Entity<Buffer>,
1490 project: Option<Entity<Project>>,
1491 window: &mut Window,
1492 cx: &mut Context<Self>,
1493 ) -> Self {
1494 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1495 Self::new(EditorMode::full(), buffer, project, window, cx)
1496 }
1497
1498 pub fn for_multibuffer(
1499 buffer: Entity<MultiBuffer>,
1500 project: Option<Entity<Project>>,
1501 window: &mut Window,
1502 cx: &mut Context<Self>,
1503 ) -> Self {
1504 Self::new(EditorMode::full(), buffer, project, window, cx)
1505 }
1506
1507 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1508 let mut clone = Self::new(
1509 self.mode.clone(),
1510 self.buffer.clone(),
1511 self.project.clone(),
1512 window,
1513 cx,
1514 );
1515 self.display_map.update(cx, |display_map, cx| {
1516 let snapshot = display_map.snapshot(cx);
1517 clone.display_map.update(cx, |display_map, cx| {
1518 display_map.set_state(&snapshot, cx);
1519 });
1520 });
1521 clone.folds_did_change(cx);
1522 clone.selections.clone_state(&self.selections);
1523 clone.scroll_manager.clone_state(&self.scroll_manager);
1524 clone.searchable = self.searchable;
1525 clone.read_only = self.read_only;
1526 clone
1527 }
1528
1529 pub fn new(
1530 mode: EditorMode,
1531 buffer: Entity<MultiBuffer>,
1532 project: Option<Entity<Project>>,
1533 window: &mut Window,
1534 cx: &mut Context<Self>,
1535 ) -> Self {
1536 Editor::new_internal(mode, buffer, project, None, window, cx)
1537 }
1538
1539 fn new_internal(
1540 mode: EditorMode,
1541 buffer: Entity<MultiBuffer>,
1542 project: Option<Entity<Project>>,
1543 display_map: Option<Entity<DisplayMap>>,
1544 window: &mut Window,
1545 cx: &mut Context<Self>,
1546 ) -> Self {
1547 debug_assert!(
1548 display_map.is_none() || mode.is_minimap(),
1549 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1550 );
1551
1552 let full_mode = mode.is_full();
1553 let diagnostics_max_severity = if full_mode {
1554 EditorSettings::get_global(cx)
1555 .diagnostics_max_severity
1556 .unwrap_or(DiagnosticSeverity::Hint)
1557 } else {
1558 DiagnosticSeverity::Off
1559 };
1560 let style = window.text_style();
1561 let font_size = style.font_size.to_pixels(window.rem_size());
1562 let editor = cx.entity().downgrade();
1563 let fold_placeholder = FoldPlaceholder {
1564 constrain_width: true,
1565 render: Arc::new(move |fold_id, fold_range, cx| {
1566 let editor = editor.clone();
1567 div()
1568 .id(fold_id)
1569 .bg(cx.theme().colors().ghost_element_background)
1570 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1571 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1572 .rounded_xs()
1573 .size_full()
1574 .cursor_pointer()
1575 .child("⋯")
1576 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1577 .on_click(move |_, _window, cx| {
1578 editor
1579 .update(cx, |editor, cx| {
1580 editor.unfold_ranges(
1581 &[fold_range.start..fold_range.end],
1582 true,
1583 false,
1584 cx,
1585 );
1586 cx.stop_propagation();
1587 })
1588 .ok();
1589 })
1590 .into_any()
1591 }),
1592 merge_adjacent: true,
1593 ..FoldPlaceholder::default()
1594 };
1595 let display_map = display_map.unwrap_or_else(|| {
1596 cx.new(|cx| {
1597 DisplayMap::new(
1598 buffer.clone(),
1599 style.font(),
1600 font_size,
1601 None,
1602 FILE_HEADER_HEIGHT,
1603 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1604 fold_placeholder,
1605 diagnostics_max_severity,
1606 cx,
1607 )
1608 })
1609 });
1610
1611 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1612
1613 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1614
1615 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1616 .then(|| language_settings::SoftWrap::None);
1617
1618 let mut project_subscriptions = Vec::new();
1619 if mode.is_full() {
1620 if let Some(project) = project.as_ref() {
1621 project_subscriptions.push(cx.subscribe_in(
1622 project,
1623 window,
1624 |editor, _, event, window, cx| match event {
1625 project::Event::RefreshCodeLens => {
1626 // we always query lens with actions, without storing them, always refreshing them
1627 }
1628 project::Event::RefreshInlayHints => {
1629 editor
1630 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1631 }
1632 project::Event::SnippetEdit(id, snippet_edits) => {
1633 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1634 let focus_handle = editor.focus_handle(cx);
1635 if focus_handle.is_focused(window) {
1636 let snapshot = buffer.read(cx).snapshot();
1637 for (range, snippet) in snippet_edits {
1638 let editor_range =
1639 language::range_from_lsp(*range).to_offset(&snapshot);
1640 editor
1641 .insert_snippet(
1642 &[editor_range],
1643 snippet.clone(),
1644 window,
1645 cx,
1646 )
1647 .ok();
1648 }
1649 }
1650 }
1651 }
1652 _ => {}
1653 },
1654 ));
1655 if let Some(task_inventory) = project
1656 .read(cx)
1657 .task_store()
1658 .read(cx)
1659 .task_inventory()
1660 .cloned()
1661 {
1662 project_subscriptions.push(cx.observe_in(
1663 &task_inventory,
1664 window,
1665 |editor, _, window, cx| {
1666 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1667 },
1668 ));
1669 };
1670
1671 project_subscriptions.push(cx.subscribe_in(
1672 &project.read(cx).breakpoint_store(),
1673 window,
1674 |editor, _, event, window, cx| match event {
1675 BreakpointStoreEvent::ClearDebugLines => {
1676 editor.clear_row_highlights::<ActiveDebugLine>();
1677 editor.refresh_inline_values(cx);
1678 }
1679 BreakpointStoreEvent::SetDebugLine => {
1680 if editor.go_to_active_debug_line(window, cx) {
1681 cx.stop_propagation();
1682 }
1683
1684 editor.refresh_inline_values(cx);
1685 }
1686 _ => {}
1687 },
1688 ));
1689 }
1690 }
1691
1692 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1693
1694 let inlay_hint_settings =
1695 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1696 let focus_handle = cx.focus_handle();
1697 cx.on_focus(&focus_handle, window, Self::handle_focus)
1698 .detach();
1699 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1700 .detach();
1701 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1702 .detach();
1703 cx.on_blur(&focus_handle, window, Self::handle_blur)
1704 .detach();
1705
1706 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1707 Some(false)
1708 } else {
1709 None
1710 };
1711
1712 let breakpoint_store = match (&mode, project.as_ref()) {
1713 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1714 _ => None,
1715 };
1716
1717 let mut code_action_providers = Vec::new();
1718 let mut load_uncommitted_diff = None;
1719 if let Some(project) = project.clone() {
1720 load_uncommitted_diff = Some(
1721 update_uncommitted_diff_for_buffer(
1722 cx.entity(),
1723 &project,
1724 buffer.read(cx).all_buffers(),
1725 buffer.clone(),
1726 cx,
1727 )
1728 .shared(),
1729 );
1730 code_action_providers.push(Rc::new(project) as Rc<_>);
1731 }
1732
1733 let mut this = Self {
1734 focus_handle,
1735 show_cursor_when_unfocused: false,
1736 last_focused_descendant: None,
1737 buffer: buffer.clone(),
1738 display_map: display_map.clone(),
1739 selections,
1740 scroll_manager: ScrollManager::new(cx),
1741 columnar_selection_tail: None,
1742 add_selections_state: None,
1743 select_next_state: None,
1744 select_prev_state: None,
1745 selection_history: SelectionHistory::default(),
1746 autoclose_regions: Vec::new(),
1747 snippet_stack: InvalidationStack::default(),
1748 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1749 ime_transaction: None,
1750 active_diagnostics: ActiveDiagnostic::None,
1751 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1752 inline_diagnostics_update: Task::ready(()),
1753 inline_diagnostics: Vec::new(),
1754 soft_wrap_mode_override,
1755 diagnostics_max_severity,
1756 hard_wrap: None,
1757 completion_provider: project.clone().map(|project| Box::new(project) as _),
1758 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1759 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1760 project,
1761 blink_manager: blink_manager.clone(),
1762 show_local_selections: true,
1763 show_scrollbars: full_mode,
1764 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1765 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1766 show_gutter: mode.is_full(),
1767 show_line_numbers: None,
1768 use_relative_line_numbers: None,
1769 disable_expand_excerpt_buttons: false,
1770 show_git_diff_gutter: None,
1771 show_code_actions: None,
1772 show_runnables: None,
1773 show_breakpoints: None,
1774 show_wrap_guides: None,
1775 show_indent_guides,
1776 placeholder_text: None,
1777 highlight_order: 0,
1778 highlighted_rows: HashMap::default(),
1779 background_highlights: TreeMap::default(),
1780 gutter_highlights: TreeMap::default(),
1781 scrollbar_marker_state: ScrollbarMarkerState::default(),
1782 active_indent_guides_state: ActiveIndentGuidesState::default(),
1783 nav_history: None,
1784 context_menu: RefCell::new(None),
1785 context_menu_options: None,
1786 mouse_context_menu: None,
1787 completion_tasks: Vec::new(),
1788 inline_blame_popover: None,
1789 signature_help_state: SignatureHelpState::default(),
1790 auto_signature_help: None,
1791 find_all_references_task_sources: Vec::new(),
1792 next_completion_id: 0,
1793 next_inlay_id: 0,
1794 code_action_providers,
1795 available_code_actions: None,
1796 code_actions_task: None,
1797 quick_selection_highlight_task: None,
1798 debounced_selection_highlight_task: None,
1799 document_highlights_task: None,
1800 linked_editing_range_task: None,
1801 pending_rename: None,
1802 searchable: true,
1803 cursor_shape: EditorSettings::get_global(cx)
1804 .cursor_shape
1805 .unwrap_or_default(),
1806 current_line_highlight: None,
1807 autoindent_mode: Some(AutoindentMode::EachLine),
1808 collapse_matches: false,
1809 workspace: None,
1810 input_enabled: true,
1811 use_modal_editing: mode.is_full(),
1812 read_only: mode.is_minimap(),
1813 use_autoclose: true,
1814 use_auto_surround: true,
1815 auto_replace_emoji_shortcode: false,
1816 jsx_tag_auto_close_enabled_in_any_buffer: false,
1817 leader_id: None,
1818 remote_id: None,
1819 hover_state: HoverState::default(),
1820 pending_mouse_down: None,
1821 hovered_link_state: None,
1822 edit_prediction_provider: None,
1823 active_inline_completion: None,
1824 stale_inline_completion_in_menu: None,
1825 edit_prediction_preview: EditPredictionPreview::Inactive {
1826 released_too_fast: false,
1827 },
1828 inline_diagnostics_enabled: mode.is_full(),
1829 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1830 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1831
1832 gutter_hovered: false,
1833 pixel_position_of_newest_cursor: None,
1834 last_bounds: None,
1835 last_position_map: None,
1836 expect_bounds_change: None,
1837 gutter_dimensions: GutterDimensions::default(),
1838 style: None,
1839 show_cursor_names: false,
1840 hovered_cursors: HashMap::default(),
1841 next_editor_action_id: EditorActionId::default(),
1842 editor_actions: Rc::default(),
1843 inline_completions_hidden_for_vim_mode: false,
1844 show_inline_completions_override: None,
1845 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1846 edit_prediction_settings: EditPredictionSettings::Disabled,
1847 edit_prediction_indent_conflict: false,
1848 edit_prediction_requires_modifier_in_indent_conflict: true,
1849 custom_context_menu: None,
1850 show_git_blame_gutter: false,
1851 show_git_blame_inline: false,
1852 show_selection_menu: None,
1853 show_git_blame_inline_delay_task: None,
1854 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1855 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1856 serialize_dirty_buffers: !mode.is_minimap()
1857 && ProjectSettings::get_global(cx)
1858 .session
1859 .restore_unsaved_buffers,
1860 blame: None,
1861 blame_subscription: None,
1862 tasks: BTreeMap::default(),
1863
1864 breakpoint_store,
1865 gutter_breakpoint_indicator: (None, None),
1866 _subscriptions: vec![
1867 cx.observe(&buffer, Self::on_buffer_changed),
1868 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1869 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1870 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1871 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1872 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1873 cx.observe_window_activation(window, |editor, window, cx| {
1874 let active = window.is_window_active();
1875 editor.blink_manager.update(cx, |blink_manager, cx| {
1876 if active {
1877 blink_manager.enable(cx);
1878 } else {
1879 blink_manager.disable(cx);
1880 }
1881 });
1882 }),
1883 ],
1884 tasks_update_task: None,
1885 linked_edit_ranges: Default::default(),
1886 in_project_search: false,
1887 previous_search_ranges: None,
1888 breadcrumb_header: None,
1889 focused_block: None,
1890 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1891 addons: HashMap::default(),
1892 registered_buffers: HashMap::default(),
1893 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1894 selection_mark_mode: false,
1895 toggle_fold_multiple_buffers: Task::ready(()),
1896 serialize_selections: Task::ready(()),
1897 serialize_folds: Task::ready(()),
1898 text_style_refinement: None,
1899 load_diff_task: load_uncommitted_diff,
1900 temporary_diff_override: false,
1901 mouse_cursor_hidden: false,
1902 minimap: None,
1903 hide_mouse_mode: EditorSettings::get_global(cx)
1904 .hide_mouse
1905 .unwrap_or_default(),
1906 change_list: ChangeList::new(),
1907 mode,
1908 };
1909 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1910 this._subscriptions
1911 .push(cx.observe(breakpoints, |_, _, cx| {
1912 cx.notify();
1913 }));
1914 }
1915 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1916 this._subscriptions.extend(project_subscriptions);
1917
1918 this._subscriptions.push(cx.subscribe_in(
1919 &cx.entity(),
1920 window,
1921 |editor, _, e: &EditorEvent, window, cx| match e {
1922 EditorEvent::ScrollPositionChanged { local, .. } => {
1923 if *local {
1924 let new_anchor = editor.scroll_manager.anchor();
1925 let snapshot = editor.snapshot(window, cx);
1926 editor.update_restoration_data(cx, move |data| {
1927 data.scroll_position = (
1928 new_anchor.top_row(&snapshot.buffer_snapshot),
1929 new_anchor.offset,
1930 );
1931 });
1932 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1933 editor.inline_blame_popover.take();
1934 }
1935 }
1936 EditorEvent::Edited { .. } => {
1937 if !vim_enabled(cx) {
1938 let (map, selections) = editor.selections.all_adjusted_display(cx);
1939 let pop_state = editor
1940 .change_list
1941 .last()
1942 .map(|previous| {
1943 previous.len() == selections.len()
1944 && previous.iter().enumerate().all(|(ix, p)| {
1945 p.to_display_point(&map).row()
1946 == selections[ix].head().row()
1947 })
1948 })
1949 .unwrap_or(false);
1950 let new_positions = selections
1951 .into_iter()
1952 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1953 .collect();
1954 editor
1955 .change_list
1956 .push_to_change_list(pop_state, new_positions);
1957 }
1958 }
1959 _ => (),
1960 },
1961 ));
1962
1963 if let Some(dap_store) = this
1964 .project
1965 .as_ref()
1966 .map(|project| project.read(cx).dap_store())
1967 {
1968 let weak_editor = cx.weak_entity();
1969
1970 this._subscriptions
1971 .push(
1972 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1973 let session_entity = cx.entity();
1974 weak_editor
1975 .update(cx, |editor, cx| {
1976 editor._subscriptions.push(
1977 cx.subscribe(&session_entity, Self::on_debug_session_event),
1978 );
1979 })
1980 .ok();
1981 }),
1982 );
1983
1984 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1985 this._subscriptions
1986 .push(cx.subscribe(&session, Self::on_debug_session_event));
1987 }
1988 }
1989
1990 this.end_selection(window, cx);
1991 this.scroll_manager.show_scrollbars(window, cx);
1992 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1993
1994 if full_mode {
1995 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1996 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1997
1998 if this.git_blame_inline_enabled {
1999 this.start_git_blame_inline(false, window, cx);
2000 }
2001
2002 this.go_to_active_debug_line(window, cx);
2003
2004 if let Some(buffer) = buffer.read(cx).as_singleton() {
2005 if let Some(project) = this.project.as_ref() {
2006 let handle = project.update(cx, |project, cx| {
2007 project.register_buffer_with_language_servers(&buffer, cx)
2008 });
2009 this.registered_buffers
2010 .insert(buffer.read(cx).remote_id(), handle);
2011 }
2012 }
2013
2014 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2015 }
2016
2017 this.report_editor_event("Editor Opened", None, cx);
2018 this
2019 }
2020
2021 pub fn deploy_mouse_context_menu(
2022 &mut self,
2023 position: gpui::Point<Pixels>,
2024 context_menu: Entity<ContextMenu>,
2025 window: &mut Window,
2026 cx: &mut Context<Self>,
2027 ) {
2028 self.mouse_context_menu = Some(MouseContextMenu::new(
2029 self,
2030 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2031 context_menu,
2032 window,
2033 cx,
2034 ));
2035 }
2036
2037 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2038 self.mouse_context_menu
2039 .as_ref()
2040 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2041 }
2042
2043 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2044 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2045 }
2046
2047 fn key_context_internal(
2048 &self,
2049 has_active_edit_prediction: bool,
2050 window: &Window,
2051 cx: &App,
2052 ) -> KeyContext {
2053 let mut key_context = KeyContext::new_with_defaults();
2054 key_context.add("Editor");
2055 let mode = match self.mode {
2056 EditorMode::SingleLine { .. } => "single_line",
2057 EditorMode::AutoHeight { .. } => "auto_height",
2058 EditorMode::Minimap { .. } => "minimap",
2059 EditorMode::Full { .. } => "full",
2060 };
2061
2062 if EditorSettings::jupyter_enabled(cx) {
2063 key_context.add("jupyter");
2064 }
2065
2066 key_context.set("mode", mode);
2067 if self.pending_rename.is_some() {
2068 key_context.add("renaming");
2069 }
2070
2071 match self.context_menu.borrow().as_ref() {
2072 Some(CodeContextMenu::Completions(_)) => {
2073 key_context.add("menu");
2074 key_context.add("showing_completions");
2075 }
2076 Some(CodeContextMenu::CodeActions(_)) => {
2077 key_context.add("menu");
2078 key_context.add("showing_code_actions")
2079 }
2080 None => {}
2081 }
2082
2083 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2084 if !self.focus_handle(cx).contains_focused(window, cx)
2085 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2086 {
2087 for addon in self.addons.values() {
2088 addon.extend_key_context(&mut key_context, cx)
2089 }
2090 }
2091
2092 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2093 if let Some(extension) = singleton_buffer
2094 .read(cx)
2095 .file()
2096 .and_then(|file| file.path().extension()?.to_str())
2097 {
2098 key_context.set("extension", extension.to_string());
2099 }
2100 } else {
2101 key_context.add("multibuffer");
2102 }
2103
2104 if has_active_edit_prediction {
2105 if self.edit_prediction_in_conflict() {
2106 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2107 } else {
2108 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2109 key_context.add("copilot_suggestion");
2110 }
2111 }
2112
2113 if self.selection_mark_mode {
2114 key_context.add("selection_mode");
2115 }
2116
2117 key_context
2118 }
2119
2120 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2121 self.mouse_cursor_hidden = match origin {
2122 HideMouseCursorOrigin::TypingAction => {
2123 matches!(
2124 self.hide_mouse_mode,
2125 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2126 )
2127 }
2128 HideMouseCursorOrigin::MovementAction => {
2129 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2130 }
2131 };
2132 }
2133
2134 pub fn edit_prediction_in_conflict(&self) -> bool {
2135 if !self.show_edit_predictions_in_menu() {
2136 return false;
2137 }
2138
2139 let showing_completions = self
2140 .context_menu
2141 .borrow()
2142 .as_ref()
2143 .map_or(false, |context| {
2144 matches!(context, CodeContextMenu::Completions(_))
2145 });
2146
2147 showing_completions
2148 || self.edit_prediction_requires_modifier()
2149 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2150 // bindings to insert tab characters.
2151 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2152 }
2153
2154 pub fn accept_edit_prediction_keybind(
2155 &self,
2156 window: &Window,
2157 cx: &App,
2158 ) -> AcceptEditPredictionBinding {
2159 let key_context = self.key_context_internal(true, window, cx);
2160 let in_conflict = self.edit_prediction_in_conflict();
2161
2162 AcceptEditPredictionBinding(
2163 window
2164 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2165 .into_iter()
2166 .filter(|binding| {
2167 !in_conflict
2168 || binding
2169 .keystrokes()
2170 .first()
2171 .map_or(false, |keystroke| keystroke.modifiers.modified())
2172 })
2173 .rev()
2174 .min_by_key(|binding| {
2175 binding
2176 .keystrokes()
2177 .first()
2178 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2179 }),
2180 )
2181 }
2182
2183 pub fn new_file(
2184 workspace: &mut Workspace,
2185 _: &workspace::NewFile,
2186 window: &mut Window,
2187 cx: &mut Context<Workspace>,
2188 ) {
2189 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2190 "Failed to create buffer",
2191 window,
2192 cx,
2193 |e, _, _| match e.error_code() {
2194 ErrorCode::RemoteUpgradeRequired => Some(format!(
2195 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2196 e.error_tag("required").unwrap_or("the latest version")
2197 )),
2198 _ => None,
2199 },
2200 );
2201 }
2202
2203 pub fn new_in_workspace(
2204 workspace: &mut Workspace,
2205 window: &mut Window,
2206 cx: &mut Context<Workspace>,
2207 ) -> Task<Result<Entity<Editor>>> {
2208 let project = workspace.project().clone();
2209 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2210
2211 cx.spawn_in(window, async move |workspace, cx| {
2212 let buffer = create.await?;
2213 workspace.update_in(cx, |workspace, window, cx| {
2214 let editor =
2215 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2216 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2217 editor
2218 })
2219 })
2220 }
2221
2222 fn new_file_vertical(
2223 workspace: &mut Workspace,
2224 _: &workspace::NewFileSplitVertical,
2225 window: &mut Window,
2226 cx: &mut Context<Workspace>,
2227 ) {
2228 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2229 }
2230
2231 fn new_file_horizontal(
2232 workspace: &mut Workspace,
2233 _: &workspace::NewFileSplitHorizontal,
2234 window: &mut Window,
2235 cx: &mut Context<Workspace>,
2236 ) {
2237 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2238 }
2239
2240 fn new_file_in_direction(
2241 workspace: &mut Workspace,
2242 direction: SplitDirection,
2243 window: &mut Window,
2244 cx: &mut Context<Workspace>,
2245 ) {
2246 let project = workspace.project().clone();
2247 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2248
2249 cx.spawn_in(window, async move |workspace, cx| {
2250 let buffer = create.await?;
2251 workspace.update_in(cx, move |workspace, window, cx| {
2252 workspace.split_item(
2253 direction,
2254 Box::new(
2255 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2256 ),
2257 window,
2258 cx,
2259 )
2260 })?;
2261 anyhow::Ok(())
2262 })
2263 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2264 match e.error_code() {
2265 ErrorCode::RemoteUpgradeRequired => Some(format!(
2266 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2267 e.error_tag("required").unwrap_or("the latest version")
2268 )),
2269 _ => None,
2270 }
2271 });
2272 }
2273
2274 pub fn leader_id(&self) -> Option<CollaboratorId> {
2275 self.leader_id
2276 }
2277
2278 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2279 &self.buffer
2280 }
2281
2282 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2283 self.workspace.as_ref()?.0.upgrade()
2284 }
2285
2286 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2287 self.buffer().read(cx).title(cx)
2288 }
2289
2290 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2291 let git_blame_gutter_max_author_length = self
2292 .render_git_blame_gutter(cx)
2293 .then(|| {
2294 if let Some(blame) = self.blame.as_ref() {
2295 let max_author_length =
2296 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2297 Some(max_author_length)
2298 } else {
2299 None
2300 }
2301 })
2302 .flatten();
2303
2304 EditorSnapshot {
2305 mode: self.mode.clone(),
2306 show_gutter: self.show_gutter,
2307 show_line_numbers: self.show_line_numbers,
2308 show_git_diff_gutter: self.show_git_diff_gutter,
2309 show_runnables: self.show_runnables,
2310 show_breakpoints: self.show_breakpoints,
2311 git_blame_gutter_max_author_length,
2312 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2313 scroll_anchor: self.scroll_manager.anchor(),
2314 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2315 placeholder_text: self.placeholder_text.clone(),
2316 is_focused: self.focus_handle.is_focused(window),
2317 current_line_highlight: self
2318 .current_line_highlight
2319 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2320 gutter_hovered: self.gutter_hovered,
2321 }
2322 }
2323
2324 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2325 self.buffer.read(cx).language_at(point, cx)
2326 }
2327
2328 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2329 self.buffer.read(cx).read(cx).file_at(point).cloned()
2330 }
2331
2332 pub fn active_excerpt(
2333 &self,
2334 cx: &App,
2335 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2336 self.buffer
2337 .read(cx)
2338 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2339 }
2340
2341 pub fn mode(&self) -> &EditorMode {
2342 &self.mode
2343 }
2344
2345 pub fn set_mode(&mut self, mode: EditorMode) {
2346 self.mode = mode;
2347 }
2348
2349 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2350 self.collaboration_hub.as_deref()
2351 }
2352
2353 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2354 self.collaboration_hub = Some(hub);
2355 }
2356
2357 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2358 self.in_project_search = in_project_search;
2359 }
2360
2361 pub fn set_custom_context_menu(
2362 &mut self,
2363 f: impl 'static
2364 + Fn(
2365 &mut Self,
2366 DisplayPoint,
2367 &mut Window,
2368 &mut Context<Self>,
2369 ) -> Option<Entity<ui::ContextMenu>>,
2370 ) {
2371 self.custom_context_menu = Some(Box::new(f))
2372 }
2373
2374 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2375 self.completion_provider = provider;
2376 }
2377
2378 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2379 self.semantics_provider.clone()
2380 }
2381
2382 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2383 self.semantics_provider = provider;
2384 }
2385
2386 pub fn set_edit_prediction_provider<T>(
2387 &mut self,
2388 provider: Option<Entity<T>>,
2389 window: &mut Window,
2390 cx: &mut Context<Self>,
2391 ) where
2392 T: EditPredictionProvider,
2393 {
2394 self.edit_prediction_provider =
2395 provider.map(|provider| RegisteredInlineCompletionProvider {
2396 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2397 if this.focus_handle.is_focused(window) {
2398 this.update_visible_inline_completion(window, cx);
2399 }
2400 }),
2401 provider: Arc::new(provider),
2402 });
2403 self.update_edit_prediction_settings(cx);
2404 self.refresh_inline_completion(false, false, window, cx);
2405 }
2406
2407 pub fn placeholder_text(&self) -> Option<&str> {
2408 self.placeholder_text.as_deref()
2409 }
2410
2411 pub fn set_placeholder_text(
2412 &mut self,
2413 placeholder_text: impl Into<Arc<str>>,
2414 cx: &mut Context<Self>,
2415 ) {
2416 let placeholder_text = Some(placeholder_text.into());
2417 if self.placeholder_text != placeholder_text {
2418 self.placeholder_text = placeholder_text;
2419 cx.notify();
2420 }
2421 }
2422
2423 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2424 self.cursor_shape = cursor_shape;
2425
2426 // Disrupt blink for immediate user feedback that the cursor shape has changed
2427 self.blink_manager.update(cx, BlinkManager::show_cursor);
2428
2429 cx.notify();
2430 }
2431
2432 pub fn set_current_line_highlight(
2433 &mut self,
2434 current_line_highlight: Option<CurrentLineHighlight>,
2435 ) {
2436 self.current_line_highlight = current_line_highlight;
2437 }
2438
2439 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2440 self.collapse_matches = collapse_matches;
2441 }
2442
2443 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2444 let buffers = self.buffer.read(cx).all_buffers();
2445 let Some(project) = self.project.as_ref() else {
2446 return;
2447 };
2448 project.update(cx, |project, cx| {
2449 for buffer in buffers {
2450 self.registered_buffers
2451 .entry(buffer.read(cx).remote_id())
2452 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2453 }
2454 })
2455 }
2456
2457 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2458 if self.collapse_matches {
2459 return range.start..range.start;
2460 }
2461 range.clone()
2462 }
2463
2464 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2465 if self.display_map.read(cx).clip_at_line_ends != clip {
2466 self.display_map
2467 .update(cx, |map, _| map.clip_at_line_ends = clip);
2468 }
2469 }
2470
2471 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2472 self.input_enabled = input_enabled;
2473 }
2474
2475 pub fn set_inline_completions_hidden_for_vim_mode(
2476 &mut self,
2477 hidden: bool,
2478 window: &mut Window,
2479 cx: &mut Context<Self>,
2480 ) {
2481 if hidden != self.inline_completions_hidden_for_vim_mode {
2482 self.inline_completions_hidden_for_vim_mode = hidden;
2483 if hidden {
2484 self.update_visible_inline_completion(window, cx);
2485 } else {
2486 self.refresh_inline_completion(true, false, window, cx);
2487 }
2488 }
2489 }
2490
2491 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2492 self.menu_inline_completions_policy = value;
2493 }
2494
2495 pub fn set_autoindent(&mut self, autoindent: bool) {
2496 if autoindent {
2497 self.autoindent_mode = Some(AutoindentMode::EachLine);
2498 } else {
2499 self.autoindent_mode = None;
2500 }
2501 }
2502
2503 pub fn read_only(&self, cx: &App) -> bool {
2504 self.read_only || self.buffer.read(cx).read_only()
2505 }
2506
2507 pub fn set_read_only(&mut self, read_only: bool) {
2508 self.read_only = read_only;
2509 }
2510
2511 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2512 self.use_autoclose = autoclose;
2513 }
2514
2515 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2516 self.use_auto_surround = auto_surround;
2517 }
2518
2519 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2520 self.auto_replace_emoji_shortcode = auto_replace;
2521 }
2522
2523 pub fn toggle_edit_predictions(
2524 &mut self,
2525 _: &ToggleEditPrediction,
2526 window: &mut Window,
2527 cx: &mut Context<Self>,
2528 ) {
2529 if self.show_inline_completions_override.is_some() {
2530 self.set_show_edit_predictions(None, window, cx);
2531 } else {
2532 let show_edit_predictions = !self.edit_predictions_enabled();
2533 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2534 }
2535 }
2536
2537 pub fn set_show_edit_predictions(
2538 &mut self,
2539 show_edit_predictions: Option<bool>,
2540 window: &mut Window,
2541 cx: &mut Context<Self>,
2542 ) {
2543 self.show_inline_completions_override = show_edit_predictions;
2544 self.update_edit_prediction_settings(cx);
2545
2546 if let Some(false) = show_edit_predictions {
2547 self.discard_inline_completion(false, cx);
2548 } else {
2549 self.refresh_inline_completion(false, true, window, cx);
2550 }
2551 }
2552
2553 fn inline_completions_disabled_in_scope(
2554 &self,
2555 buffer: &Entity<Buffer>,
2556 buffer_position: language::Anchor,
2557 cx: &App,
2558 ) -> bool {
2559 let snapshot = buffer.read(cx).snapshot();
2560 let settings = snapshot.settings_at(buffer_position, cx);
2561
2562 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2563 return false;
2564 };
2565
2566 scope.override_name().map_or(false, |scope_name| {
2567 settings
2568 .edit_predictions_disabled_in
2569 .iter()
2570 .any(|s| s == scope_name)
2571 })
2572 }
2573
2574 pub fn set_use_modal_editing(&mut self, to: bool) {
2575 self.use_modal_editing = to;
2576 }
2577
2578 pub fn use_modal_editing(&self) -> bool {
2579 self.use_modal_editing
2580 }
2581
2582 fn selections_did_change(
2583 &mut self,
2584 local: bool,
2585 old_cursor_position: &Anchor,
2586 show_completions: bool,
2587 window: &mut Window,
2588 cx: &mut Context<Self>,
2589 ) {
2590 window.invalidate_character_coordinates();
2591
2592 // Copy selections to primary selection buffer
2593 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2594 if local {
2595 let selections = self.selections.all::<usize>(cx);
2596 let buffer_handle = self.buffer.read(cx).read(cx);
2597
2598 let mut text = String::new();
2599 for (index, selection) in selections.iter().enumerate() {
2600 let text_for_selection = buffer_handle
2601 .text_for_range(selection.start..selection.end)
2602 .collect::<String>();
2603
2604 text.push_str(&text_for_selection);
2605 if index != selections.len() - 1 {
2606 text.push('\n');
2607 }
2608 }
2609
2610 if !text.is_empty() {
2611 cx.write_to_primary(ClipboardItem::new_string(text));
2612 }
2613 }
2614
2615 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2616 self.buffer.update(cx, |buffer, cx| {
2617 buffer.set_active_selections(
2618 &self.selections.disjoint_anchors(),
2619 self.selections.line_mode,
2620 self.cursor_shape,
2621 cx,
2622 )
2623 });
2624 }
2625 let display_map = self
2626 .display_map
2627 .update(cx, |display_map, cx| display_map.snapshot(cx));
2628 let buffer = &display_map.buffer_snapshot;
2629 self.add_selections_state = None;
2630 self.select_next_state = None;
2631 self.select_prev_state = None;
2632 self.select_syntax_node_history.try_clear();
2633 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2634 self.snippet_stack
2635 .invalidate(&self.selections.disjoint_anchors(), buffer);
2636 self.take_rename(false, window, cx);
2637
2638 let new_cursor_position = self.selections.newest_anchor().head();
2639
2640 self.push_to_nav_history(
2641 *old_cursor_position,
2642 Some(new_cursor_position.to_point(buffer)),
2643 false,
2644 cx,
2645 );
2646
2647 if local {
2648 let new_cursor_position = self.selections.newest_anchor().head();
2649 let mut context_menu = self.context_menu.borrow_mut();
2650 let completion_menu = match context_menu.as_ref() {
2651 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2652 _ => {
2653 *context_menu = None;
2654 None
2655 }
2656 };
2657 if let Some(buffer_id) = new_cursor_position.buffer_id {
2658 if !self.registered_buffers.contains_key(&buffer_id) {
2659 if let Some(project) = self.project.as_ref() {
2660 project.update(cx, |project, cx| {
2661 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2662 return;
2663 };
2664 self.registered_buffers.insert(
2665 buffer_id,
2666 project.register_buffer_with_language_servers(&buffer, cx),
2667 );
2668 })
2669 }
2670 }
2671 }
2672
2673 if let Some(completion_menu) = completion_menu {
2674 let cursor_position = new_cursor_position.to_offset(buffer);
2675 let (word_range, kind) =
2676 buffer.surrounding_word(completion_menu.initial_position, true);
2677 if kind == Some(CharKind::Word)
2678 && word_range.to_inclusive().contains(&cursor_position)
2679 {
2680 let mut completion_menu = completion_menu.clone();
2681 drop(context_menu);
2682
2683 let query = Self::completion_query(buffer, cursor_position);
2684 cx.spawn(async move |this, cx| {
2685 completion_menu
2686 .filter(query.as_deref(), cx.background_executor().clone())
2687 .await;
2688
2689 this.update(cx, |this, cx| {
2690 let mut context_menu = this.context_menu.borrow_mut();
2691 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2692 else {
2693 return;
2694 };
2695
2696 if menu.id > completion_menu.id {
2697 return;
2698 }
2699
2700 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2701 drop(context_menu);
2702 cx.notify();
2703 })
2704 })
2705 .detach();
2706
2707 if show_completions {
2708 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2709 }
2710 } else {
2711 drop(context_menu);
2712 self.hide_context_menu(window, cx);
2713 }
2714 } else {
2715 drop(context_menu);
2716 }
2717
2718 hide_hover(self, cx);
2719
2720 if old_cursor_position.to_display_point(&display_map).row()
2721 != new_cursor_position.to_display_point(&display_map).row()
2722 {
2723 self.available_code_actions.take();
2724 }
2725 self.refresh_code_actions(window, cx);
2726 self.refresh_document_highlights(cx);
2727 self.refresh_selected_text_highlights(false, window, cx);
2728 refresh_matching_bracket_highlights(self, window, cx);
2729 self.update_visible_inline_completion(window, cx);
2730 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2731 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2732 self.inline_blame_popover.take();
2733 if self.git_blame_inline_enabled {
2734 self.start_inline_blame_timer(window, cx);
2735 }
2736 }
2737
2738 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2739 cx.emit(EditorEvent::SelectionsChanged { local });
2740
2741 let selections = &self.selections.disjoint;
2742 if selections.len() == 1 {
2743 cx.emit(SearchEvent::ActiveMatchChanged)
2744 }
2745 if local {
2746 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2747 let inmemory_selections = selections
2748 .iter()
2749 .map(|s| {
2750 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2751 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2752 })
2753 .collect();
2754 self.update_restoration_data(cx, |data| {
2755 data.selections = inmemory_selections;
2756 });
2757
2758 if WorkspaceSettings::get(None, cx).restore_on_startup
2759 != RestoreOnStartupBehavior::None
2760 {
2761 if let Some(workspace_id) =
2762 self.workspace.as_ref().and_then(|workspace| workspace.1)
2763 {
2764 let snapshot = self.buffer().read(cx).snapshot(cx);
2765 let selections = selections.clone();
2766 let background_executor = cx.background_executor().clone();
2767 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2768 self.serialize_selections = cx.background_spawn(async move {
2769 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2770 let db_selections = selections
2771 .iter()
2772 .map(|selection| {
2773 (
2774 selection.start.to_offset(&snapshot),
2775 selection.end.to_offset(&snapshot),
2776 )
2777 })
2778 .collect();
2779
2780 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2781 .await
2782 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2783 .log_err();
2784 });
2785 }
2786 }
2787 }
2788 }
2789
2790 cx.notify();
2791 }
2792
2793 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2794 use text::ToOffset as _;
2795 use text::ToPoint as _;
2796
2797 if self.mode.is_minimap()
2798 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2799 {
2800 return;
2801 }
2802
2803 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2804 return;
2805 };
2806
2807 let snapshot = singleton.read(cx).snapshot();
2808 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2809 let display_snapshot = display_map.snapshot(cx);
2810
2811 display_snapshot
2812 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2813 .map(|fold| {
2814 fold.range.start.text_anchor.to_point(&snapshot)
2815 ..fold.range.end.text_anchor.to_point(&snapshot)
2816 })
2817 .collect()
2818 });
2819 self.update_restoration_data(cx, |data| {
2820 data.folds = inmemory_folds;
2821 });
2822
2823 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2824 return;
2825 };
2826 let background_executor = cx.background_executor().clone();
2827 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2828 let db_folds = self.display_map.update(cx, |display_map, cx| {
2829 display_map
2830 .snapshot(cx)
2831 .folds_in_range(0..snapshot.len())
2832 .map(|fold| {
2833 (
2834 fold.range.start.text_anchor.to_offset(&snapshot),
2835 fold.range.end.text_anchor.to_offset(&snapshot),
2836 )
2837 })
2838 .collect()
2839 });
2840 self.serialize_folds = cx.background_spawn(async move {
2841 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2842 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2843 .await
2844 .with_context(|| {
2845 format!(
2846 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2847 )
2848 })
2849 .log_err();
2850 });
2851 }
2852
2853 pub fn sync_selections(
2854 &mut self,
2855 other: Entity<Editor>,
2856 cx: &mut Context<Self>,
2857 ) -> gpui::Subscription {
2858 let other_selections = other.read(cx).selections.disjoint.to_vec();
2859 self.selections.change_with(cx, |selections| {
2860 selections.select_anchors(other_selections);
2861 });
2862
2863 let other_subscription =
2864 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2865 EditorEvent::SelectionsChanged { local: true } => {
2866 let other_selections = other.read(cx).selections.disjoint.to_vec();
2867 if other_selections.is_empty() {
2868 return;
2869 }
2870 this.selections.change_with(cx, |selections| {
2871 selections.select_anchors(other_selections);
2872 });
2873 }
2874 _ => {}
2875 });
2876
2877 let this_subscription =
2878 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2879 EditorEvent::SelectionsChanged { local: true } => {
2880 let these_selections = this.selections.disjoint.to_vec();
2881 if these_selections.is_empty() {
2882 return;
2883 }
2884 other.update(cx, |other_editor, cx| {
2885 other_editor.selections.change_with(cx, |selections| {
2886 selections.select_anchors(these_selections);
2887 })
2888 });
2889 }
2890 _ => {}
2891 });
2892
2893 Subscription::join(other_subscription, this_subscription)
2894 }
2895
2896 pub fn change_selections<R>(
2897 &mut self,
2898 autoscroll: Option<Autoscroll>,
2899 window: &mut Window,
2900 cx: &mut Context<Self>,
2901 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2902 ) -> R {
2903 self.change_selections_inner(autoscroll, true, window, cx, change)
2904 }
2905
2906 fn change_selections_inner<R>(
2907 &mut self,
2908 autoscroll: Option<Autoscroll>,
2909 request_completions: bool,
2910 window: &mut Window,
2911 cx: &mut Context<Self>,
2912 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2913 ) -> R {
2914 let old_cursor_position = self.selections.newest_anchor().head();
2915 self.push_to_selection_history();
2916
2917 let (changed, result) = self.selections.change_with(cx, change);
2918
2919 if changed {
2920 if let Some(autoscroll) = autoscroll {
2921 self.request_autoscroll(autoscroll, cx);
2922 }
2923 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2924
2925 if self.should_open_signature_help_automatically(
2926 &old_cursor_position,
2927 self.signature_help_state.backspace_pressed(),
2928 cx,
2929 ) {
2930 self.show_signature_help(&ShowSignatureHelp, window, cx);
2931 }
2932 self.signature_help_state.set_backspace_pressed(false);
2933 }
2934
2935 result
2936 }
2937
2938 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2939 where
2940 I: IntoIterator<Item = (Range<S>, T)>,
2941 S: ToOffset,
2942 T: Into<Arc<str>>,
2943 {
2944 if self.read_only(cx) {
2945 return;
2946 }
2947
2948 self.buffer
2949 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2950 }
2951
2952 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2953 where
2954 I: IntoIterator<Item = (Range<S>, T)>,
2955 S: ToOffset,
2956 T: Into<Arc<str>>,
2957 {
2958 if self.read_only(cx) {
2959 return;
2960 }
2961
2962 self.buffer.update(cx, |buffer, cx| {
2963 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2964 });
2965 }
2966
2967 pub fn edit_with_block_indent<I, S, T>(
2968 &mut self,
2969 edits: I,
2970 original_indent_columns: Vec<Option<u32>>,
2971 cx: &mut Context<Self>,
2972 ) where
2973 I: IntoIterator<Item = (Range<S>, T)>,
2974 S: ToOffset,
2975 T: Into<Arc<str>>,
2976 {
2977 if self.read_only(cx) {
2978 return;
2979 }
2980
2981 self.buffer.update(cx, |buffer, cx| {
2982 buffer.edit(
2983 edits,
2984 Some(AutoindentMode::Block {
2985 original_indent_columns,
2986 }),
2987 cx,
2988 )
2989 });
2990 }
2991
2992 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2993 self.hide_context_menu(window, cx);
2994
2995 match phase {
2996 SelectPhase::Begin {
2997 position,
2998 add,
2999 click_count,
3000 } => self.begin_selection(position, add, click_count, window, cx),
3001 SelectPhase::BeginColumnar {
3002 position,
3003 goal_column,
3004 reset,
3005 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3006 SelectPhase::Extend {
3007 position,
3008 click_count,
3009 } => self.extend_selection(position, click_count, window, cx),
3010 SelectPhase::Update {
3011 position,
3012 goal_column,
3013 scroll_delta,
3014 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3015 SelectPhase::End => self.end_selection(window, cx),
3016 }
3017 }
3018
3019 fn extend_selection(
3020 &mut self,
3021 position: DisplayPoint,
3022 click_count: usize,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3027 let tail = self.selections.newest::<usize>(cx).tail();
3028 self.begin_selection(position, false, click_count, window, cx);
3029
3030 let position = position.to_offset(&display_map, Bias::Left);
3031 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3032
3033 let mut pending_selection = self
3034 .selections
3035 .pending_anchor()
3036 .expect("extend_selection not called with pending selection");
3037 if position >= tail {
3038 pending_selection.start = tail_anchor;
3039 } else {
3040 pending_selection.end = tail_anchor;
3041 pending_selection.reversed = true;
3042 }
3043
3044 let mut pending_mode = self.selections.pending_mode().unwrap();
3045 match &mut pending_mode {
3046 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3047 _ => {}
3048 }
3049
3050 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3051
3052 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3053 s.set_pending(pending_selection, pending_mode)
3054 });
3055 }
3056
3057 fn begin_selection(
3058 &mut self,
3059 position: DisplayPoint,
3060 add: bool,
3061 click_count: usize,
3062 window: &mut Window,
3063 cx: &mut Context<Self>,
3064 ) {
3065 if !self.focus_handle.is_focused(window) {
3066 self.last_focused_descendant = None;
3067 window.focus(&self.focus_handle);
3068 }
3069
3070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3071 let buffer = &display_map.buffer_snapshot;
3072 let position = display_map.clip_point(position, Bias::Left);
3073
3074 let start;
3075 let end;
3076 let mode;
3077 let mut auto_scroll;
3078 match click_count {
3079 1 => {
3080 start = buffer.anchor_before(position.to_point(&display_map));
3081 end = start;
3082 mode = SelectMode::Character;
3083 auto_scroll = true;
3084 }
3085 2 => {
3086 let range = movement::surrounding_word(&display_map, position);
3087 start = buffer.anchor_before(range.start.to_point(&display_map));
3088 end = buffer.anchor_before(range.end.to_point(&display_map));
3089 mode = SelectMode::Word(start..end);
3090 auto_scroll = true;
3091 }
3092 3 => {
3093 let position = display_map
3094 .clip_point(position, Bias::Left)
3095 .to_point(&display_map);
3096 let line_start = display_map.prev_line_boundary(position).0;
3097 let next_line_start = buffer.clip_point(
3098 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3099 Bias::Left,
3100 );
3101 start = buffer.anchor_before(line_start);
3102 end = buffer.anchor_before(next_line_start);
3103 mode = SelectMode::Line(start..end);
3104 auto_scroll = true;
3105 }
3106 _ => {
3107 start = buffer.anchor_before(0);
3108 end = buffer.anchor_before(buffer.len());
3109 mode = SelectMode::All;
3110 auto_scroll = false;
3111 }
3112 }
3113 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3114
3115 let point_to_delete: Option<usize> = {
3116 let selected_points: Vec<Selection<Point>> =
3117 self.selections.disjoint_in_range(start..end, cx);
3118
3119 if !add || click_count > 1 {
3120 None
3121 } else if !selected_points.is_empty() {
3122 Some(selected_points[0].id)
3123 } else {
3124 let clicked_point_already_selected =
3125 self.selections.disjoint.iter().find(|selection| {
3126 selection.start.to_point(buffer) == start.to_point(buffer)
3127 || selection.end.to_point(buffer) == end.to_point(buffer)
3128 });
3129
3130 clicked_point_already_selected.map(|selection| selection.id)
3131 }
3132 };
3133
3134 let selections_count = self.selections.count();
3135
3136 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3137 if let Some(point_to_delete) = point_to_delete {
3138 s.delete(point_to_delete);
3139
3140 if selections_count == 1 {
3141 s.set_pending_anchor_range(start..end, mode);
3142 }
3143 } else {
3144 if !add {
3145 s.clear_disjoint();
3146 }
3147
3148 s.set_pending_anchor_range(start..end, mode);
3149 }
3150 });
3151 }
3152
3153 fn begin_columnar_selection(
3154 &mut self,
3155 position: DisplayPoint,
3156 goal_column: u32,
3157 reset: bool,
3158 window: &mut Window,
3159 cx: &mut Context<Self>,
3160 ) {
3161 if !self.focus_handle.is_focused(window) {
3162 self.last_focused_descendant = None;
3163 window.focus(&self.focus_handle);
3164 }
3165
3166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3167
3168 if reset {
3169 let pointer_position = display_map
3170 .buffer_snapshot
3171 .anchor_before(position.to_point(&display_map));
3172
3173 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3174 s.clear_disjoint();
3175 s.set_pending_anchor_range(
3176 pointer_position..pointer_position,
3177 SelectMode::Character,
3178 );
3179 });
3180 }
3181
3182 let tail = self.selections.newest::<Point>(cx).tail();
3183 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3184
3185 if !reset {
3186 self.select_columns(
3187 tail.to_display_point(&display_map),
3188 position,
3189 goal_column,
3190 &display_map,
3191 window,
3192 cx,
3193 );
3194 }
3195 }
3196
3197 fn update_selection(
3198 &mut self,
3199 position: DisplayPoint,
3200 goal_column: u32,
3201 scroll_delta: gpui::Point<f32>,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3206
3207 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3208 let tail = tail.to_display_point(&display_map);
3209 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3210 } else if let Some(mut pending) = self.selections.pending_anchor() {
3211 let buffer = self.buffer.read(cx).snapshot(cx);
3212 let head;
3213 let tail;
3214 let mode = self.selections.pending_mode().unwrap();
3215 match &mode {
3216 SelectMode::Character => {
3217 head = position.to_point(&display_map);
3218 tail = pending.tail().to_point(&buffer);
3219 }
3220 SelectMode::Word(original_range) => {
3221 let original_display_range = original_range.start.to_display_point(&display_map)
3222 ..original_range.end.to_display_point(&display_map);
3223 let original_buffer_range = original_display_range.start.to_point(&display_map)
3224 ..original_display_range.end.to_point(&display_map);
3225 if movement::is_inside_word(&display_map, position)
3226 || original_display_range.contains(&position)
3227 {
3228 let word_range = movement::surrounding_word(&display_map, position);
3229 if word_range.start < original_display_range.start {
3230 head = word_range.start.to_point(&display_map);
3231 } else {
3232 head = word_range.end.to_point(&display_map);
3233 }
3234 } else {
3235 head = position.to_point(&display_map);
3236 }
3237
3238 if head <= original_buffer_range.start {
3239 tail = original_buffer_range.end;
3240 } else {
3241 tail = original_buffer_range.start;
3242 }
3243 }
3244 SelectMode::Line(original_range) => {
3245 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3246
3247 let position = display_map
3248 .clip_point(position, Bias::Left)
3249 .to_point(&display_map);
3250 let line_start = display_map.prev_line_boundary(position).0;
3251 let next_line_start = buffer.clip_point(
3252 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3253 Bias::Left,
3254 );
3255
3256 if line_start < original_range.start {
3257 head = line_start
3258 } else {
3259 head = next_line_start
3260 }
3261
3262 if head <= original_range.start {
3263 tail = original_range.end;
3264 } else {
3265 tail = original_range.start;
3266 }
3267 }
3268 SelectMode::All => {
3269 return;
3270 }
3271 };
3272
3273 if head < tail {
3274 pending.start = buffer.anchor_before(head);
3275 pending.end = buffer.anchor_before(tail);
3276 pending.reversed = true;
3277 } else {
3278 pending.start = buffer.anchor_before(tail);
3279 pending.end = buffer.anchor_before(head);
3280 pending.reversed = false;
3281 }
3282
3283 self.change_selections(None, window, cx, |s| {
3284 s.set_pending(pending, mode);
3285 });
3286 } else {
3287 log::error!("update_selection dispatched with no pending selection");
3288 return;
3289 }
3290
3291 self.apply_scroll_delta(scroll_delta, window, cx);
3292 cx.notify();
3293 }
3294
3295 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3296 self.columnar_selection_tail.take();
3297 if self.selections.pending_anchor().is_some() {
3298 let selections = self.selections.all::<usize>(cx);
3299 self.change_selections(None, window, cx, |s| {
3300 s.select(selections);
3301 s.clear_pending();
3302 });
3303 }
3304 }
3305
3306 fn select_columns(
3307 &mut self,
3308 tail: DisplayPoint,
3309 head: DisplayPoint,
3310 goal_column: u32,
3311 display_map: &DisplaySnapshot,
3312 window: &mut Window,
3313 cx: &mut Context<Self>,
3314 ) {
3315 let start_row = cmp::min(tail.row(), head.row());
3316 let end_row = cmp::max(tail.row(), head.row());
3317 let start_column = cmp::min(tail.column(), goal_column);
3318 let end_column = cmp::max(tail.column(), goal_column);
3319 let reversed = start_column < tail.column();
3320
3321 let selection_ranges = (start_row.0..=end_row.0)
3322 .map(DisplayRow)
3323 .filter_map(|row| {
3324 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3325 let start = display_map
3326 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3327 .to_point(display_map);
3328 let end = display_map
3329 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3330 .to_point(display_map);
3331 if reversed {
3332 Some(end..start)
3333 } else {
3334 Some(start..end)
3335 }
3336 } else {
3337 None
3338 }
3339 })
3340 .collect::<Vec<_>>();
3341
3342 self.change_selections(None, window, cx, |s| {
3343 s.select_ranges(selection_ranges);
3344 });
3345 cx.notify();
3346 }
3347
3348 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3349 self.selections
3350 .all_adjusted(cx)
3351 .iter()
3352 .any(|selection| !selection.is_empty())
3353 }
3354
3355 pub fn has_pending_nonempty_selection(&self) -> bool {
3356 let pending_nonempty_selection = match self.selections.pending_anchor() {
3357 Some(Selection { start, end, .. }) => start != end,
3358 None => false,
3359 };
3360
3361 pending_nonempty_selection
3362 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3363 }
3364
3365 pub fn has_pending_selection(&self) -> bool {
3366 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3367 }
3368
3369 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3370 self.selection_mark_mode = false;
3371
3372 if self.clear_expanded_diff_hunks(cx) {
3373 cx.notify();
3374 return;
3375 }
3376 if self.dismiss_menus_and_popups(true, window, cx) {
3377 return;
3378 }
3379
3380 if self.mode.is_full()
3381 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3382 {
3383 return;
3384 }
3385
3386 cx.propagate();
3387 }
3388
3389 pub fn dismiss_menus_and_popups(
3390 &mut self,
3391 is_user_requested: bool,
3392 window: &mut Window,
3393 cx: &mut Context<Self>,
3394 ) -> bool {
3395 if self.take_rename(false, window, cx).is_some() {
3396 return true;
3397 }
3398
3399 if hide_hover(self, cx) {
3400 return true;
3401 }
3402
3403 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3404 return true;
3405 }
3406
3407 if self.hide_context_menu(window, cx).is_some() {
3408 return true;
3409 }
3410
3411 if self.mouse_context_menu.take().is_some() {
3412 return true;
3413 }
3414
3415 if is_user_requested && self.discard_inline_completion(true, cx) {
3416 return true;
3417 }
3418
3419 if self.snippet_stack.pop().is_some() {
3420 return true;
3421 }
3422
3423 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3424 self.dismiss_diagnostics(cx);
3425 return true;
3426 }
3427
3428 false
3429 }
3430
3431 fn linked_editing_ranges_for(
3432 &self,
3433 selection: Range<text::Anchor>,
3434 cx: &App,
3435 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3436 if self.linked_edit_ranges.is_empty() {
3437 return None;
3438 }
3439 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3440 selection.end.buffer_id.and_then(|end_buffer_id| {
3441 if selection.start.buffer_id != Some(end_buffer_id) {
3442 return None;
3443 }
3444 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3445 let snapshot = buffer.read(cx).snapshot();
3446 self.linked_edit_ranges
3447 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3448 .map(|ranges| (ranges, snapshot, buffer))
3449 })?;
3450 use text::ToOffset as TO;
3451 // find offset from the start of current range to current cursor position
3452 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3453
3454 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3455 let start_difference = start_offset - start_byte_offset;
3456 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3457 let end_difference = end_offset - start_byte_offset;
3458 // Current range has associated linked ranges.
3459 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3460 for range in linked_ranges.iter() {
3461 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3462 let end_offset = start_offset + end_difference;
3463 let start_offset = start_offset + start_difference;
3464 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3465 continue;
3466 }
3467 if self.selections.disjoint_anchor_ranges().any(|s| {
3468 if s.start.buffer_id != selection.start.buffer_id
3469 || s.end.buffer_id != selection.end.buffer_id
3470 {
3471 return false;
3472 }
3473 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3474 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3475 }) {
3476 continue;
3477 }
3478 let start = buffer_snapshot.anchor_after(start_offset);
3479 let end = buffer_snapshot.anchor_after(end_offset);
3480 linked_edits
3481 .entry(buffer.clone())
3482 .or_default()
3483 .push(start..end);
3484 }
3485 Some(linked_edits)
3486 }
3487
3488 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3489 let text: Arc<str> = text.into();
3490
3491 if self.read_only(cx) {
3492 return;
3493 }
3494
3495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3496
3497 let selections = self.selections.all_adjusted(cx);
3498 let mut bracket_inserted = false;
3499 let mut edits = Vec::new();
3500 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3501 let mut new_selections = Vec::with_capacity(selections.len());
3502 let mut new_autoclose_regions = Vec::new();
3503 let snapshot = self.buffer.read(cx).read(cx);
3504 let mut clear_linked_edit_ranges = false;
3505
3506 for (selection, autoclose_region) in
3507 self.selections_with_autoclose_regions(selections, &snapshot)
3508 {
3509 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3510 // Determine if the inserted text matches the opening or closing
3511 // bracket of any of this language's bracket pairs.
3512 let mut bracket_pair = None;
3513 let mut is_bracket_pair_start = false;
3514 let mut is_bracket_pair_end = false;
3515 if !text.is_empty() {
3516 let mut bracket_pair_matching_end = None;
3517 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3518 // and they are removing the character that triggered IME popup.
3519 for (pair, enabled) in scope.brackets() {
3520 if !pair.close && !pair.surround {
3521 continue;
3522 }
3523
3524 if enabled && pair.start.ends_with(text.as_ref()) {
3525 let prefix_len = pair.start.len() - text.len();
3526 let preceding_text_matches_prefix = prefix_len == 0
3527 || (selection.start.column >= (prefix_len as u32)
3528 && snapshot.contains_str_at(
3529 Point::new(
3530 selection.start.row,
3531 selection.start.column - (prefix_len as u32),
3532 ),
3533 &pair.start[..prefix_len],
3534 ));
3535 if preceding_text_matches_prefix {
3536 bracket_pair = Some(pair.clone());
3537 is_bracket_pair_start = true;
3538 break;
3539 }
3540 }
3541 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3542 {
3543 // take first bracket pair matching end, but don't break in case a later bracket
3544 // pair matches start
3545 bracket_pair_matching_end = Some(pair.clone());
3546 }
3547 }
3548 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3549 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3550 is_bracket_pair_end = true;
3551 }
3552 }
3553
3554 if let Some(bracket_pair) = bracket_pair {
3555 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3556 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3557 let auto_surround =
3558 self.use_auto_surround && snapshot_settings.use_auto_surround;
3559 if selection.is_empty() {
3560 if is_bracket_pair_start {
3561 // If the inserted text is a suffix of an opening bracket and the
3562 // selection is preceded by the rest of the opening bracket, then
3563 // insert the closing bracket.
3564 let following_text_allows_autoclose = snapshot
3565 .chars_at(selection.start)
3566 .next()
3567 .map_or(true, |c| scope.should_autoclose_before(c));
3568
3569 let preceding_text_allows_autoclose = selection.start.column == 0
3570 || snapshot.reversed_chars_at(selection.start).next().map_or(
3571 true,
3572 |c| {
3573 bracket_pair.start != bracket_pair.end
3574 || !snapshot
3575 .char_classifier_at(selection.start)
3576 .is_word(c)
3577 },
3578 );
3579
3580 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3581 && bracket_pair.start.len() == 1
3582 {
3583 let target = bracket_pair.start.chars().next().unwrap();
3584 let current_line_count = snapshot
3585 .reversed_chars_at(selection.start)
3586 .take_while(|&c| c != '\n')
3587 .filter(|&c| c == target)
3588 .count();
3589 current_line_count % 2 == 1
3590 } else {
3591 false
3592 };
3593
3594 if autoclose
3595 && bracket_pair.close
3596 && following_text_allows_autoclose
3597 && preceding_text_allows_autoclose
3598 && !is_closing_quote
3599 {
3600 let anchor = snapshot.anchor_before(selection.end);
3601 new_selections.push((selection.map(|_| anchor), text.len()));
3602 new_autoclose_regions.push((
3603 anchor,
3604 text.len(),
3605 selection.id,
3606 bracket_pair.clone(),
3607 ));
3608 edits.push((
3609 selection.range(),
3610 format!("{}{}", text, bracket_pair.end).into(),
3611 ));
3612 bracket_inserted = true;
3613 continue;
3614 }
3615 }
3616
3617 if let Some(region) = autoclose_region {
3618 // If the selection is followed by an auto-inserted closing bracket,
3619 // then don't insert that closing bracket again; just move the selection
3620 // past the closing bracket.
3621 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3622 && text.as_ref() == region.pair.end.as_str();
3623 if should_skip {
3624 let anchor = snapshot.anchor_after(selection.end);
3625 new_selections
3626 .push((selection.map(|_| anchor), region.pair.end.len()));
3627 continue;
3628 }
3629 }
3630
3631 let always_treat_brackets_as_autoclosed = snapshot
3632 .language_settings_at(selection.start, cx)
3633 .always_treat_brackets_as_autoclosed;
3634 if always_treat_brackets_as_autoclosed
3635 && is_bracket_pair_end
3636 && snapshot.contains_str_at(selection.end, text.as_ref())
3637 {
3638 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3639 // and the inserted text is a closing bracket and the selection is followed
3640 // by the closing bracket then move the selection past the closing bracket.
3641 let anchor = snapshot.anchor_after(selection.end);
3642 new_selections.push((selection.map(|_| anchor), text.len()));
3643 continue;
3644 }
3645 }
3646 // If an opening bracket is 1 character long and is typed while
3647 // text is selected, then surround that text with the bracket pair.
3648 else if auto_surround
3649 && bracket_pair.surround
3650 && is_bracket_pair_start
3651 && bracket_pair.start.chars().count() == 1
3652 {
3653 edits.push((selection.start..selection.start, text.clone()));
3654 edits.push((
3655 selection.end..selection.end,
3656 bracket_pair.end.as_str().into(),
3657 ));
3658 bracket_inserted = true;
3659 new_selections.push((
3660 Selection {
3661 id: selection.id,
3662 start: snapshot.anchor_after(selection.start),
3663 end: snapshot.anchor_before(selection.end),
3664 reversed: selection.reversed,
3665 goal: selection.goal,
3666 },
3667 0,
3668 ));
3669 continue;
3670 }
3671 }
3672 }
3673
3674 if self.auto_replace_emoji_shortcode
3675 && selection.is_empty()
3676 && text.as_ref().ends_with(':')
3677 {
3678 if let Some(possible_emoji_short_code) =
3679 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3680 {
3681 if !possible_emoji_short_code.is_empty() {
3682 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3683 let emoji_shortcode_start = Point::new(
3684 selection.start.row,
3685 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3686 );
3687
3688 // Remove shortcode from buffer
3689 edits.push((
3690 emoji_shortcode_start..selection.start,
3691 "".to_string().into(),
3692 ));
3693 new_selections.push((
3694 Selection {
3695 id: selection.id,
3696 start: snapshot.anchor_after(emoji_shortcode_start),
3697 end: snapshot.anchor_before(selection.start),
3698 reversed: selection.reversed,
3699 goal: selection.goal,
3700 },
3701 0,
3702 ));
3703
3704 // Insert emoji
3705 let selection_start_anchor = snapshot.anchor_after(selection.start);
3706 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3707 edits.push((selection.start..selection.end, emoji.to_string().into()));
3708
3709 continue;
3710 }
3711 }
3712 }
3713 }
3714
3715 // If not handling any auto-close operation, then just replace the selected
3716 // text with the given input and move the selection to the end of the
3717 // newly inserted text.
3718 let anchor = snapshot.anchor_after(selection.end);
3719 if !self.linked_edit_ranges.is_empty() {
3720 let start_anchor = snapshot.anchor_before(selection.start);
3721
3722 let is_word_char = text.chars().next().map_or(true, |char| {
3723 let classifier = snapshot
3724 .char_classifier_at(start_anchor.to_offset(&snapshot))
3725 .ignore_punctuation(true);
3726 classifier.is_word(char)
3727 });
3728
3729 if is_word_char {
3730 if let Some(ranges) = self
3731 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3732 {
3733 for (buffer, edits) in ranges {
3734 linked_edits
3735 .entry(buffer.clone())
3736 .or_default()
3737 .extend(edits.into_iter().map(|range| (range, text.clone())));
3738 }
3739 }
3740 } else {
3741 clear_linked_edit_ranges = true;
3742 }
3743 }
3744
3745 new_selections.push((selection.map(|_| anchor), 0));
3746 edits.push((selection.start..selection.end, text.clone()));
3747 }
3748
3749 drop(snapshot);
3750
3751 self.transact(window, cx, |this, window, cx| {
3752 if clear_linked_edit_ranges {
3753 this.linked_edit_ranges.clear();
3754 }
3755 let initial_buffer_versions =
3756 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3757
3758 this.buffer.update(cx, |buffer, cx| {
3759 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3760 });
3761 for (buffer, edits) in linked_edits {
3762 buffer.update(cx, |buffer, cx| {
3763 let snapshot = buffer.snapshot();
3764 let edits = edits
3765 .into_iter()
3766 .map(|(range, text)| {
3767 use text::ToPoint as TP;
3768 let end_point = TP::to_point(&range.end, &snapshot);
3769 let start_point = TP::to_point(&range.start, &snapshot);
3770 (start_point..end_point, text)
3771 })
3772 .sorted_by_key(|(range, _)| range.start);
3773 buffer.edit(edits, None, cx);
3774 })
3775 }
3776 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3777 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3778 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3779 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3780 .zip(new_selection_deltas)
3781 .map(|(selection, delta)| Selection {
3782 id: selection.id,
3783 start: selection.start + delta,
3784 end: selection.end + delta,
3785 reversed: selection.reversed,
3786 goal: SelectionGoal::None,
3787 })
3788 .collect::<Vec<_>>();
3789
3790 let mut i = 0;
3791 for (position, delta, selection_id, pair) in new_autoclose_regions {
3792 let position = position.to_offset(&map.buffer_snapshot) + delta;
3793 let start = map.buffer_snapshot.anchor_before(position);
3794 let end = map.buffer_snapshot.anchor_after(position);
3795 while let Some(existing_state) = this.autoclose_regions.get(i) {
3796 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3797 Ordering::Less => i += 1,
3798 Ordering::Greater => break,
3799 Ordering::Equal => {
3800 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3801 Ordering::Less => i += 1,
3802 Ordering::Equal => break,
3803 Ordering::Greater => break,
3804 }
3805 }
3806 }
3807 }
3808 this.autoclose_regions.insert(
3809 i,
3810 AutocloseRegion {
3811 selection_id,
3812 range: start..end,
3813 pair,
3814 },
3815 );
3816 }
3817
3818 let had_active_inline_completion = this.has_active_inline_completion();
3819 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3820 s.select(new_selections)
3821 });
3822
3823 if !bracket_inserted {
3824 if let Some(on_type_format_task) =
3825 this.trigger_on_type_formatting(text.to_string(), window, cx)
3826 {
3827 on_type_format_task.detach_and_log_err(cx);
3828 }
3829 }
3830
3831 let editor_settings = EditorSettings::get_global(cx);
3832 if bracket_inserted
3833 && (editor_settings.auto_signature_help
3834 || editor_settings.show_signature_help_after_edits)
3835 {
3836 this.show_signature_help(&ShowSignatureHelp, window, cx);
3837 }
3838
3839 let trigger_in_words =
3840 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3841 if this.hard_wrap.is_some() {
3842 let latest: Range<Point> = this.selections.newest(cx).range();
3843 if latest.is_empty()
3844 && this
3845 .buffer()
3846 .read(cx)
3847 .snapshot(cx)
3848 .line_len(MultiBufferRow(latest.start.row))
3849 == latest.start.column
3850 {
3851 this.rewrap_impl(
3852 RewrapOptions {
3853 override_language_settings: true,
3854 preserve_existing_whitespace: true,
3855 },
3856 cx,
3857 )
3858 }
3859 }
3860 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3861 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3862 this.refresh_inline_completion(true, false, window, cx);
3863 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3864 });
3865 }
3866
3867 fn find_possible_emoji_shortcode_at_position(
3868 snapshot: &MultiBufferSnapshot,
3869 position: Point,
3870 ) -> Option<String> {
3871 let mut chars = Vec::new();
3872 let mut found_colon = false;
3873 for char in snapshot.reversed_chars_at(position).take(100) {
3874 // Found a possible emoji shortcode in the middle of the buffer
3875 if found_colon {
3876 if char.is_whitespace() {
3877 chars.reverse();
3878 return Some(chars.iter().collect());
3879 }
3880 // If the previous character is not a whitespace, we are in the middle of a word
3881 // and we only want to complete the shortcode if the word is made up of other emojis
3882 let mut containing_word = String::new();
3883 for ch in snapshot
3884 .reversed_chars_at(position)
3885 .skip(chars.len() + 1)
3886 .take(100)
3887 {
3888 if ch.is_whitespace() {
3889 break;
3890 }
3891 containing_word.push(ch);
3892 }
3893 let containing_word = containing_word.chars().rev().collect::<String>();
3894 if util::word_consists_of_emojis(containing_word.as_str()) {
3895 chars.reverse();
3896 return Some(chars.iter().collect());
3897 }
3898 }
3899
3900 if char.is_whitespace() || !char.is_ascii() {
3901 return None;
3902 }
3903 if char == ':' {
3904 found_colon = true;
3905 } else {
3906 chars.push(char);
3907 }
3908 }
3909 // Found a possible emoji shortcode at the beginning of the buffer
3910 chars.reverse();
3911 Some(chars.iter().collect())
3912 }
3913
3914 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3916 self.transact(window, cx, |this, window, cx| {
3917 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3918 let selections = this.selections.all::<usize>(cx);
3919 let multi_buffer = this.buffer.read(cx);
3920 let buffer = multi_buffer.snapshot(cx);
3921 selections
3922 .iter()
3923 .map(|selection| {
3924 let start_point = selection.start.to_point(&buffer);
3925 let mut existing_indent =
3926 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3927 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3928 let start = selection.start;
3929 let end = selection.end;
3930 let selection_is_empty = start == end;
3931 let language_scope = buffer.language_scope_at(start);
3932 let (
3933 comment_delimiter,
3934 doc_delimiter,
3935 insert_extra_newline,
3936 indent_on_newline,
3937 indent_on_extra_newline,
3938 ) = if let Some(language) = &language_scope {
3939 let mut insert_extra_newline =
3940 insert_extra_newline_brackets(&buffer, start..end, language)
3941 || insert_extra_newline_tree_sitter(&buffer, start..end);
3942
3943 // Comment extension on newline is allowed only for cursor selections
3944 let comment_delimiter = maybe!({
3945 if !selection_is_empty {
3946 return None;
3947 }
3948
3949 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3950 return None;
3951 }
3952
3953 let delimiters = language.line_comment_prefixes();
3954 let max_len_of_delimiter =
3955 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3956 let (snapshot, range) =
3957 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3958
3959 let num_of_whitespaces = snapshot
3960 .chars_for_range(range.clone())
3961 .take_while(|c| c.is_whitespace())
3962 .count();
3963 let comment_candidate = snapshot
3964 .chars_for_range(range)
3965 .skip(num_of_whitespaces)
3966 .take(max_len_of_delimiter)
3967 .collect::<String>();
3968 let (delimiter, trimmed_len) = delimiters
3969 .iter()
3970 .filter_map(|delimiter| {
3971 let prefix = delimiter.trim_end();
3972 if comment_candidate.starts_with(prefix) {
3973 Some((delimiter, prefix.len()))
3974 } else {
3975 None
3976 }
3977 })
3978 .max_by_key(|(_, len)| *len)?;
3979
3980 let cursor_is_placed_after_comment_marker =
3981 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3982 if cursor_is_placed_after_comment_marker {
3983 Some(delimiter.clone())
3984 } else {
3985 None
3986 }
3987 });
3988
3989 let mut indent_on_newline = IndentSize::spaces(0);
3990 let mut indent_on_extra_newline = IndentSize::spaces(0);
3991
3992 let doc_delimiter = maybe!({
3993 if !selection_is_empty {
3994 return None;
3995 }
3996
3997 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3998 return None;
3999 }
4000
4001 let DocumentationConfig {
4002 start: start_tag,
4003 end: end_tag,
4004 prefix: delimiter,
4005 tab_size: len,
4006 } = language.documentation()?;
4007
4008 let is_within_block_comment = buffer
4009 .language_scope_at(start_point)
4010 .is_some_and(|scope| scope.override_name() == Some("comment"));
4011 if !is_within_block_comment {
4012 return None;
4013 }
4014
4015 let (snapshot, range) =
4016 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4017
4018 let num_of_whitespaces = snapshot
4019 .chars_for_range(range.clone())
4020 .take_while(|c| c.is_whitespace())
4021 .count();
4022
4023 // 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.
4024 let column = start_point.column;
4025 let cursor_is_after_start_tag = {
4026 let start_tag_len = start_tag.len();
4027 let start_tag_line = snapshot
4028 .chars_for_range(range.clone())
4029 .skip(num_of_whitespaces)
4030 .take(start_tag_len)
4031 .collect::<String>();
4032 if start_tag_line.starts_with(start_tag.as_ref()) {
4033 num_of_whitespaces + start_tag_len <= column as usize
4034 } else {
4035 false
4036 }
4037 };
4038
4039 let cursor_is_after_delimiter = {
4040 let delimiter_trim = delimiter.trim_end();
4041 let delimiter_line = snapshot
4042 .chars_for_range(range.clone())
4043 .skip(num_of_whitespaces)
4044 .take(delimiter_trim.len())
4045 .collect::<String>();
4046 if delimiter_line.starts_with(delimiter_trim) {
4047 num_of_whitespaces + delimiter_trim.len() <= column as usize
4048 } else {
4049 false
4050 }
4051 };
4052
4053 let cursor_is_before_end_tag_if_exists = {
4054 let mut char_position = 0u32;
4055 let mut end_tag_offset = None;
4056
4057 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4058 if let Some(byte_pos) = chunk.find(&**end_tag) {
4059 let chars_before_match =
4060 chunk[..byte_pos].chars().count() as u32;
4061 end_tag_offset =
4062 Some(char_position + chars_before_match);
4063 break 'outer;
4064 }
4065 char_position += chunk.chars().count() as u32;
4066 }
4067
4068 if let Some(end_tag_offset) = end_tag_offset {
4069 let cursor_is_before_end_tag = column <= end_tag_offset;
4070 if cursor_is_after_start_tag {
4071 if cursor_is_before_end_tag {
4072 insert_extra_newline = true;
4073 }
4074 let cursor_is_at_start_of_end_tag =
4075 column == end_tag_offset;
4076 if cursor_is_at_start_of_end_tag {
4077 indent_on_extra_newline.len = (*len).into();
4078 }
4079 }
4080 cursor_is_before_end_tag
4081 } else {
4082 true
4083 }
4084 };
4085
4086 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4087 && cursor_is_before_end_tag_if_exists
4088 {
4089 if cursor_is_after_start_tag {
4090 indent_on_newline.len = (*len).into();
4091 }
4092 Some(delimiter.clone())
4093 } else {
4094 None
4095 }
4096 });
4097
4098 (
4099 comment_delimiter,
4100 doc_delimiter,
4101 insert_extra_newline,
4102 indent_on_newline,
4103 indent_on_extra_newline,
4104 )
4105 } else {
4106 (
4107 None,
4108 None,
4109 false,
4110 IndentSize::default(),
4111 IndentSize::default(),
4112 )
4113 };
4114
4115 let prevent_auto_indent = doc_delimiter.is_some();
4116 let delimiter = comment_delimiter.or(doc_delimiter);
4117
4118 let capacity_for_delimiter =
4119 delimiter.as_deref().map(str::len).unwrap_or_default();
4120 let mut new_text = String::with_capacity(
4121 1 + capacity_for_delimiter
4122 + existing_indent.len as usize
4123 + indent_on_newline.len as usize
4124 + indent_on_extra_newline.len as usize,
4125 );
4126 new_text.push('\n');
4127 new_text.extend(existing_indent.chars());
4128 new_text.extend(indent_on_newline.chars());
4129
4130 if let Some(delimiter) = &delimiter {
4131 new_text.push_str(delimiter);
4132 }
4133
4134 if insert_extra_newline {
4135 new_text.push('\n');
4136 new_text.extend(existing_indent.chars());
4137 new_text.extend(indent_on_extra_newline.chars());
4138 }
4139
4140 let anchor = buffer.anchor_after(end);
4141 let new_selection = selection.map(|_| anchor);
4142 (
4143 ((start..end, new_text), prevent_auto_indent),
4144 (insert_extra_newline, new_selection),
4145 )
4146 })
4147 .unzip()
4148 };
4149
4150 let mut auto_indent_edits = Vec::new();
4151 let mut edits = Vec::new();
4152 for (edit, prevent_auto_indent) in edits_with_flags {
4153 if prevent_auto_indent {
4154 edits.push(edit);
4155 } else {
4156 auto_indent_edits.push(edit);
4157 }
4158 }
4159 if !edits.is_empty() {
4160 this.edit(edits, cx);
4161 }
4162 if !auto_indent_edits.is_empty() {
4163 this.edit_with_autoindent(auto_indent_edits, cx);
4164 }
4165
4166 let buffer = this.buffer.read(cx).snapshot(cx);
4167 let new_selections = selection_info
4168 .into_iter()
4169 .map(|(extra_newline_inserted, new_selection)| {
4170 let mut cursor = new_selection.end.to_point(&buffer);
4171 if extra_newline_inserted {
4172 cursor.row -= 1;
4173 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4174 }
4175 new_selection.map(|_| cursor)
4176 })
4177 .collect();
4178
4179 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4180 s.select(new_selections)
4181 });
4182 this.refresh_inline_completion(true, false, window, cx);
4183 });
4184 }
4185
4186 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4187 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4188
4189 let buffer = self.buffer.read(cx);
4190 let snapshot = buffer.snapshot(cx);
4191
4192 let mut edits = Vec::new();
4193 let mut rows = Vec::new();
4194
4195 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4196 let cursor = selection.head();
4197 let row = cursor.row;
4198
4199 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4200
4201 let newline = "\n".to_string();
4202 edits.push((start_of_line..start_of_line, newline));
4203
4204 rows.push(row + rows_inserted as u32);
4205 }
4206
4207 self.transact(window, cx, |editor, window, cx| {
4208 editor.edit(edits, cx);
4209
4210 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4211 let mut index = 0;
4212 s.move_cursors_with(|map, _, _| {
4213 let row = rows[index];
4214 index += 1;
4215
4216 let point = Point::new(row, 0);
4217 let boundary = map.next_line_boundary(point).1;
4218 let clipped = map.clip_point(boundary, Bias::Left);
4219
4220 (clipped, SelectionGoal::None)
4221 });
4222 });
4223
4224 let mut indent_edits = Vec::new();
4225 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4226 for row in rows {
4227 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4228 for (row, indent) in indents {
4229 if indent.len == 0 {
4230 continue;
4231 }
4232
4233 let text = match indent.kind {
4234 IndentKind::Space => " ".repeat(indent.len as usize),
4235 IndentKind::Tab => "\t".repeat(indent.len as usize),
4236 };
4237 let point = Point::new(row.0, 0);
4238 indent_edits.push((point..point, text));
4239 }
4240 }
4241 editor.edit(indent_edits, cx);
4242 });
4243 }
4244
4245 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4246 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4247
4248 let buffer = self.buffer.read(cx);
4249 let snapshot = buffer.snapshot(cx);
4250
4251 let mut edits = Vec::new();
4252 let mut rows = Vec::new();
4253 let mut rows_inserted = 0;
4254
4255 for selection in self.selections.all_adjusted(cx) {
4256 let cursor = selection.head();
4257 let row = cursor.row;
4258
4259 let point = Point::new(row + 1, 0);
4260 let start_of_line = snapshot.clip_point(point, Bias::Left);
4261
4262 let newline = "\n".to_string();
4263 edits.push((start_of_line..start_of_line, newline));
4264
4265 rows_inserted += 1;
4266 rows.push(row + rows_inserted);
4267 }
4268
4269 self.transact(window, cx, |editor, window, cx| {
4270 editor.edit(edits, cx);
4271
4272 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4273 let mut index = 0;
4274 s.move_cursors_with(|map, _, _| {
4275 let row = rows[index];
4276 index += 1;
4277
4278 let point = Point::new(row, 0);
4279 let boundary = map.next_line_boundary(point).1;
4280 let clipped = map.clip_point(boundary, Bias::Left);
4281
4282 (clipped, SelectionGoal::None)
4283 });
4284 });
4285
4286 let mut indent_edits = Vec::new();
4287 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4288 for row in rows {
4289 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4290 for (row, indent) in indents {
4291 if indent.len == 0 {
4292 continue;
4293 }
4294
4295 let text = match indent.kind {
4296 IndentKind::Space => " ".repeat(indent.len as usize),
4297 IndentKind::Tab => "\t".repeat(indent.len as usize),
4298 };
4299 let point = Point::new(row.0, 0);
4300 indent_edits.push((point..point, text));
4301 }
4302 }
4303 editor.edit(indent_edits, cx);
4304 });
4305 }
4306
4307 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4308 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4309 original_indent_columns: Vec::new(),
4310 });
4311 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4312 }
4313
4314 fn insert_with_autoindent_mode(
4315 &mut self,
4316 text: &str,
4317 autoindent_mode: Option<AutoindentMode>,
4318 window: &mut Window,
4319 cx: &mut Context<Self>,
4320 ) {
4321 if self.read_only(cx) {
4322 return;
4323 }
4324
4325 let text: Arc<str> = text.into();
4326 self.transact(window, cx, |this, window, cx| {
4327 let old_selections = this.selections.all_adjusted(cx);
4328 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4329 let anchors = {
4330 let snapshot = buffer.read(cx);
4331 old_selections
4332 .iter()
4333 .map(|s| {
4334 let anchor = snapshot.anchor_after(s.head());
4335 s.map(|_| anchor)
4336 })
4337 .collect::<Vec<_>>()
4338 };
4339 buffer.edit(
4340 old_selections
4341 .iter()
4342 .map(|s| (s.start..s.end, text.clone())),
4343 autoindent_mode,
4344 cx,
4345 );
4346 anchors
4347 });
4348
4349 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4350 s.select_anchors(selection_anchors);
4351 });
4352
4353 cx.notify();
4354 });
4355 }
4356
4357 fn trigger_completion_on_input(
4358 &mut self,
4359 text: &str,
4360 trigger_in_words: bool,
4361 window: &mut Window,
4362 cx: &mut Context<Self>,
4363 ) {
4364 let ignore_completion_provider = self
4365 .context_menu
4366 .borrow()
4367 .as_ref()
4368 .map(|menu| match menu {
4369 CodeContextMenu::Completions(completions_menu) => {
4370 completions_menu.ignore_completion_provider
4371 }
4372 CodeContextMenu::CodeActions(_) => false,
4373 })
4374 .unwrap_or(false);
4375
4376 if ignore_completion_provider {
4377 self.show_word_completions(&ShowWordCompletions, window, cx);
4378 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4379 self.show_completions(
4380 &ShowCompletions {
4381 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4382 },
4383 window,
4384 cx,
4385 );
4386 } else {
4387 self.hide_context_menu(window, cx);
4388 }
4389 }
4390
4391 fn is_completion_trigger(
4392 &self,
4393 text: &str,
4394 trigger_in_words: bool,
4395 cx: &mut Context<Self>,
4396 ) -> bool {
4397 let position = self.selections.newest_anchor().head();
4398 let multibuffer = self.buffer.read(cx);
4399 let Some(buffer) = position
4400 .buffer_id
4401 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4402 else {
4403 return false;
4404 };
4405
4406 if let Some(completion_provider) = &self.completion_provider {
4407 completion_provider.is_completion_trigger(
4408 &buffer,
4409 position.text_anchor,
4410 text,
4411 trigger_in_words,
4412 cx,
4413 )
4414 } else {
4415 false
4416 }
4417 }
4418
4419 /// If any empty selections is touching the start of its innermost containing autoclose
4420 /// region, expand it to select the brackets.
4421 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4422 let selections = self.selections.all::<usize>(cx);
4423 let buffer = self.buffer.read(cx).read(cx);
4424 let new_selections = self
4425 .selections_with_autoclose_regions(selections, &buffer)
4426 .map(|(mut selection, region)| {
4427 if !selection.is_empty() {
4428 return selection;
4429 }
4430
4431 if let Some(region) = region {
4432 let mut range = region.range.to_offset(&buffer);
4433 if selection.start == range.start && range.start >= region.pair.start.len() {
4434 range.start -= region.pair.start.len();
4435 if buffer.contains_str_at(range.start, ®ion.pair.start)
4436 && buffer.contains_str_at(range.end, ®ion.pair.end)
4437 {
4438 range.end += region.pair.end.len();
4439 selection.start = range.start;
4440 selection.end = range.end;
4441
4442 return selection;
4443 }
4444 }
4445 }
4446
4447 let always_treat_brackets_as_autoclosed = buffer
4448 .language_settings_at(selection.start, cx)
4449 .always_treat_brackets_as_autoclosed;
4450
4451 if !always_treat_brackets_as_autoclosed {
4452 return selection;
4453 }
4454
4455 if let Some(scope) = buffer.language_scope_at(selection.start) {
4456 for (pair, enabled) in scope.brackets() {
4457 if !enabled || !pair.close {
4458 continue;
4459 }
4460
4461 if buffer.contains_str_at(selection.start, &pair.end) {
4462 let pair_start_len = pair.start.len();
4463 if buffer.contains_str_at(
4464 selection.start.saturating_sub(pair_start_len),
4465 &pair.start,
4466 ) {
4467 selection.start -= pair_start_len;
4468 selection.end += pair.end.len();
4469
4470 return selection;
4471 }
4472 }
4473 }
4474 }
4475
4476 selection
4477 })
4478 .collect();
4479
4480 drop(buffer);
4481 self.change_selections(None, window, cx, |selections| {
4482 selections.select(new_selections)
4483 });
4484 }
4485
4486 /// Iterate the given selections, and for each one, find the smallest surrounding
4487 /// autoclose region. This uses the ordering of the selections and the autoclose
4488 /// regions to avoid repeated comparisons.
4489 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4490 &'a self,
4491 selections: impl IntoIterator<Item = Selection<D>>,
4492 buffer: &'a MultiBufferSnapshot,
4493 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4494 let mut i = 0;
4495 let mut regions = self.autoclose_regions.as_slice();
4496 selections.into_iter().map(move |selection| {
4497 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4498
4499 let mut enclosing = None;
4500 while let Some(pair_state) = regions.get(i) {
4501 if pair_state.range.end.to_offset(buffer) < range.start {
4502 regions = ®ions[i + 1..];
4503 i = 0;
4504 } else if pair_state.range.start.to_offset(buffer) > range.end {
4505 break;
4506 } else {
4507 if pair_state.selection_id == selection.id {
4508 enclosing = Some(pair_state);
4509 }
4510 i += 1;
4511 }
4512 }
4513
4514 (selection, enclosing)
4515 })
4516 }
4517
4518 /// Remove any autoclose regions that no longer contain their selection.
4519 fn invalidate_autoclose_regions(
4520 &mut self,
4521 mut selections: &[Selection<Anchor>],
4522 buffer: &MultiBufferSnapshot,
4523 ) {
4524 self.autoclose_regions.retain(|state| {
4525 let mut i = 0;
4526 while let Some(selection) = selections.get(i) {
4527 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4528 selections = &selections[1..];
4529 continue;
4530 }
4531 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4532 break;
4533 }
4534 if selection.id == state.selection_id {
4535 return true;
4536 } else {
4537 i += 1;
4538 }
4539 }
4540 false
4541 });
4542 }
4543
4544 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4545 let offset = position.to_offset(buffer);
4546 let (word_range, kind) = buffer.surrounding_word(offset, true);
4547 if offset > word_range.start && kind == Some(CharKind::Word) {
4548 Some(
4549 buffer
4550 .text_for_range(word_range.start..offset)
4551 .collect::<String>(),
4552 )
4553 } else {
4554 None
4555 }
4556 }
4557
4558 pub fn toggle_inline_values(
4559 &mut self,
4560 _: &ToggleInlineValues,
4561 _: &mut Window,
4562 cx: &mut Context<Self>,
4563 ) {
4564 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4565
4566 self.refresh_inline_values(cx);
4567 }
4568
4569 pub fn toggle_inlay_hints(
4570 &mut self,
4571 _: &ToggleInlayHints,
4572 _: &mut Window,
4573 cx: &mut Context<Self>,
4574 ) {
4575 self.refresh_inlay_hints(
4576 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4577 cx,
4578 );
4579 }
4580
4581 pub fn inlay_hints_enabled(&self) -> bool {
4582 self.inlay_hint_cache.enabled
4583 }
4584
4585 pub fn inline_values_enabled(&self) -> bool {
4586 self.inline_value_cache.enabled
4587 }
4588
4589 #[cfg(any(test, feature = "test-support"))]
4590 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4591 self.display_map
4592 .read(cx)
4593 .current_inlays()
4594 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4595 .cloned()
4596 .collect()
4597 }
4598
4599 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4600 if self.semantics_provider.is_none() || !self.mode.is_full() {
4601 return;
4602 }
4603
4604 let reason_description = reason.description();
4605 let ignore_debounce = matches!(
4606 reason,
4607 InlayHintRefreshReason::SettingsChange(_)
4608 | InlayHintRefreshReason::Toggle(_)
4609 | InlayHintRefreshReason::ExcerptsRemoved(_)
4610 | InlayHintRefreshReason::ModifiersChanged(_)
4611 );
4612 let (invalidate_cache, required_languages) = match reason {
4613 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4614 match self.inlay_hint_cache.modifiers_override(enabled) {
4615 Some(enabled) => {
4616 if enabled {
4617 (InvalidationStrategy::RefreshRequested, None)
4618 } else {
4619 self.splice_inlays(
4620 &self
4621 .visible_inlay_hints(cx)
4622 .iter()
4623 .map(|inlay| inlay.id)
4624 .collect::<Vec<InlayId>>(),
4625 Vec::new(),
4626 cx,
4627 );
4628 return;
4629 }
4630 }
4631 None => return,
4632 }
4633 }
4634 InlayHintRefreshReason::Toggle(enabled) => {
4635 if self.inlay_hint_cache.toggle(enabled) {
4636 if enabled {
4637 (InvalidationStrategy::RefreshRequested, None)
4638 } else {
4639 self.splice_inlays(
4640 &self
4641 .visible_inlay_hints(cx)
4642 .iter()
4643 .map(|inlay| inlay.id)
4644 .collect::<Vec<InlayId>>(),
4645 Vec::new(),
4646 cx,
4647 );
4648 return;
4649 }
4650 } else {
4651 return;
4652 }
4653 }
4654 InlayHintRefreshReason::SettingsChange(new_settings) => {
4655 match self.inlay_hint_cache.update_settings(
4656 &self.buffer,
4657 new_settings,
4658 self.visible_inlay_hints(cx),
4659 cx,
4660 ) {
4661 ControlFlow::Break(Some(InlaySplice {
4662 to_remove,
4663 to_insert,
4664 })) => {
4665 self.splice_inlays(&to_remove, to_insert, cx);
4666 return;
4667 }
4668 ControlFlow::Break(None) => return,
4669 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4670 }
4671 }
4672 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4673 if let Some(InlaySplice {
4674 to_remove,
4675 to_insert,
4676 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4677 {
4678 self.splice_inlays(&to_remove, to_insert, cx);
4679 }
4680 self.display_map.update(cx, |display_map, _| {
4681 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4682 });
4683 return;
4684 }
4685 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4686 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4687 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4688 }
4689 InlayHintRefreshReason::RefreshRequested => {
4690 (InvalidationStrategy::RefreshRequested, None)
4691 }
4692 };
4693
4694 if let Some(InlaySplice {
4695 to_remove,
4696 to_insert,
4697 }) = self.inlay_hint_cache.spawn_hint_refresh(
4698 reason_description,
4699 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4700 invalidate_cache,
4701 ignore_debounce,
4702 cx,
4703 ) {
4704 self.splice_inlays(&to_remove, to_insert, cx);
4705 }
4706 }
4707
4708 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4709 self.display_map
4710 .read(cx)
4711 .current_inlays()
4712 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4713 .cloned()
4714 .collect()
4715 }
4716
4717 pub fn excerpts_for_inlay_hints_query(
4718 &self,
4719 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4720 cx: &mut Context<Editor>,
4721 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4722 let Some(project) = self.project.as_ref() else {
4723 return HashMap::default();
4724 };
4725 let project = project.read(cx);
4726 let multi_buffer = self.buffer().read(cx);
4727 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4728 let multi_buffer_visible_start = self
4729 .scroll_manager
4730 .anchor()
4731 .anchor
4732 .to_point(&multi_buffer_snapshot);
4733 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4734 multi_buffer_visible_start
4735 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4736 Bias::Left,
4737 );
4738 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4739 multi_buffer_snapshot
4740 .range_to_buffer_ranges(multi_buffer_visible_range)
4741 .into_iter()
4742 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4743 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4744 let buffer_file = project::File::from_dyn(buffer.file())?;
4745 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4746 let worktree_entry = buffer_worktree
4747 .read(cx)
4748 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4749 if worktree_entry.is_ignored {
4750 return None;
4751 }
4752
4753 let language = buffer.language()?;
4754 if let Some(restrict_to_languages) = restrict_to_languages {
4755 if !restrict_to_languages.contains(language) {
4756 return None;
4757 }
4758 }
4759 Some((
4760 excerpt_id,
4761 (
4762 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4763 buffer.version().clone(),
4764 excerpt_visible_range,
4765 ),
4766 ))
4767 })
4768 .collect()
4769 }
4770
4771 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4772 TextLayoutDetails {
4773 text_system: window.text_system().clone(),
4774 editor_style: self.style.clone().unwrap(),
4775 rem_size: window.rem_size(),
4776 scroll_anchor: self.scroll_manager.anchor(),
4777 visible_rows: self.visible_line_count(),
4778 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4779 }
4780 }
4781
4782 pub fn splice_inlays(
4783 &self,
4784 to_remove: &[InlayId],
4785 to_insert: Vec<Inlay>,
4786 cx: &mut Context<Self>,
4787 ) {
4788 self.display_map.update(cx, |display_map, cx| {
4789 display_map.splice_inlays(to_remove, to_insert, cx)
4790 });
4791 cx.notify();
4792 }
4793
4794 fn trigger_on_type_formatting(
4795 &self,
4796 input: String,
4797 window: &mut Window,
4798 cx: &mut Context<Self>,
4799 ) -> Option<Task<Result<()>>> {
4800 if input.len() != 1 {
4801 return None;
4802 }
4803
4804 let project = self.project.as_ref()?;
4805 let position = self.selections.newest_anchor().head();
4806 let (buffer, buffer_position) = self
4807 .buffer
4808 .read(cx)
4809 .text_anchor_for_position(position, cx)?;
4810
4811 let settings = language_settings::language_settings(
4812 buffer
4813 .read(cx)
4814 .language_at(buffer_position)
4815 .map(|l| l.name()),
4816 buffer.read(cx).file(),
4817 cx,
4818 );
4819 if !settings.use_on_type_format {
4820 return None;
4821 }
4822
4823 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4824 // hence we do LSP request & edit on host side only — add formats to host's history.
4825 let push_to_lsp_host_history = true;
4826 // If this is not the host, append its history with new edits.
4827 let push_to_client_history = project.read(cx).is_via_collab();
4828
4829 let on_type_formatting = project.update(cx, |project, cx| {
4830 project.on_type_format(
4831 buffer.clone(),
4832 buffer_position,
4833 input,
4834 push_to_lsp_host_history,
4835 cx,
4836 )
4837 });
4838 Some(cx.spawn_in(window, async move |editor, cx| {
4839 if let Some(transaction) = on_type_formatting.await? {
4840 if push_to_client_history {
4841 buffer
4842 .update(cx, |buffer, _| {
4843 buffer.push_transaction(transaction, Instant::now());
4844 buffer.finalize_last_transaction();
4845 })
4846 .ok();
4847 }
4848 editor.update(cx, |editor, cx| {
4849 editor.refresh_document_highlights(cx);
4850 })?;
4851 }
4852 Ok(())
4853 }))
4854 }
4855
4856 pub fn show_word_completions(
4857 &mut self,
4858 _: &ShowWordCompletions,
4859 window: &mut Window,
4860 cx: &mut Context<Self>,
4861 ) {
4862 self.open_completions_menu(true, None, window, cx);
4863 }
4864
4865 pub fn show_completions(
4866 &mut self,
4867 options: &ShowCompletions,
4868 window: &mut Window,
4869 cx: &mut Context<Self>,
4870 ) {
4871 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4872 }
4873
4874 fn open_completions_menu(
4875 &mut self,
4876 ignore_completion_provider: bool,
4877 trigger: Option<&str>,
4878 window: &mut Window,
4879 cx: &mut Context<Self>,
4880 ) {
4881 if self.pending_rename.is_some() {
4882 return;
4883 }
4884 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4885 return;
4886 }
4887
4888 let position = self.selections.newest_anchor().head();
4889 if position.diff_base_anchor.is_some() {
4890 return;
4891 }
4892 let (buffer, buffer_position) =
4893 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4894 output
4895 } else {
4896 return;
4897 };
4898 let buffer_snapshot = buffer.read(cx).snapshot();
4899 let show_completion_documentation = buffer_snapshot
4900 .settings_at(buffer_position, cx)
4901 .show_completion_documentation;
4902
4903 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4904
4905 let trigger_kind = match trigger {
4906 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4907 CompletionTriggerKind::TRIGGER_CHARACTER
4908 }
4909 _ => CompletionTriggerKind::INVOKED,
4910 };
4911 let completion_context = CompletionContext {
4912 trigger_character: trigger.and_then(|trigger| {
4913 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4914 Some(String::from(trigger))
4915 } else {
4916 None
4917 }
4918 }),
4919 trigger_kind,
4920 };
4921
4922 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4923 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4924 let word_to_exclude = buffer_snapshot
4925 .text_for_range(old_range.clone())
4926 .collect::<String>();
4927 (
4928 buffer_snapshot.anchor_before(old_range.start)
4929 ..buffer_snapshot.anchor_after(old_range.end),
4930 Some(word_to_exclude),
4931 )
4932 } else {
4933 (buffer_position..buffer_position, None)
4934 };
4935
4936 let completion_settings = language_settings(
4937 buffer_snapshot
4938 .language_at(buffer_position)
4939 .map(|language| language.name()),
4940 buffer_snapshot.file(),
4941 cx,
4942 )
4943 .completions;
4944
4945 // The document can be large, so stay in reasonable bounds when searching for words,
4946 // otherwise completion pop-up might be slow to appear.
4947 const WORD_LOOKUP_ROWS: u32 = 5_000;
4948 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4949 let min_word_search = buffer_snapshot.clip_point(
4950 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4951 Bias::Left,
4952 );
4953 let max_word_search = buffer_snapshot.clip_point(
4954 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4955 Bias::Right,
4956 );
4957 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4958 ..buffer_snapshot.point_to_offset(max_word_search);
4959
4960 let provider = self
4961 .completion_provider
4962 .as_ref()
4963 .filter(|_| !ignore_completion_provider);
4964 let skip_digits = query
4965 .as_ref()
4966 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4967
4968 let (mut words, provided_completions) = match provider {
4969 Some(provider) => {
4970 let completions = provider.completions(
4971 position.excerpt_id,
4972 &buffer,
4973 buffer_position,
4974 completion_context,
4975 window,
4976 cx,
4977 );
4978
4979 let words = match completion_settings.words {
4980 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4981 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4982 .background_spawn(async move {
4983 buffer_snapshot.words_in_range(WordsQuery {
4984 fuzzy_contents: None,
4985 range: word_search_range,
4986 skip_digits,
4987 })
4988 }),
4989 };
4990
4991 (words, completions)
4992 }
4993 None => (
4994 cx.background_spawn(async move {
4995 buffer_snapshot.words_in_range(WordsQuery {
4996 fuzzy_contents: None,
4997 range: word_search_range,
4998 skip_digits,
4999 })
5000 }),
5001 Task::ready(Ok(None)),
5002 ),
5003 };
5004
5005 let sort_completions = provider
5006 .as_ref()
5007 .map_or(false, |provider| provider.sort_completions());
5008
5009 let filter_completions = provider
5010 .as_ref()
5011 .map_or(true, |provider| provider.filter_completions());
5012
5013 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5014
5015 let id = post_inc(&mut self.next_completion_id);
5016 let task = cx.spawn_in(window, async move |editor, cx| {
5017 async move {
5018 editor.update(cx, |this, _| {
5019 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5020 })?;
5021
5022 let mut completions = Vec::new();
5023 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5024 completions.extend(provided_completions);
5025 if completion_settings.words == WordsCompletionMode::Fallback {
5026 words = Task::ready(BTreeMap::default());
5027 }
5028 }
5029
5030 let mut words = words.await;
5031 if let Some(word_to_exclude) = &word_to_exclude {
5032 words.remove(word_to_exclude);
5033 }
5034 for lsp_completion in &completions {
5035 words.remove(&lsp_completion.new_text);
5036 }
5037 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5038 replace_range: old_range.clone(),
5039 new_text: word.clone(),
5040 label: CodeLabel::plain(word, None),
5041 icon_path: None,
5042 documentation: None,
5043 source: CompletionSource::BufferWord {
5044 word_range,
5045 resolved: false,
5046 },
5047 insert_text_mode: Some(InsertTextMode::AS_IS),
5048 confirm: None,
5049 }));
5050
5051 let menu = if completions.is_empty() {
5052 None
5053 } else {
5054 let mut menu = CompletionsMenu::new(
5055 id,
5056 sort_completions,
5057 show_completion_documentation,
5058 ignore_completion_provider,
5059 position,
5060 buffer.clone(),
5061 completions.into(),
5062 snippet_sort_order,
5063 );
5064
5065 menu.filter(
5066 if filter_completions {
5067 query.as_deref()
5068 } else {
5069 None
5070 },
5071 cx.background_executor().clone(),
5072 )
5073 .await;
5074
5075 menu.visible().then_some(menu)
5076 };
5077
5078 editor.update_in(cx, |editor, window, cx| {
5079 match editor.context_menu.borrow().as_ref() {
5080 None => {}
5081 Some(CodeContextMenu::Completions(prev_menu)) => {
5082 if prev_menu.id > id {
5083 return;
5084 }
5085 }
5086 _ => return,
5087 }
5088
5089 if editor.focus_handle.is_focused(window) && menu.is_some() {
5090 let mut menu = menu.unwrap();
5091 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5092 crate::hover_popover::hide_hover(editor, cx);
5093 *editor.context_menu.borrow_mut() =
5094 Some(CodeContextMenu::Completions(menu));
5095
5096 if editor.show_edit_predictions_in_menu() {
5097 editor.update_visible_inline_completion(window, cx);
5098 } else {
5099 editor.discard_inline_completion(false, cx);
5100 }
5101
5102 cx.notify();
5103 } else if editor.completion_tasks.len() <= 1 {
5104 // If there are no more completion tasks and the last menu was
5105 // empty, we should hide it.
5106 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5107 // If it was already hidden and we don't show inline
5108 // completions in the menu, we should also show the
5109 // inline-completion when available.
5110 if was_hidden && editor.show_edit_predictions_in_menu() {
5111 editor.update_visible_inline_completion(window, cx);
5112 }
5113 }
5114 })?;
5115
5116 anyhow::Ok(())
5117 }
5118 .log_err()
5119 .await
5120 });
5121
5122 self.completion_tasks.push((id, task));
5123 }
5124
5125 #[cfg(feature = "test-support")]
5126 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5127 let menu = self.context_menu.borrow();
5128 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5129 let completions = menu.completions.borrow();
5130 Some(completions.to_vec())
5131 } else {
5132 None
5133 }
5134 }
5135
5136 pub fn confirm_completion(
5137 &mut self,
5138 action: &ConfirmCompletion,
5139 window: &mut Window,
5140 cx: &mut Context<Self>,
5141 ) -> Option<Task<Result<()>>> {
5142 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5143 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5144 }
5145
5146 pub fn confirm_completion_insert(
5147 &mut self,
5148 _: &ConfirmCompletionInsert,
5149 window: &mut Window,
5150 cx: &mut Context<Self>,
5151 ) -> Option<Task<Result<()>>> {
5152 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5153 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5154 }
5155
5156 pub fn confirm_completion_replace(
5157 &mut self,
5158 _: &ConfirmCompletionReplace,
5159 window: &mut Window,
5160 cx: &mut Context<Self>,
5161 ) -> Option<Task<Result<()>>> {
5162 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5163 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5164 }
5165
5166 pub fn compose_completion(
5167 &mut self,
5168 action: &ComposeCompletion,
5169 window: &mut Window,
5170 cx: &mut Context<Self>,
5171 ) -> Option<Task<Result<()>>> {
5172 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5173 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5174 }
5175
5176 fn do_completion(
5177 &mut self,
5178 item_ix: Option<usize>,
5179 intent: CompletionIntent,
5180 window: &mut Window,
5181 cx: &mut Context<Editor>,
5182 ) -> Option<Task<Result<()>>> {
5183 use language::ToOffset as _;
5184
5185 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5186 else {
5187 return None;
5188 };
5189
5190 let candidate_id = {
5191 let entries = completions_menu.entries.borrow();
5192 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5193 if self.show_edit_predictions_in_menu() {
5194 self.discard_inline_completion(true, cx);
5195 }
5196 mat.candidate_id
5197 };
5198
5199 let buffer_handle = completions_menu.buffer;
5200 let completion = completions_menu
5201 .completions
5202 .borrow()
5203 .get(candidate_id)?
5204 .clone();
5205 cx.stop_propagation();
5206
5207 let snapshot = self.buffer.read(cx).snapshot(cx);
5208 let newest_anchor = self.selections.newest_anchor();
5209
5210 let snippet;
5211 let new_text;
5212 if completion.is_snippet() {
5213 let mut snippet_source = completion.new_text.clone();
5214 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5215 if scope.prefers_label_for_snippet_in_completion() {
5216 if let Some(label) = completion.label() {
5217 if matches!(
5218 completion.kind(),
5219 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5220 ) {
5221 snippet_source = label;
5222 }
5223 }
5224 }
5225 }
5226 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5227 new_text = snippet.as_ref().unwrap().text.clone();
5228 } else {
5229 snippet = None;
5230 new_text = completion.new_text.clone();
5231 };
5232
5233 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5234 let buffer = buffer_handle.read(cx);
5235 let replace_range_multibuffer = {
5236 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5237 let multibuffer_anchor = snapshot
5238 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5239 .unwrap()
5240 ..snapshot
5241 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5242 .unwrap();
5243 multibuffer_anchor.start.to_offset(&snapshot)
5244 ..multibuffer_anchor.end.to_offset(&snapshot)
5245 };
5246 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5247 return None;
5248 }
5249
5250 let old_text = buffer
5251 .text_for_range(replace_range.clone())
5252 .collect::<String>();
5253 let lookbehind = newest_anchor
5254 .start
5255 .text_anchor
5256 .to_offset(buffer)
5257 .saturating_sub(replace_range.start);
5258 let lookahead = replace_range
5259 .end
5260 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5261 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5262 let suffix = &old_text[lookbehind.min(old_text.len())..];
5263
5264 let selections = self.selections.all::<usize>(cx);
5265 let mut ranges = Vec::new();
5266 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5267
5268 for selection in &selections {
5269 let range = if selection.id == newest_anchor.id {
5270 replace_range_multibuffer.clone()
5271 } else {
5272 let mut range = selection.range();
5273
5274 // if prefix is present, don't duplicate it
5275 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5276 range.start = range.start.saturating_sub(lookbehind);
5277
5278 // if suffix is also present, mimic the newest cursor and replace it
5279 if selection.id != newest_anchor.id
5280 && snapshot.contains_str_at(range.end, suffix)
5281 {
5282 range.end += lookahead;
5283 }
5284 }
5285 range
5286 };
5287
5288 ranges.push(range.clone());
5289
5290 if !self.linked_edit_ranges.is_empty() {
5291 let start_anchor = snapshot.anchor_before(range.start);
5292 let end_anchor = snapshot.anchor_after(range.end);
5293 if let Some(ranges) = self
5294 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5295 {
5296 for (buffer, edits) in ranges {
5297 linked_edits
5298 .entry(buffer.clone())
5299 .or_default()
5300 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5301 }
5302 }
5303 }
5304 }
5305
5306 cx.emit(EditorEvent::InputHandled {
5307 utf16_range_to_replace: None,
5308 text: new_text.clone().into(),
5309 });
5310
5311 self.transact(window, cx, |this, window, cx| {
5312 if let Some(mut snippet) = snippet {
5313 snippet.text = new_text.to_string();
5314 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5315 } else {
5316 this.buffer.update(cx, |buffer, cx| {
5317 let auto_indent = match completion.insert_text_mode {
5318 Some(InsertTextMode::AS_IS) => None,
5319 _ => this.autoindent_mode.clone(),
5320 };
5321 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5322 buffer.edit(edits, auto_indent, cx);
5323 });
5324 }
5325 for (buffer, edits) in linked_edits {
5326 buffer.update(cx, |buffer, cx| {
5327 let snapshot = buffer.snapshot();
5328 let edits = edits
5329 .into_iter()
5330 .map(|(range, text)| {
5331 use text::ToPoint as TP;
5332 let end_point = TP::to_point(&range.end, &snapshot);
5333 let start_point = TP::to_point(&range.start, &snapshot);
5334 (start_point..end_point, text)
5335 })
5336 .sorted_by_key(|(range, _)| range.start);
5337 buffer.edit(edits, None, cx);
5338 })
5339 }
5340
5341 this.refresh_inline_completion(true, false, window, cx);
5342 });
5343
5344 let show_new_completions_on_confirm = completion
5345 .confirm
5346 .as_ref()
5347 .map_or(false, |confirm| confirm(intent, window, cx));
5348 if show_new_completions_on_confirm {
5349 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5350 }
5351
5352 let provider = self.completion_provider.as_ref()?;
5353 drop(completion);
5354 let apply_edits = provider.apply_additional_edits_for_completion(
5355 buffer_handle,
5356 completions_menu.completions.clone(),
5357 candidate_id,
5358 true,
5359 cx,
5360 );
5361
5362 let editor_settings = EditorSettings::get_global(cx);
5363 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5364 // After the code completion is finished, users often want to know what signatures are needed.
5365 // so we should automatically call signature_help
5366 self.show_signature_help(&ShowSignatureHelp, window, cx);
5367 }
5368
5369 Some(cx.foreground_executor().spawn(async move {
5370 apply_edits.await?;
5371 Ok(())
5372 }))
5373 }
5374
5375 pub fn toggle_code_actions(
5376 &mut self,
5377 action: &ToggleCodeActions,
5378 window: &mut Window,
5379 cx: &mut Context<Self>,
5380 ) {
5381 let quick_launch = action.quick_launch;
5382 let mut context_menu = self.context_menu.borrow_mut();
5383 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5384 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5385 // Toggle if we're selecting the same one
5386 *context_menu = None;
5387 cx.notify();
5388 return;
5389 } else {
5390 // Otherwise, clear it and start a new one
5391 *context_menu = None;
5392 cx.notify();
5393 }
5394 }
5395 drop(context_menu);
5396 let snapshot = self.snapshot(window, cx);
5397 let deployed_from_indicator = action.deployed_from_indicator;
5398 let mut task = self.code_actions_task.take();
5399 let action = action.clone();
5400 cx.spawn_in(window, async move |editor, cx| {
5401 while let Some(prev_task) = task {
5402 prev_task.await.log_err();
5403 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5404 }
5405
5406 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5407 if editor.focus_handle.is_focused(window) {
5408 let multibuffer_point = action
5409 .deployed_from_indicator
5410 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5411 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5412 let (buffer, buffer_row) = snapshot
5413 .buffer_snapshot
5414 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5415 .and_then(|(buffer_snapshot, range)| {
5416 editor
5417 .buffer
5418 .read(cx)
5419 .buffer(buffer_snapshot.remote_id())
5420 .map(|buffer| (buffer, range.start.row))
5421 })?;
5422 let (_, code_actions) = editor
5423 .available_code_actions
5424 .clone()
5425 .and_then(|(location, code_actions)| {
5426 let snapshot = location.buffer.read(cx).snapshot();
5427 let point_range = location.range.to_point(&snapshot);
5428 let point_range = point_range.start.row..=point_range.end.row;
5429 if point_range.contains(&buffer_row) {
5430 Some((location, code_actions))
5431 } else {
5432 None
5433 }
5434 })
5435 .unzip();
5436 let buffer_id = buffer.read(cx).remote_id();
5437 let tasks = editor
5438 .tasks
5439 .get(&(buffer_id, buffer_row))
5440 .map(|t| Arc::new(t.to_owned()));
5441 if tasks.is_none() && code_actions.is_none() {
5442 return None;
5443 }
5444
5445 editor.completion_tasks.clear();
5446 editor.discard_inline_completion(false, cx);
5447 let task_context =
5448 tasks
5449 .as_ref()
5450 .zip(editor.project.clone())
5451 .map(|(tasks, project)| {
5452 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5453 });
5454
5455 Some(cx.spawn_in(window, async move |editor, cx| {
5456 let task_context = match task_context {
5457 Some(task_context) => task_context.await,
5458 None => None,
5459 };
5460 let resolved_tasks =
5461 tasks
5462 .zip(task_context.clone())
5463 .map(|(tasks, task_context)| ResolvedTasks {
5464 templates: tasks.resolve(&task_context).collect(),
5465 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5466 multibuffer_point.row,
5467 tasks.column,
5468 )),
5469 });
5470 let debug_scenarios = editor.update(cx, |editor, cx| {
5471 if cx.has_flag::<DebuggerFeatureFlag>() {
5472 maybe!({
5473 let project = editor.project.as_ref()?;
5474 let dap_store = project.read(cx).dap_store();
5475 let mut scenarios = vec![];
5476 let resolved_tasks = resolved_tasks.as_ref()?;
5477 let buffer = buffer.read(cx);
5478 let language = buffer.language()?;
5479 let file = buffer.file();
5480 let debug_adapter =
5481 language_settings(language.name().into(), file, cx)
5482 .debuggers
5483 .first()
5484 .map(SharedString::from)
5485 .or_else(|| {
5486 language
5487 .config()
5488 .debuggers
5489 .first()
5490 .map(SharedString::from)
5491 })?;
5492
5493 dap_store.update(cx, |dap_store, cx| {
5494 for (_, task) in &resolved_tasks.templates {
5495 if let Some(scenario) = dap_store
5496 .debug_scenario_for_build_task(
5497 task.original_task().clone(),
5498 debug_adapter.clone().into(),
5499 task.display_label().to_owned().into(),
5500 cx,
5501 )
5502 {
5503 scenarios.push(scenario);
5504 }
5505 }
5506 });
5507 Some(scenarios)
5508 })
5509 .unwrap_or_default()
5510 } else {
5511 vec![]
5512 }
5513 })?;
5514 let spawn_straight_away = quick_launch
5515 && resolved_tasks
5516 .as_ref()
5517 .map_or(false, |tasks| tasks.templates.len() == 1)
5518 && code_actions
5519 .as_ref()
5520 .map_or(true, |actions| actions.is_empty())
5521 && debug_scenarios.is_empty();
5522 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5523 crate::hover_popover::hide_hover(editor, cx);
5524 *editor.context_menu.borrow_mut() =
5525 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5526 buffer,
5527 actions: CodeActionContents::new(
5528 resolved_tasks,
5529 code_actions,
5530 debug_scenarios,
5531 task_context.unwrap_or_default(),
5532 ),
5533 selected_item: Default::default(),
5534 scroll_handle: UniformListScrollHandle::default(),
5535 deployed_from_indicator,
5536 }));
5537 if spawn_straight_away {
5538 if let Some(task) = editor.confirm_code_action(
5539 &ConfirmCodeAction { item_ix: Some(0) },
5540 window,
5541 cx,
5542 ) {
5543 cx.notify();
5544 return task;
5545 }
5546 }
5547 cx.notify();
5548 Task::ready(Ok(()))
5549 }) {
5550 task.await
5551 } else {
5552 Ok(())
5553 }
5554 }))
5555 } else {
5556 Some(Task::ready(Ok(())))
5557 }
5558 })?;
5559 if let Some(task) = spawned_test_task {
5560 task.await?;
5561 }
5562
5563 anyhow::Ok(())
5564 })
5565 .detach_and_log_err(cx);
5566 }
5567
5568 pub fn confirm_code_action(
5569 &mut self,
5570 action: &ConfirmCodeAction,
5571 window: &mut Window,
5572 cx: &mut Context<Self>,
5573 ) -> Option<Task<Result<()>>> {
5574 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5575
5576 let actions_menu =
5577 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5578 menu
5579 } else {
5580 return None;
5581 };
5582
5583 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5584 let action = actions_menu.actions.get(action_ix)?;
5585 let title = action.label();
5586 let buffer = actions_menu.buffer;
5587 let workspace = self.workspace()?;
5588
5589 match action {
5590 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5591 workspace.update(cx, |workspace, cx| {
5592 workspace.schedule_resolved_task(
5593 task_source_kind,
5594 resolved_task,
5595 false,
5596 window,
5597 cx,
5598 );
5599
5600 Some(Task::ready(Ok(())))
5601 })
5602 }
5603 CodeActionsItem::CodeAction {
5604 excerpt_id,
5605 action,
5606 provider,
5607 } => {
5608 let apply_code_action =
5609 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5610 let workspace = workspace.downgrade();
5611 Some(cx.spawn_in(window, async move |editor, cx| {
5612 let project_transaction = apply_code_action.await?;
5613 Self::open_project_transaction(
5614 &editor,
5615 workspace,
5616 project_transaction,
5617 title,
5618 cx,
5619 )
5620 .await
5621 }))
5622 }
5623 CodeActionsItem::DebugScenario(scenario) => {
5624 let context = actions_menu.actions.context.clone();
5625
5626 workspace.update(cx, |workspace, cx| {
5627 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5628 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5629 });
5630 Some(Task::ready(Ok(())))
5631 }
5632 }
5633 }
5634
5635 pub async fn open_project_transaction(
5636 this: &WeakEntity<Editor>,
5637 workspace: WeakEntity<Workspace>,
5638 transaction: ProjectTransaction,
5639 title: String,
5640 cx: &mut AsyncWindowContext,
5641 ) -> Result<()> {
5642 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5643 cx.update(|_, cx| {
5644 entries.sort_unstable_by_key(|(buffer, _)| {
5645 buffer.read(cx).file().map(|f| f.path().clone())
5646 });
5647 })?;
5648
5649 // If the project transaction's edits are all contained within this editor, then
5650 // avoid opening a new editor to display them.
5651
5652 if let Some((buffer, transaction)) = entries.first() {
5653 if entries.len() == 1 {
5654 let excerpt = this.update(cx, |editor, cx| {
5655 editor
5656 .buffer()
5657 .read(cx)
5658 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5659 })?;
5660 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5661 if excerpted_buffer == *buffer {
5662 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5663 let excerpt_range = excerpt_range.to_offset(buffer);
5664 buffer
5665 .edited_ranges_for_transaction::<usize>(transaction)
5666 .all(|range| {
5667 excerpt_range.start <= range.start
5668 && excerpt_range.end >= range.end
5669 })
5670 })?;
5671
5672 if all_edits_within_excerpt {
5673 return Ok(());
5674 }
5675 }
5676 }
5677 }
5678 } else {
5679 return Ok(());
5680 }
5681
5682 let mut ranges_to_highlight = Vec::new();
5683 let excerpt_buffer = cx.new(|cx| {
5684 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5685 for (buffer_handle, transaction) in &entries {
5686 let edited_ranges = buffer_handle
5687 .read(cx)
5688 .edited_ranges_for_transaction::<Point>(transaction)
5689 .collect::<Vec<_>>();
5690 let (ranges, _) = multibuffer.set_excerpts_for_path(
5691 PathKey::for_buffer(buffer_handle, cx),
5692 buffer_handle.clone(),
5693 edited_ranges,
5694 DEFAULT_MULTIBUFFER_CONTEXT,
5695 cx,
5696 );
5697
5698 ranges_to_highlight.extend(ranges);
5699 }
5700 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5701 multibuffer
5702 })?;
5703
5704 workspace.update_in(cx, |workspace, window, cx| {
5705 let project = workspace.project().clone();
5706 let editor =
5707 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5708 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5709 editor.update(cx, |editor, cx| {
5710 editor.highlight_background::<Self>(
5711 &ranges_to_highlight,
5712 |theme| theme.editor_highlighted_line_background,
5713 cx,
5714 );
5715 });
5716 })?;
5717
5718 Ok(())
5719 }
5720
5721 pub fn clear_code_action_providers(&mut self) {
5722 self.code_action_providers.clear();
5723 self.available_code_actions.take();
5724 }
5725
5726 pub fn add_code_action_provider(
5727 &mut self,
5728 provider: Rc<dyn CodeActionProvider>,
5729 window: &mut Window,
5730 cx: &mut Context<Self>,
5731 ) {
5732 if self
5733 .code_action_providers
5734 .iter()
5735 .any(|existing_provider| existing_provider.id() == provider.id())
5736 {
5737 return;
5738 }
5739
5740 self.code_action_providers.push(provider);
5741 self.refresh_code_actions(window, cx);
5742 }
5743
5744 pub fn remove_code_action_provider(
5745 &mut self,
5746 id: Arc<str>,
5747 window: &mut Window,
5748 cx: &mut Context<Self>,
5749 ) {
5750 self.code_action_providers
5751 .retain(|provider| provider.id() != id);
5752 self.refresh_code_actions(window, cx);
5753 }
5754
5755 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5756 let newest_selection = self.selections.newest_anchor().clone();
5757 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5758 let buffer = self.buffer.read(cx);
5759 if newest_selection.head().diff_base_anchor.is_some() {
5760 return None;
5761 }
5762 let (start_buffer, start) =
5763 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5764 let (end_buffer, end) =
5765 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5766 if start_buffer != end_buffer {
5767 return None;
5768 }
5769
5770 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5771 cx.background_executor()
5772 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5773 .await;
5774
5775 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5776 let providers = this.code_action_providers.clone();
5777 let tasks = this
5778 .code_action_providers
5779 .iter()
5780 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5781 .collect::<Vec<_>>();
5782 (providers, tasks)
5783 })?;
5784
5785 let mut actions = Vec::new();
5786 for (provider, provider_actions) in
5787 providers.into_iter().zip(future::join_all(tasks).await)
5788 {
5789 if let Some(provider_actions) = provider_actions.log_err() {
5790 actions.extend(provider_actions.into_iter().map(|action| {
5791 AvailableCodeAction {
5792 excerpt_id: newest_selection.start.excerpt_id,
5793 action,
5794 provider: provider.clone(),
5795 }
5796 }));
5797 }
5798 }
5799
5800 this.update(cx, |this, cx| {
5801 this.available_code_actions = if actions.is_empty() {
5802 None
5803 } else {
5804 Some((
5805 Location {
5806 buffer: start_buffer,
5807 range: start..end,
5808 },
5809 actions.into(),
5810 ))
5811 };
5812 cx.notify();
5813 })
5814 }));
5815 None
5816 }
5817
5818 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5819 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5820 self.show_git_blame_inline = false;
5821
5822 self.show_git_blame_inline_delay_task =
5823 Some(cx.spawn_in(window, async move |this, cx| {
5824 cx.background_executor().timer(delay).await;
5825
5826 this.update(cx, |this, cx| {
5827 this.show_git_blame_inline = true;
5828 cx.notify();
5829 })
5830 .log_err();
5831 }));
5832 }
5833 }
5834
5835 fn show_blame_popover(
5836 &mut self,
5837 blame_entry: &BlameEntry,
5838 position: gpui::Point<Pixels>,
5839 cx: &mut Context<Self>,
5840 ) {
5841 if let Some(state) = &mut self.inline_blame_popover {
5842 state.hide_task.take();
5843 cx.notify();
5844 } else {
5845 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5846 let show_task = cx.spawn(async move |editor, cx| {
5847 cx.background_executor()
5848 .timer(std::time::Duration::from_millis(delay))
5849 .await;
5850 editor
5851 .update(cx, |editor, cx| {
5852 if let Some(state) = &mut editor.inline_blame_popover {
5853 state.show_task = None;
5854 cx.notify();
5855 }
5856 })
5857 .ok();
5858 });
5859 let Some(blame) = self.blame.as_ref() else {
5860 return;
5861 };
5862 let blame = blame.read(cx);
5863 let details = blame.details_for_entry(&blame_entry);
5864 let markdown = cx.new(|cx| {
5865 Markdown::new(
5866 details
5867 .as_ref()
5868 .map(|message| message.message.clone())
5869 .unwrap_or_default(),
5870 None,
5871 None,
5872 cx,
5873 )
5874 });
5875 self.inline_blame_popover = Some(InlineBlamePopover {
5876 position,
5877 show_task: Some(show_task),
5878 hide_task: None,
5879 popover_bounds: None,
5880 popover_state: InlineBlamePopoverState {
5881 scroll_handle: ScrollHandle::new(),
5882 commit_message: details,
5883 markdown,
5884 },
5885 });
5886 }
5887 }
5888
5889 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5890 if let Some(state) = &mut self.inline_blame_popover {
5891 if state.show_task.is_some() {
5892 self.inline_blame_popover.take();
5893 cx.notify();
5894 } else {
5895 let hide_task = cx.spawn(async move |editor, cx| {
5896 cx.background_executor()
5897 .timer(std::time::Duration::from_millis(100))
5898 .await;
5899 editor
5900 .update(cx, |editor, cx| {
5901 editor.inline_blame_popover.take();
5902 cx.notify();
5903 })
5904 .ok();
5905 });
5906 state.hide_task = Some(hide_task);
5907 }
5908 }
5909 }
5910
5911 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5912 if self.pending_rename.is_some() {
5913 return None;
5914 }
5915
5916 let provider = self.semantics_provider.clone()?;
5917 let buffer = self.buffer.read(cx);
5918 let newest_selection = self.selections.newest_anchor().clone();
5919 let cursor_position = newest_selection.head();
5920 let (cursor_buffer, cursor_buffer_position) =
5921 buffer.text_anchor_for_position(cursor_position, cx)?;
5922 let (tail_buffer, tail_buffer_position) =
5923 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5924 if cursor_buffer != tail_buffer {
5925 return None;
5926 }
5927
5928 let snapshot = cursor_buffer.read(cx).snapshot();
5929 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5930 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5931 if start_word_range != end_word_range {
5932 self.document_highlights_task.take();
5933 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5934 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5935 return None;
5936 }
5937
5938 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5939 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5940 cx.background_executor()
5941 .timer(Duration::from_millis(debounce))
5942 .await;
5943
5944 let highlights = if let Some(highlights) = cx
5945 .update(|cx| {
5946 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5947 })
5948 .ok()
5949 .flatten()
5950 {
5951 highlights.await.log_err()
5952 } else {
5953 None
5954 };
5955
5956 if let Some(highlights) = highlights {
5957 this.update(cx, |this, cx| {
5958 if this.pending_rename.is_some() {
5959 return;
5960 }
5961
5962 let buffer_id = cursor_position.buffer_id;
5963 let buffer = this.buffer.read(cx);
5964 if !buffer
5965 .text_anchor_for_position(cursor_position, cx)
5966 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5967 {
5968 return;
5969 }
5970
5971 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5972 let mut write_ranges = Vec::new();
5973 let mut read_ranges = Vec::new();
5974 for highlight in highlights {
5975 for (excerpt_id, excerpt_range) in
5976 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5977 {
5978 let start = highlight
5979 .range
5980 .start
5981 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5982 let end = highlight
5983 .range
5984 .end
5985 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5986 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5987 continue;
5988 }
5989
5990 let range = Anchor {
5991 buffer_id,
5992 excerpt_id,
5993 text_anchor: start,
5994 diff_base_anchor: None,
5995 }..Anchor {
5996 buffer_id,
5997 excerpt_id,
5998 text_anchor: end,
5999 diff_base_anchor: None,
6000 };
6001 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6002 write_ranges.push(range);
6003 } else {
6004 read_ranges.push(range);
6005 }
6006 }
6007 }
6008
6009 this.highlight_background::<DocumentHighlightRead>(
6010 &read_ranges,
6011 |theme| theme.editor_document_highlight_read_background,
6012 cx,
6013 );
6014 this.highlight_background::<DocumentHighlightWrite>(
6015 &write_ranges,
6016 |theme| theme.editor_document_highlight_write_background,
6017 cx,
6018 );
6019 cx.notify();
6020 })
6021 .log_err();
6022 }
6023 }));
6024 None
6025 }
6026
6027 fn prepare_highlight_query_from_selection(
6028 &mut self,
6029 cx: &mut Context<Editor>,
6030 ) -> Option<(String, Range<Anchor>)> {
6031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6032 return None;
6033 }
6034 if !EditorSettings::get_global(cx).selection_highlight {
6035 return None;
6036 }
6037 if self.selections.count() != 1 || self.selections.line_mode {
6038 return None;
6039 }
6040 let selection = self.selections.newest::<Point>(cx);
6041 if selection.is_empty() || selection.start.row != selection.end.row {
6042 return None;
6043 }
6044 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6045 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6046 let query = multi_buffer_snapshot
6047 .text_for_range(selection_anchor_range.clone())
6048 .collect::<String>();
6049 if query.trim().is_empty() {
6050 return None;
6051 }
6052 Some((query, selection_anchor_range))
6053 }
6054
6055 fn update_selection_occurrence_highlights(
6056 &mut self,
6057 query_text: String,
6058 query_range: Range<Anchor>,
6059 multi_buffer_range_to_query: Range<Point>,
6060 use_debounce: bool,
6061 window: &mut Window,
6062 cx: &mut Context<Editor>,
6063 ) -> Task<()> {
6064 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6065 cx.spawn_in(window, async move |editor, cx| {
6066 if use_debounce {
6067 cx.background_executor()
6068 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6069 .await;
6070 }
6071 let match_task = cx.background_spawn(async move {
6072 let buffer_ranges = multi_buffer_snapshot
6073 .range_to_buffer_ranges(multi_buffer_range_to_query)
6074 .into_iter()
6075 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6076 let mut match_ranges = Vec::new();
6077 let Ok(regex) = project::search::SearchQuery::text(
6078 query_text.clone(),
6079 false,
6080 false,
6081 false,
6082 Default::default(),
6083 Default::default(),
6084 false,
6085 None,
6086 ) else {
6087 return Vec::default();
6088 };
6089 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6090 match_ranges.extend(
6091 regex
6092 .search(&buffer_snapshot, Some(search_range.clone()))
6093 .await
6094 .into_iter()
6095 .filter_map(|match_range| {
6096 let match_start = buffer_snapshot
6097 .anchor_after(search_range.start + match_range.start);
6098 let match_end = buffer_snapshot
6099 .anchor_before(search_range.start + match_range.end);
6100 let match_anchor_range = Anchor::range_in_buffer(
6101 excerpt_id,
6102 buffer_snapshot.remote_id(),
6103 match_start..match_end,
6104 );
6105 (match_anchor_range != query_range).then_some(match_anchor_range)
6106 }),
6107 );
6108 }
6109 match_ranges
6110 });
6111 let match_ranges = match_task.await;
6112 editor
6113 .update_in(cx, |editor, _, cx| {
6114 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6115 if !match_ranges.is_empty() {
6116 editor.highlight_background::<SelectedTextHighlight>(
6117 &match_ranges,
6118 |theme| theme.editor_document_highlight_bracket_background,
6119 cx,
6120 )
6121 }
6122 })
6123 .log_err();
6124 })
6125 }
6126
6127 fn refresh_selected_text_highlights(
6128 &mut self,
6129 on_buffer_edit: bool,
6130 window: &mut Window,
6131 cx: &mut Context<Editor>,
6132 ) {
6133 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6134 else {
6135 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6136 self.quick_selection_highlight_task.take();
6137 self.debounced_selection_highlight_task.take();
6138 return;
6139 };
6140 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6141 if on_buffer_edit
6142 || self
6143 .quick_selection_highlight_task
6144 .as_ref()
6145 .map_or(true, |(prev_anchor_range, _)| {
6146 prev_anchor_range != &query_range
6147 })
6148 {
6149 let multi_buffer_visible_start = self
6150 .scroll_manager
6151 .anchor()
6152 .anchor
6153 .to_point(&multi_buffer_snapshot);
6154 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6155 multi_buffer_visible_start
6156 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6157 Bias::Left,
6158 );
6159 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6160 self.quick_selection_highlight_task = Some((
6161 query_range.clone(),
6162 self.update_selection_occurrence_highlights(
6163 query_text.clone(),
6164 query_range.clone(),
6165 multi_buffer_visible_range,
6166 false,
6167 window,
6168 cx,
6169 ),
6170 ));
6171 }
6172 if on_buffer_edit
6173 || self
6174 .debounced_selection_highlight_task
6175 .as_ref()
6176 .map_or(true, |(prev_anchor_range, _)| {
6177 prev_anchor_range != &query_range
6178 })
6179 {
6180 let multi_buffer_start = multi_buffer_snapshot
6181 .anchor_before(0)
6182 .to_point(&multi_buffer_snapshot);
6183 let multi_buffer_end = multi_buffer_snapshot
6184 .anchor_after(multi_buffer_snapshot.len())
6185 .to_point(&multi_buffer_snapshot);
6186 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6187 self.debounced_selection_highlight_task = Some((
6188 query_range.clone(),
6189 self.update_selection_occurrence_highlights(
6190 query_text,
6191 query_range,
6192 multi_buffer_full_range,
6193 true,
6194 window,
6195 cx,
6196 ),
6197 ));
6198 }
6199 }
6200
6201 pub fn refresh_inline_completion(
6202 &mut self,
6203 debounce: bool,
6204 user_requested: bool,
6205 window: &mut Window,
6206 cx: &mut Context<Self>,
6207 ) -> Option<()> {
6208 let provider = self.edit_prediction_provider()?;
6209 let cursor = self.selections.newest_anchor().head();
6210 let (buffer, cursor_buffer_position) =
6211 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6212
6213 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6214 self.discard_inline_completion(false, cx);
6215 return None;
6216 }
6217
6218 if !user_requested
6219 && (!self.should_show_edit_predictions()
6220 || !self.is_focused(window)
6221 || buffer.read(cx).is_empty())
6222 {
6223 self.discard_inline_completion(false, cx);
6224 return None;
6225 }
6226
6227 self.update_visible_inline_completion(window, cx);
6228 provider.refresh(
6229 self.project.clone(),
6230 buffer,
6231 cursor_buffer_position,
6232 debounce,
6233 cx,
6234 );
6235 Some(())
6236 }
6237
6238 fn show_edit_predictions_in_menu(&self) -> bool {
6239 match self.edit_prediction_settings {
6240 EditPredictionSettings::Disabled => false,
6241 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6242 }
6243 }
6244
6245 pub fn edit_predictions_enabled(&self) -> bool {
6246 match self.edit_prediction_settings {
6247 EditPredictionSettings::Disabled => false,
6248 EditPredictionSettings::Enabled { .. } => true,
6249 }
6250 }
6251
6252 fn edit_prediction_requires_modifier(&self) -> bool {
6253 match self.edit_prediction_settings {
6254 EditPredictionSettings::Disabled => false,
6255 EditPredictionSettings::Enabled {
6256 preview_requires_modifier,
6257 ..
6258 } => preview_requires_modifier,
6259 }
6260 }
6261
6262 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6263 if self.edit_prediction_provider.is_none() {
6264 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6265 } else {
6266 let selection = self.selections.newest_anchor();
6267 let cursor = selection.head();
6268
6269 if let Some((buffer, cursor_buffer_position)) =
6270 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6271 {
6272 self.edit_prediction_settings =
6273 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6274 }
6275 }
6276 }
6277
6278 fn edit_prediction_settings_at_position(
6279 &self,
6280 buffer: &Entity<Buffer>,
6281 buffer_position: language::Anchor,
6282 cx: &App,
6283 ) -> EditPredictionSettings {
6284 if !self.mode.is_full()
6285 || !self.show_inline_completions_override.unwrap_or(true)
6286 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6287 {
6288 return EditPredictionSettings::Disabled;
6289 }
6290
6291 let buffer = buffer.read(cx);
6292
6293 let file = buffer.file();
6294
6295 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6296 return EditPredictionSettings::Disabled;
6297 };
6298
6299 let by_provider = matches!(
6300 self.menu_inline_completions_policy,
6301 MenuInlineCompletionsPolicy::ByProvider
6302 );
6303
6304 let show_in_menu = by_provider
6305 && self
6306 .edit_prediction_provider
6307 .as_ref()
6308 .map_or(false, |provider| {
6309 provider.provider.show_completions_in_menu()
6310 });
6311
6312 let preview_requires_modifier =
6313 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6314
6315 EditPredictionSettings::Enabled {
6316 show_in_menu,
6317 preview_requires_modifier,
6318 }
6319 }
6320
6321 fn should_show_edit_predictions(&self) -> bool {
6322 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6323 }
6324
6325 pub fn edit_prediction_preview_is_active(&self) -> bool {
6326 matches!(
6327 self.edit_prediction_preview,
6328 EditPredictionPreview::Active { .. }
6329 )
6330 }
6331
6332 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6333 let cursor = self.selections.newest_anchor().head();
6334 if let Some((buffer, cursor_position)) =
6335 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6336 {
6337 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6338 } else {
6339 false
6340 }
6341 }
6342
6343 pub fn supports_minimap(&self, cx: &App) -> bool {
6344 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6345 }
6346
6347 fn edit_predictions_enabled_in_buffer(
6348 &self,
6349 buffer: &Entity<Buffer>,
6350 buffer_position: language::Anchor,
6351 cx: &App,
6352 ) -> bool {
6353 maybe!({
6354 if self.read_only(cx) {
6355 return Some(false);
6356 }
6357 let provider = self.edit_prediction_provider()?;
6358 if !provider.is_enabled(&buffer, buffer_position, cx) {
6359 return Some(false);
6360 }
6361 let buffer = buffer.read(cx);
6362 let Some(file) = buffer.file() else {
6363 return Some(true);
6364 };
6365 let settings = all_language_settings(Some(file), cx);
6366 Some(settings.edit_predictions_enabled_for_file(file, cx))
6367 })
6368 .unwrap_or(false)
6369 }
6370
6371 fn cycle_inline_completion(
6372 &mut self,
6373 direction: Direction,
6374 window: &mut Window,
6375 cx: &mut Context<Self>,
6376 ) -> Option<()> {
6377 let provider = self.edit_prediction_provider()?;
6378 let cursor = self.selections.newest_anchor().head();
6379 let (buffer, cursor_buffer_position) =
6380 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6381 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6382 return None;
6383 }
6384
6385 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6386 self.update_visible_inline_completion(window, cx);
6387
6388 Some(())
6389 }
6390
6391 pub fn show_inline_completion(
6392 &mut self,
6393 _: &ShowEditPrediction,
6394 window: &mut Window,
6395 cx: &mut Context<Self>,
6396 ) {
6397 if !self.has_active_inline_completion() {
6398 self.refresh_inline_completion(false, true, window, cx);
6399 return;
6400 }
6401
6402 self.update_visible_inline_completion(window, cx);
6403 }
6404
6405 pub fn display_cursor_names(
6406 &mut self,
6407 _: &DisplayCursorNames,
6408 window: &mut Window,
6409 cx: &mut Context<Self>,
6410 ) {
6411 self.show_cursor_names(window, cx);
6412 }
6413
6414 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6415 self.show_cursor_names = true;
6416 cx.notify();
6417 cx.spawn_in(window, async move |this, cx| {
6418 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6419 this.update(cx, |this, cx| {
6420 this.show_cursor_names = false;
6421 cx.notify()
6422 })
6423 .ok()
6424 })
6425 .detach();
6426 }
6427
6428 pub fn next_edit_prediction(
6429 &mut self,
6430 _: &NextEditPrediction,
6431 window: &mut Window,
6432 cx: &mut Context<Self>,
6433 ) {
6434 if self.has_active_inline_completion() {
6435 self.cycle_inline_completion(Direction::Next, window, cx);
6436 } else {
6437 let is_copilot_disabled = self
6438 .refresh_inline_completion(false, true, window, cx)
6439 .is_none();
6440 if is_copilot_disabled {
6441 cx.propagate();
6442 }
6443 }
6444 }
6445
6446 pub fn previous_edit_prediction(
6447 &mut self,
6448 _: &PreviousEditPrediction,
6449 window: &mut Window,
6450 cx: &mut Context<Self>,
6451 ) {
6452 if self.has_active_inline_completion() {
6453 self.cycle_inline_completion(Direction::Prev, window, cx);
6454 } else {
6455 let is_copilot_disabled = self
6456 .refresh_inline_completion(false, true, window, cx)
6457 .is_none();
6458 if is_copilot_disabled {
6459 cx.propagate();
6460 }
6461 }
6462 }
6463
6464 pub fn accept_edit_prediction(
6465 &mut self,
6466 _: &AcceptEditPrediction,
6467 window: &mut Window,
6468 cx: &mut Context<Self>,
6469 ) {
6470 if self.show_edit_predictions_in_menu() {
6471 self.hide_context_menu(window, cx);
6472 }
6473
6474 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6475 return;
6476 };
6477
6478 self.report_inline_completion_event(
6479 active_inline_completion.completion_id.clone(),
6480 true,
6481 cx,
6482 );
6483
6484 match &active_inline_completion.completion {
6485 InlineCompletion::Move { target, .. } => {
6486 let target = *target;
6487
6488 if let Some(position_map) = &self.last_position_map {
6489 if position_map
6490 .visible_row_range
6491 .contains(&target.to_display_point(&position_map.snapshot).row())
6492 || !self.edit_prediction_requires_modifier()
6493 {
6494 self.unfold_ranges(&[target..target], true, false, cx);
6495 // Note that this is also done in vim's handler of the Tab action.
6496 self.change_selections(
6497 Some(Autoscroll::newest()),
6498 window,
6499 cx,
6500 |selections| {
6501 selections.select_anchor_ranges([target..target]);
6502 },
6503 );
6504 self.clear_row_highlights::<EditPredictionPreview>();
6505
6506 self.edit_prediction_preview
6507 .set_previous_scroll_position(None);
6508 } else {
6509 self.edit_prediction_preview
6510 .set_previous_scroll_position(Some(
6511 position_map.snapshot.scroll_anchor,
6512 ));
6513
6514 self.highlight_rows::<EditPredictionPreview>(
6515 target..target,
6516 cx.theme().colors().editor_highlighted_line_background,
6517 RowHighlightOptions {
6518 autoscroll: true,
6519 ..Default::default()
6520 },
6521 cx,
6522 );
6523 self.request_autoscroll(Autoscroll::fit(), cx);
6524 }
6525 }
6526 }
6527 InlineCompletion::Edit { edits, .. } => {
6528 if let Some(provider) = self.edit_prediction_provider() {
6529 provider.accept(cx);
6530 }
6531
6532 let snapshot = self.buffer.read(cx).snapshot(cx);
6533 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6534
6535 self.buffer.update(cx, |buffer, cx| {
6536 buffer.edit(edits.iter().cloned(), None, cx)
6537 });
6538
6539 self.change_selections(None, window, cx, |s| {
6540 s.select_anchor_ranges([last_edit_end..last_edit_end])
6541 });
6542
6543 self.update_visible_inline_completion(window, cx);
6544 if self.active_inline_completion.is_none() {
6545 self.refresh_inline_completion(true, true, window, cx);
6546 }
6547
6548 cx.notify();
6549 }
6550 }
6551
6552 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6553 }
6554
6555 pub fn accept_partial_inline_completion(
6556 &mut self,
6557 _: &AcceptPartialEditPrediction,
6558 window: &mut Window,
6559 cx: &mut Context<Self>,
6560 ) {
6561 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6562 return;
6563 };
6564 if self.selections.count() != 1 {
6565 return;
6566 }
6567
6568 self.report_inline_completion_event(
6569 active_inline_completion.completion_id.clone(),
6570 true,
6571 cx,
6572 );
6573
6574 match &active_inline_completion.completion {
6575 InlineCompletion::Move { target, .. } => {
6576 let target = *target;
6577 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6578 selections.select_anchor_ranges([target..target]);
6579 });
6580 }
6581 InlineCompletion::Edit { edits, .. } => {
6582 // Find an insertion that starts at the cursor position.
6583 let snapshot = self.buffer.read(cx).snapshot(cx);
6584 let cursor_offset = self.selections.newest::<usize>(cx).head();
6585 let insertion = edits.iter().find_map(|(range, text)| {
6586 let range = range.to_offset(&snapshot);
6587 if range.is_empty() && range.start == cursor_offset {
6588 Some(text)
6589 } else {
6590 None
6591 }
6592 });
6593
6594 if let Some(text) = insertion {
6595 let mut partial_completion = text
6596 .chars()
6597 .by_ref()
6598 .take_while(|c| c.is_alphabetic())
6599 .collect::<String>();
6600 if partial_completion.is_empty() {
6601 partial_completion = text
6602 .chars()
6603 .by_ref()
6604 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6605 .collect::<String>();
6606 }
6607
6608 cx.emit(EditorEvent::InputHandled {
6609 utf16_range_to_replace: None,
6610 text: partial_completion.clone().into(),
6611 });
6612
6613 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6614
6615 self.refresh_inline_completion(true, true, window, cx);
6616 cx.notify();
6617 } else {
6618 self.accept_edit_prediction(&Default::default(), window, cx);
6619 }
6620 }
6621 }
6622 }
6623
6624 fn discard_inline_completion(
6625 &mut self,
6626 should_report_inline_completion_event: bool,
6627 cx: &mut Context<Self>,
6628 ) -> bool {
6629 if should_report_inline_completion_event {
6630 let completion_id = self
6631 .active_inline_completion
6632 .as_ref()
6633 .and_then(|active_completion| active_completion.completion_id.clone());
6634
6635 self.report_inline_completion_event(completion_id, false, cx);
6636 }
6637
6638 if let Some(provider) = self.edit_prediction_provider() {
6639 provider.discard(cx);
6640 }
6641
6642 self.take_active_inline_completion(cx)
6643 }
6644
6645 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6646 let Some(provider) = self.edit_prediction_provider() else {
6647 return;
6648 };
6649
6650 let Some((_, buffer, _)) = self
6651 .buffer
6652 .read(cx)
6653 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6654 else {
6655 return;
6656 };
6657
6658 let extension = buffer
6659 .read(cx)
6660 .file()
6661 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6662
6663 let event_type = match accepted {
6664 true => "Edit Prediction Accepted",
6665 false => "Edit Prediction Discarded",
6666 };
6667 telemetry::event!(
6668 event_type,
6669 provider = provider.name(),
6670 prediction_id = id,
6671 suggestion_accepted = accepted,
6672 file_extension = extension,
6673 );
6674 }
6675
6676 pub fn has_active_inline_completion(&self) -> bool {
6677 self.active_inline_completion.is_some()
6678 }
6679
6680 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6681 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6682 return false;
6683 };
6684
6685 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6686 self.clear_highlights::<InlineCompletionHighlight>(cx);
6687 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6688 true
6689 }
6690
6691 /// Returns true when we're displaying the edit prediction popover below the cursor
6692 /// like we are not previewing and the LSP autocomplete menu is visible
6693 /// or we are in `when_holding_modifier` mode.
6694 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6695 if self.edit_prediction_preview_is_active()
6696 || !self.show_edit_predictions_in_menu()
6697 || !self.edit_predictions_enabled()
6698 {
6699 return false;
6700 }
6701
6702 if self.has_visible_completions_menu() {
6703 return true;
6704 }
6705
6706 has_completion && self.edit_prediction_requires_modifier()
6707 }
6708
6709 fn handle_modifiers_changed(
6710 &mut self,
6711 modifiers: Modifiers,
6712 position_map: &PositionMap,
6713 window: &mut Window,
6714 cx: &mut Context<Self>,
6715 ) {
6716 if self.show_edit_predictions_in_menu() {
6717 self.update_edit_prediction_preview(&modifiers, window, cx);
6718 }
6719
6720 self.update_selection_mode(&modifiers, position_map, window, cx);
6721
6722 let mouse_position = window.mouse_position();
6723 if !position_map.text_hitbox.is_hovered(window) {
6724 return;
6725 }
6726
6727 self.update_hovered_link(
6728 position_map.point_for_position(mouse_position),
6729 &position_map.snapshot,
6730 modifiers,
6731 window,
6732 cx,
6733 )
6734 }
6735
6736 fn update_selection_mode(
6737 &mut self,
6738 modifiers: &Modifiers,
6739 position_map: &PositionMap,
6740 window: &mut Window,
6741 cx: &mut Context<Self>,
6742 ) {
6743 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6744 return;
6745 }
6746
6747 let mouse_position = window.mouse_position();
6748 let point_for_position = position_map.point_for_position(mouse_position);
6749 let position = point_for_position.previous_valid;
6750
6751 self.select(
6752 SelectPhase::BeginColumnar {
6753 position,
6754 reset: false,
6755 goal_column: point_for_position.exact_unclipped.column(),
6756 },
6757 window,
6758 cx,
6759 );
6760 }
6761
6762 fn update_edit_prediction_preview(
6763 &mut self,
6764 modifiers: &Modifiers,
6765 window: &mut Window,
6766 cx: &mut Context<Self>,
6767 ) {
6768 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6769 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6770 return;
6771 };
6772
6773 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6774 if matches!(
6775 self.edit_prediction_preview,
6776 EditPredictionPreview::Inactive { .. }
6777 ) {
6778 self.edit_prediction_preview = EditPredictionPreview::Active {
6779 previous_scroll_position: None,
6780 since: Instant::now(),
6781 };
6782
6783 self.update_visible_inline_completion(window, cx);
6784 cx.notify();
6785 }
6786 } else if let EditPredictionPreview::Active {
6787 previous_scroll_position,
6788 since,
6789 } = self.edit_prediction_preview
6790 {
6791 if let (Some(previous_scroll_position), Some(position_map)) =
6792 (previous_scroll_position, self.last_position_map.as_ref())
6793 {
6794 self.set_scroll_position(
6795 previous_scroll_position
6796 .scroll_position(&position_map.snapshot.display_snapshot),
6797 window,
6798 cx,
6799 );
6800 }
6801
6802 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6803 released_too_fast: since.elapsed() < Duration::from_millis(200),
6804 };
6805 self.clear_row_highlights::<EditPredictionPreview>();
6806 self.update_visible_inline_completion(window, cx);
6807 cx.notify();
6808 }
6809 }
6810
6811 fn update_visible_inline_completion(
6812 &mut self,
6813 _window: &mut Window,
6814 cx: &mut Context<Self>,
6815 ) -> Option<()> {
6816 let selection = self.selections.newest_anchor();
6817 let cursor = selection.head();
6818 let multibuffer = self.buffer.read(cx).snapshot(cx);
6819 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6820 let excerpt_id = cursor.excerpt_id;
6821
6822 let show_in_menu = self.show_edit_predictions_in_menu();
6823 let completions_menu_has_precedence = !show_in_menu
6824 && (self.context_menu.borrow().is_some()
6825 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6826
6827 if completions_menu_has_precedence
6828 || !offset_selection.is_empty()
6829 || self
6830 .active_inline_completion
6831 .as_ref()
6832 .map_or(false, |completion| {
6833 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6834 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6835 !invalidation_range.contains(&offset_selection.head())
6836 })
6837 {
6838 self.discard_inline_completion(false, cx);
6839 return None;
6840 }
6841
6842 self.take_active_inline_completion(cx);
6843 let Some(provider) = self.edit_prediction_provider() else {
6844 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6845 return None;
6846 };
6847
6848 let (buffer, cursor_buffer_position) =
6849 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6850
6851 self.edit_prediction_settings =
6852 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6853
6854 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6855
6856 if self.edit_prediction_indent_conflict {
6857 let cursor_point = cursor.to_point(&multibuffer);
6858
6859 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6860
6861 if let Some((_, indent)) = indents.iter().next() {
6862 if indent.len == cursor_point.column {
6863 self.edit_prediction_indent_conflict = false;
6864 }
6865 }
6866 }
6867
6868 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6869 let edits = inline_completion
6870 .edits
6871 .into_iter()
6872 .flat_map(|(range, new_text)| {
6873 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6874 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6875 Some((start..end, new_text))
6876 })
6877 .collect::<Vec<_>>();
6878 if edits.is_empty() {
6879 return None;
6880 }
6881
6882 let first_edit_start = edits.first().unwrap().0.start;
6883 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6884 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6885
6886 let last_edit_end = edits.last().unwrap().0.end;
6887 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6888 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6889
6890 let cursor_row = cursor.to_point(&multibuffer).row;
6891
6892 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6893
6894 let mut inlay_ids = Vec::new();
6895 let invalidation_row_range;
6896 let move_invalidation_row_range = if cursor_row < edit_start_row {
6897 Some(cursor_row..edit_end_row)
6898 } else if cursor_row > edit_end_row {
6899 Some(edit_start_row..cursor_row)
6900 } else {
6901 None
6902 };
6903 let is_move =
6904 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6905 let completion = if is_move {
6906 invalidation_row_range =
6907 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6908 let target = first_edit_start;
6909 InlineCompletion::Move { target, snapshot }
6910 } else {
6911 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6912 && !self.inline_completions_hidden_for_vim_mode;
6913
6914 if show_completions_in_buffer {
6915 if edits
6916 .iter()
6917 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6918 {
6919 let mut inlays = Vec::new();
6920 for (range, new_text) in &edits {
6921 let inlay = Inlay::inline_completion(
6922 post_inc(&mut self.next_inlay_id),
6923 range.start,
6924 new_text.as_str(),
6925 );
6926 inlay_ids.push(inlay.id);
6927 inlays.push(inlay);
6928 }
6929
6930 self.splice_inlays(&[], inlays, cx);
6931 } else {
6932 let background_color = cx.theme().status().deleted_background;
6933 self.highlight_text::<InlineCompletionHighlight>(
6934 edits.iter().map(|(range, _)| range.clone()).collect(),
6935 HighlightStyle {
6936 background_color: Some(background_color),
6937 ..Default::default()
6938 },
6939 cx,
6940 );
6941 }
6942 }
6943
6944 invalidation_row_range = edit_start_row..edit_end_row;
6945
6946 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6947 if provider.show_tab_accept_marker() {
6948 EditDisplayMode::TabAccept
6949 } else {
6950 EditDisplayMode::Inline
6951 }
6952 } else {
6953 EditDisplayMode::DiffPopover
6954 };
6955
6956 InlineCompletion::Edit {
6957 edits,
6958 edit_preview: inline_completion.edit_preview,
6959 display_mode,
6960 snapshot,
6961 }
6962 };
6963
6964 let invalidation_range = multibuffer
6965 .anchor_before(Point::new(invalidation_row_range.start, 0))
6966 ..multibuffer.anchor_after(Point::new(
6967 invalidation_row_range.end,
6968 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6969 ));
6970
6971 self.stale_inline_completion_in_menu = None;
6972 self.active_inline_completion = Some(InlineCompletionState {
6973 inlay_ids,
6974 completion,
6975 completion_id: inline_completion.id,
6976 invalidation_range,
6977 });
6978
6979 cx.notify();
6980
6981 Some(())
6982 }
6983
6984 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6985 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6986 }
6987
6988 fn clear_tasks(&mut self) {
6989 self.tasks.clear()
6990 }
6991
6992 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6993 if self.tasks.insert(key, value).is_some() {
6994 // This case should hopefully be rare, but just in case...
6995 log::error!(
6996 "multiple different run targets found on a single line, only the last target will be rendered"
6997 )
6998 }
6999 }
7000
7001 /// Get all display points of breakpoints that will be rendered within editor
7002 ///
7003 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7004 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7005 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7006 fn active_breakpoints(
7007 &self,
7008 range: Range<DisplayRow>,
7009 window: &mut Window,
7010 cx: &mut Context<Self>,
7011 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7012 let mut breakpoint_display_points = HashMap::default();
7013
7014 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7015 return breakpoint_display_points;
7016 };
7017
7018 let snapshot = self.snapshot(window, cx);
7019
7020 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7021 let Some(project) = self.project.as_ref() else {
7022 return breakpoint_display_points;
7023 };
7024
7025 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7026 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7027
7028 for (buffer_snapshot, range, excerpt_id) in
7029 multi_buffer_snapshot.range_to_buffer_ranges(range)
7030 {
7031 let Some(buffer) = project.read_with(cx, |this, cx| {
7032 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7033 }) else {
7034 continue;
7035 };
7036 let breakpoints = breakpoint_store.read(cx).breakpoints(
7037 &buffer,
7038 Some(
7039 buffer_snapshot.anchor_before(range.start)
7040 ..buffer_snapshot.anchor_after(range.end),
7041 ),
7042 buffer_snapshot,
7043 cx,
7044 );
7045 for (breakpoint, state) in breakpoints {
7046 let multi_buffer_anchor =
7047 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7048 let position = multi_buffer_anchor
7049 .to_point(&multi_buffer_snapshot)
7050 .to_display_point(&snapshot);
7051
7052 breakpoint_display_points.insert(
7053 position.row(),
7054 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7055 );
7056 }
7057 }
7058
7059 breakpoint_display_points
7060 }
7061
7062 fn breakpoint_context_menu(
7063 &self,
7064 anchor: Anchor,
7065 window: &mut Window,
7066 cx: &mut Context<Self>,
7067 ) -> Entity<ui::ContextMenu> {
7068 let weak_editor = cx.weak_entity();
7069 let focus_handle = self.focus_handle(cx);
7070
7071 let row = self
7072 .buffer
7073 .read(cx)
7074 .snapshot(cx)
7075 .summary_for_anchor::<Point>(&anchor)
7076 .row;
7077
7078 let breakpoint = self
7079 .breakpoint_at_row(row, window, cx)
7080 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7081
7082 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7083 "Edit Log Breakpoint"
7084 } else {
7085 "Set Log Breakpoint"
7086 };
7087
7088 let condition_breakpoint_msg = if breakpoint
7089 .as_ref()
7090 .is_some_and(|bp| bp.1.condition.is_some())
7091 {
7092 "Edit Condition Breakpoint"
7093 } else {
7094 "Set Condition Breakpoint"
7095 };
7096
7097 let hit_condition_breakpoint_msg = if breakpoint
7098 .as_ref()
7099 .is_some_and(|bp| bp.1.hit_condition.is_some())
7100 {
7101 "Edit Hit Condition Breakpoint"
7102 } else {
7103 "Set Hit Condition Breakpoint"
7104 };
7105
7106 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7107 "Unset Breakpoint"
7108 } else {
7109 "Set Breakpoint"
7110 };
7111
7112 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7113 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7114
7115 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7116 BreakpointState::Enabled => Some("Disable"),
7117 BreakpointState::Disabled => Some("Enable"),
7118 });
7119
7120 let (anchor, breakpoint) =
7121 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7122
7123 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7124 menu.on_blur_subscription(Subscription::new(|| {}))
7125 .context(focus_handle)
7126 .when(run_to_cursor, |this| {
7127 let weak_editor = weak_editor.clone();
7128 this.entry("Run to cursor", None, move |window, cx| {
7129 weak_editor
7130 .update(cx, |editor, cx| {
7131 editor.change_selections(None, window, cx, |s| {
7132 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7133 });
7134 })
7135 .ok();
7136
7137 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7138 })
7139 .separator()
7140 })
7141 .when_some(toggle_state_msg, |this, msg| {
7142 this.entry(msg, None, {
7143 let weak_editor = weak_editor.clone();
7144 let breakpoint = breakpoint.clone();
7145 move |_window, cx| {
7146 weak_editor
7147 .update(cx, |this, cx| {
7148 this.edit_breakpoint_at_anchor(
7149 anchor,
7150 breakpoint.as_ref().clone(),
7151 BreakpointEditAction::InvertState,
7152 cx,
7153 );
7154 })
7155 .log_err();
7156 }
7157 })
7158 })
7159 .entry(set_breakpoint_msg, None, {
7160 let weak_editor = weak_editor.clone();
7161 let breakpoint = breakpoint.clone();
7162 move |_window, cx| {
7163 weak_editor
7164 .update(cx, |this, cx| {
7165 this.edit_breakpoint_at_anchor(
7166 anchor,
7167 breakpoint.as_ref().clone(),
7168 BreakpointEditAction::Toggle,
7169 cx,
7170 );
7171 })
7172 .log_err();
7173 }
7174 })
7175 .entry(log_breakpoint_msg, None, {
7176 let breakpoint = breakpoint.clone();
7177 let weak_editor = weak_editor.clone();
7178 move |window, cx| {
7179 weak_editor
7180 .update(cx, |this, cx| {
7181 this.add_edit_breakpoint_block(
7182 anchor,
7183 breakpoint.as_ref(),
7184 BreakpointPromptEditAction::Log,
7185 window,
7186 cx,
7187 );
7188 })
7189 .log_err();
7190 }
7191 })
7192 .entry(condition_breakpoint_msg, None, {
7193 let breakpoint = breakpoint.clone();
7194 let weak_editor = weak_editor.clone();
7195 move |window, cx| {
7196 weak_editor
7197 .update(cx, |this, cx| {
7198 this.add_edit_breakpoint_block(
7199 anchor,
7200 breakpoint.as_ref(),
7201 BreakpointPromptEditAction::Condition,
7202 window,
7203 cx,
7204 );
7205 })
7206 .log_err();
7207 }
7208 })
7209 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7210 weak_editor
7211 .update(cx, |this, cx| {
7212 this.add_edit_breakpoint_block(
7213 anchor,
7214 breakpoint.as_ref(),
7215 BreakpointPromptEditAction::HitCondition,
7216 window,
7217 cx,
7218 );
7219 })
7220 .log_err();
7221 })
7222 })
7223 }
7224
7225 fn render_breakpoint(
7226 &self,
7227 position: Anchor,
7228 row: DisplayRow,
7229 breakpoint: &Breakpoint,
7230 state: Option<BreakpointSessionState>,
7231 cx: &mut Context<Self>,
7232 ) -> IconButton {
7233 let is_rejected = state.is_some_and(|s| !s.verified);
7234 // Is it a breakpoint that shows up when hovering over gutter?
7235 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7236 (false, false),
7237 |PhantomBreakpointIndicator {
7238 is_active,
7239 display_row,
7240 collides_with_existing_breakpoint,
7241 }| {
7242 (
7243 is_active && display_row == row,
7244 collides_with_existing_breakpoint,
7245 )
7246 },
7247 );
7248
7249 let (color, icon) = {
7250 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7251 (false, false) => ui::IconName::DebugBreakpoint,
7252 (true, false) => ui::IconName::DebugLogBreakpoint,
7253 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7254 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7255 };
7256
7257 let color = if is_phantom {
7258 Color::Hint
7259 } else if is_rejected {
7260 Color::Disabled
7261 } else {
7262 Color::Debugger
7263 };
7264
7265 (color, icon)
7266 };
7267
7268 let breakpoint = Arc::from(breakpoint.clone());
7269
7270 let alt_as_text = gpui::Keystroke {
7271 modifiers: Modifiers::secondary_key(),
7272 ..Default::default()
7273 };
7274 let primary_action_text = if breakpoint.is_disabled() {
7275 "Enable breakpoint"
7276 } else if is_phantom && !collides_with_existing {
7277 "Set breakpoint"
7278 } else {
7279 "Unset breakpoint"
7280 };
7281 let focus_handle = self.focus_handle.clone();
7282
7283 let meta = if is_rejected {
7284 SharedString::from("No executable code is associated with this line.")
7285 } else if collides_with_existing && !breakpoint.is_disabled() {
7286 SharedString::from(format!(
7287 "{alt_as_text}-click to disable,\nright-click for more options."
7288 ))
7289 } else {
7290 SharedString::from("Right-click for more options.")
7291 };
7292 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7293 .icon_size(IconSize::XSmall)
7294 .size(ui::ButtonSize::None)
7295 .when(is_rejected, |this| {
7296 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7297 })
7298 .icon_color(color)
7299 .style(ButtonStyle::Transparent)
7300 .on_click(cx.listener({
7301 let breakpoint = breakpoint.clone();
7302
7303 move |editor, event: &ClickEvent, window, cx| {
7304 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7305 BreakpointEditAction::InvertState
7306 } else {
7307 BreakpointEditAction::Toggle
7308 };
7309
7310 window.focus(&editor.focus_handle(cx));
7311 editor.edit_breakpoint_at_anchor(
7312 position,
7313 breakpoint.as_ref().clone(),
7314 edit_action,
7315 cx,
7316 );
7317 }
7318 }))
7319 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7320 editor.set_breakpoint_context_menu(
7321 row,
7322 Some(position),
7323 event.down.position,
7324 window,
7325 cx,
7326 );
7327 }))
7328 .tooltip(move |window, cx| {
7329 Tooltip::with_meta_in(
7330 primary_action_text,
7331 Some(&ToggleBreakpoint),
7332 meta.clone(),
7333 &focus_handle,
7334 window,
7335 cx,
7336 )
7337 })
7338 }
7339
7340 fn build_tasks_context(
7341 project: &Entity<Project>,
7342 buffer: &Entity<Buffer>,
7343 buffer_row: u32,
7344 tasks: &Arc<RunnableTasks>,
7345 cx: &mut Context<Self>,
7346 ) -> Task<Option<task::TaskContext>> {
7347 let position = Point::new(buffer_row, tasks.column);
7348 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7349 let location = Location {
7350 buffer: buffer.clone(),
7351 range: range_start..range_start,
7352 };
7353 // Fill in the environmental variables from the tree-sitter captures
7354 let mut captured_task_variables = TaskVariables::default();
7355 for (capture_name, value) in tasks.extra_variables.clone() {
7356 captured_task_variables.insert(
7357 task::VariableName::Custom(capture_name.into()),
7358 value.clone(),
7359 );
7360 }
7361 project.update(cx, |project, cx| {
7362 project.task_store().update(cx, |task_store, cx| {
7363 task_store.task_context_for_location(captured_task_variables, location, cx)
7364 })
7365 })
7366 }
7367
7368 pub fn spawn_nearest_task(
7369 &mut self,
7370 action: &SpawnNearestTask,
7371 window: &mut Window,
7372 cx: &mut Context<Self>,
7373 ) {
7374 let Some((workspace, _)) = self.workspace.clone() else {
7375 return;
7376 };
7377 let Some(project) = self.project.clone() else {
7378 return;
7379 };
7380
7381 // Try to find a closest, enclosing node using tree-sitter that has a
7382 // task
7383 let Some((buffer, buffer_row, tasks)) = self
7384 .find_enclosing_node_task(cx)
7385 // Or find the task that's closest in row-distance.
7386 .or_else(|| self.find_closest_task(cx))
7387 else {
7388 return;
7389 };
7390
7391 let reveal_strategy = action.reveal;
7392 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7393 cx.spawn_in(window, async move |_, cx| {
7394 let context = task_context.await?;
7395 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7396
7397 let resolved = &mut resolved_task.resolved;
7398 resolved.reveal = reveal_strategy;
7399
7400 workspace
7401 .update_in(cx, |workspace, window, cx| {
7402 workspace.schedule_resolved_task(
7403 task_source_kind,
7404 resolved_task,
7405 false,
7406 window,
7407 cx,
7408 );
7409 })
7410 .ok()
7411 })
7412 .detach();
7413 }
7414
7415 fn find_closest_task(
7416 &mut self,
7417 cx: &mut Context<Self>,
7418 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7419 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7420
7421 let ((buffer_id, row), tasks) = self
7422 .tasks
7423 .iter()
7424 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7425
7426 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7427 let tasks = Arc::new(tasks.to_owned());
7428 Some((buffer, *row, tasks))
7429 }
7430
7431 fn find_enclosing_node_task(
7432 &mut self,
7433 cx: &mut Context<Self>,
7434 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7435 let snapshot = self.buffer.read(cx).snapshot(cx);
7436 let offset = self.selections.newest::<usize>(cx).head();
7437 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7438 let buffer_id = excerpt.buffer().remote_id();
7439
7440 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7441 let mut cursor = layer.node().walk();
7442
7443 while cursor.goto_first_child_for_byte(offset).is_some() {
7444 if cursor.node().end_byte() == offset {
7445 cursor.goto_next_sibling();
7446 }
7447 }
7448
7449 // Ascend to the smallest ancestor that contains the range and has a task.
7450 loop {
7451 let node = cursor.node();
7452 let node_range = node.byte_range();
7453 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7454
7455 // Check if this node contains our offset
7456 if node_range.start <= offset && node_range.end >= offset {
7457 // If it contains offset, check for task
7458 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7459 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7460 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7461 }
7462 }
7463
7464 if !cursor.goto_parent() {
7465 break;
7466 }
7467 }
7468 None
7469 }
7470
7471 fn render_run_indicator(
7472 &self,
7473 _style: &EditorStyle,
7474 is_active: bool,
7475 row: DisplayRow,
7476 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7477 cx: &mut Context<Self>,
7478 ) -> IconButton {
7479 let color = Color::Muted;
7480 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7481
7482 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7483 .shape(ui::IconButtonShape::Square)
7484 .icon_size(IconSize::XSmall)
7485 .icon_color(color)
7486 .toggle_state(is_active)
7487 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7488 let quick_launch = e.down.button == MouseButton::Left;
7489 window.focus(&editor.focus_handle(cx));
7490 editor.toggle_code_actions(
7491 &ToggleCodeActions {
7492 deployed_from_indicator: Some(row),
7493 quick_launch,
7494 },
7495 window,
7496 cx,
7497 );
7498 }))
7499 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7500 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7501 }))
7502 }
7503
7504 pub fn context_menu_visible(&self) -> bool {
7505 !self.edit_prediction_preview_is_active()
7506 && self
7507 .context_menu
7508 .borrow()
7509 .as_ref()
7510 .map_or(false, |menu| menu.visible())
7511 }
7512
7513 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7514 self.context_menu
7515 .borrow()
7516 .as_ref()
7517 .map(|menu| menu.origin())
7518 }
7519
7520 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7521 self.context_menu_options = Some(options);
7522 }
7523
7524 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7525 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7526
7527 fn render_edit_prediction_popover(
7528 &mut self,
7529 text_bounds: &Bounds<Pixels>,
7530 content_origin: gpui::Point<Pixels>,
7531 right_margin: Pixels,
7532 editor_snapshot: &EditorSnapshot,
7533 visible_row_range: Range<DisplayRow>,
7534 scroll_top: f32,
7535 scroll_bottom: f32,
7536 line_layouts: &[LineWithInvisibles],
7537 line_height: Pixels,
7538 scroll_pixel_position: gpui::Point<Pixels>,
7539 newest_selection_head: Option<DisplayPoint>,
7540 editor_width: Pixels,
7541 style: &EditorStyle,
7542 window: &mut Window,
7543 cx: &mut App,
7544 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7545 if self.mode().is_minimap() {
7546 return None;
7547 }
7548 let active_inline_completion = self.active_inline_completion.as_ref()?;
7549
7550 if self.edit_prediction_visible_in_cursor_popover(true) {
7551 return None;
7552 }
7553
7554 match &active_inline_completion.completion {
7555 InlineCompletion::Move { target, .. } => {
7556 let target_display_point = target.to_display_point(editor_snapshot);
7557
7558 if self.edit_prediction_requires_modifier() {
7559 if !self.edit_prediction_preview_is_active() {
7560 return None;
7561 }
7562
7563 self.render_edit_prediction_modifier_jump_popover(
7564 text_bounds,
7565 content_origin,
7566 visible_row_range,
7567 line_layouts,
7568 line_height,
7569 scroll_pixel_position,
7570 newest_selection_head,
7571 target_display_point,
7572 window,
7573 cx,
7574 )
7575 } else {
7576 self.render_edit_prediction_eager_jump_popover(
7577 text_bounds,
7578 content_origin,
7579 editor_snapshot,
7580 visible_row_range,
7581 scroll_top,
7582 scroll_bottom,
7583 line_height,
7584 scroll_pixel_position,
7585 target_display_point,
7586 editor_width,
7587 window,
7588 cx,
7589 )
7590 }
7591 }
7592 InlineCompletion::Edit {
7593 display_mode: EditDisplayMode::Inline,
7594 ..
7595 } => None,
7596 InlineCompletion::Edit {
7597 display_mode: EditDisplayMode::TabAccept,
7598 edits,
7599 ..
7600 } => {
7601 let range = &edits.first()?.0;
7602 let target_display_point = range.end.to_display_point(editor_snapshot);
7603
7604 self.render_edit_prediction_end_of_line_popover(
7605 "Accept",
7606 editor_snapshot,
7607 visible_row_range,
7608 target_display_point,
7609 line_height,
7610 scroll_pixel_position,
7611 content_origin,
7612 editor_width,
7613 window,
7614 cx,
7615 )
7616 }
7617 InlineCompletion::Edit {
7618 edits,
7619 edit_preview,
7620 display_mode: EditDisplayMode::DiffPopover,
7621 snapshot,
7622 } => self.render_edit_prediction_diff_popover(
7623 text_bounds,
7624 content_origin,
7625 right_margin,
7626 editor_snapshot,
7627 visible_row_range,
7628 line_layouts,
7629 line_height,
7630 scroll_pixel_position,
7631 newest_selection_head,
7632 editor_width,
7633 style,
7634 edits,
7635 edit_preview,
7636 snapshot,
7637 window,
7638 cx,
7639 ),
7640 }
7641 }
7642
7643 fn render_edit_prediction_modifier_jump_popover(
7644 &mut self,
7645 text_bounds: &Bounds<Pixels>,
7646 content_origin: gpui::Point<Pixels>,
7647 visible_row_range: Range<DisplayRow>,
7648 line_layouts: &[LineWithInvisibles],
7649 line_height: Pixels,
7650 scroll_pixel_position: gpui::Point<Pixels>,
7651 newest_selection_head: Option<DisplayPoint>,
7652 target_display_point: DisplayPoint,
7653 window: &mut Window,
7654 cx: &mut App,
7655 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7656 let scrolled_content_origin =
7657 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7658
7659 const SCROLL_PADDING_Y: Pixels = px(12.);
7660
7661 if target_display_point.row() < visible_row_range.start {
7662 return self.render_edit_prediction_scroll_popover(
7663 |_| SCROLL_PADDING_Y,
7664 IconName::ArrowUp,
7665 visible_row_range,
7666 line_layouts,
7667 newest_selection_head,
7668 scrolled_content_origin,
7669 window,
7670 cx,
7671 );
7672 } else if target_display_point.row() >= visible_row_range.end {
7673 return self.render_edit_prediction_scroll_popover(
7674 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7675 IconName::ArrowDown,
7676 visible_row_range,
7677 line_layouts,
7678 newest_selection_head,
7679 scrolled_content_origin,
7680 window,
7681 cx,
7682 );
7683 }
7684
7685 const POLE_WIDTH: Pixels = px(2.);
7686
7687 let line_layout =
7688 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7689 let target_column = target_display_point.column() as usize;
7690
7691 let target_x = line_layout.x_for_index(target_column);
7692 let target_y =
7693 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7694
7695 let flag_on_right = target_x < text_bounds.size.width / 2.;
7696
7697 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7698 border_color.l += 0.001;
7699
7700 let mut element = v_flex()
7701 .items_end()
7702 .when(flag_on_right, |el| el.items_start())
7703 .child(if flag_on_right {
7704 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7705 .rounded_bl(px(0.))
7706 .rounded_tl(px(0.))
7707 .border_l_2()
7708 .border_color(border_color)
7709 } else {
7710 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7711 .rounded_br(px(0.))
7712 .rounded_tr(px(0.))
7713 .border_r_2()
7714 .border_color(border_color)
7715 })
7716 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7717 .into_any();
7718
7719 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7720
7721 let mut origin = scrolled_content_origin + point(target_x, target_y)
7722 - point(
7723 if flag_on_right {
7724 POLE_WIDTH
7725 } else {
7726 size.width - POLE_WIDTH
7727 },
7728 size.height - line_height,
7729 );
7730
7731 origin.x = origin.x.max(content_origin.x);
7732
7733 element.prepaint_at(origin, window, cx);
7734
7735 Some((element, origin))
7736 }
7737
7738 fn render_edit_prediction_scroll_popover(
7739 &mut self,
7740 to_y: impl Fn(Size<Pixels>) -> Pixels,
7741 scroll_icon: IconName,
7742 visible_row_range: Range<DisplayRow>,
7743 line_layouts: &[LineWithInvisibles],
7744 newest_selection_head: Option<DisplayPoint>,
7745 scrolled_content_origin: gpui::Point<Pixels>,
7746 window: &mut Window,
7747 cx: &mut App,
7748 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7749 let mut element = self
7750 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7751 .into_any();
7752
7753 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7754
7755 let cursor = newest_selection_head?;
7756 let cursor_row_layout =
7757 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7758 let cursor_column = cursor.column() as usize;
7759
7760 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7761
7762 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7763
7764 element.prepaint_at(origin, window, cx);
7765 Some((element, origin))
7766 }
7767
7768 fn render_edit_prediction_eager_jump_popover(
7769 &mut self,
7770 text_bounds: &Bounds<Pixels>,
7771 content_origin: gpui::Point<Pixels>,
7772 editor_snapshot: &EditorSnapshot,
7773 visible_row_range: Range<DisplayRow>,
7774 scroll_top: f32,
7775 scroll_bottom: f32,
7776 line_height: Pixels,
7777 scroll_pixel_position: gpui::Point<Pixels>,
7778 target_display_point: DisplayPoint,
7779 editor_width: Pixels,
7780 window: &mut Window,
7781 cx: &mut App,
7782 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7783 if target_display_point.row().as_f32() < scroll_top {
7784 let mut element = self
7785 .render_edit_prediction_line_popover(
7786 "Jump to Edit",
7787 Some(IconName::ArrowUp),
7788 window,
7789 cx,
7790 )?
7791 .into_any();
7792
7793 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7794 let offset = point(
7795 (text_bounds.size.width - size.width) / 2.,
7796 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7797 );
7798
7799 let origin = text_bounds.origin + offset;
7800 element.prepaint_at(origin, window, cx);
7801 Some((element, origin))
7802 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7803 let mut element = self
7804 .render_edit_prediction_line_popover(
7805 "Jump to Edit",
7806 Some(IconName::ArrowDown),
7807 window,
7808 cx,
7809 )?
7810 .into_any();
7811
7812 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7813 let offset = point(
7814 (text_bounds.size.width - size.width) / 2.,
7815 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7816 );
7817
7818 let origin = text_bounds.origin + offset;
7819 element.prepaint_at(origin, window, cx);
7820 Some((element, origin))
7821 } else {
7822 self.render_edit_prediction_end_of_line_popover(
7823 "Jump to Edit",
7824 editor_snapshot,
7825 visible_row_range,
7826 target_display_point,
7827 line_height,
7828 scroll_pixel_position,
7829 content_origin,
7830 editor_width,
7831 window,
7832 cx,
7833 )
7834 }
7835 }
7836
7837 fn render_edit_prediction_end_of_line_popover(
7838 self: &mut Editor,
7839 label: &'static str,
7840 editor_snapshot: &EditorSnapshot,
7841 visible_row_range: Range<DisplayRow>,
7842 target_display_point: DisplayPoint,
7843 line_height: Pixels,
7844 scroll_pixel_position: gpui::Point<Pixels>,
7845 content_origin: gpui::Point<Pixels>,
7846 editor_width: Pixels,
7847 window: &mut Window,
7848 cx: &mut App,
7849 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7850 let target_line_end = DisplayPoint::new(
7851 target_display_point.row(),
7852 editor_snapshot.line_len(target_display_point.row()),
7853 );
7854
7855 let mut element = self
7856 .render_edit_prediction_line_popover(label, None, window, cx)?
7857 .into_any();
7858
7859 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7860
7861 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7862
7863 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7864 let mut origin = start_point
7865 + line_origin
7866 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7867 origin.x = origin.x.max(content_origin.x);
7868
7869 let max_x = content_origin.x + editor_width - size.width;
7870
7871 if origin.x > max_x {
7872 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7873
7874 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7875 origin.y += offset;
7876 IconName::ArrowUp
7877 } else {
7878 origin.y -= offset;
7879 IconName::ArrowDown
7880 };
7881
7882 element = self
7883 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7884 .into_any();
7885
7886 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7887
7888 origin.x = content_origin.x + editor_width - size.width - px(2.);
7889 }
7890
7891 element.prepaint_at(origin, window, cx);
7892 Some((element, origin))
7893 }
7894
7895 fn render_edit_prediction_diff_popover(
7896 self: &Editor,
7897 text_bounds: &Bounds<Pixels>,
7898 content_origin: gpui::Point<Pixels>,
7899 right_margin: Pixels,
7900 editor_snapshot: &EditorSnapshot,
7901 visible_row_range: Range<DisplayRow>,
7902 line_layouts: &[LineWithInvisibles],
7903 line_height: Pixels,
7904 scroll_pixel_position: gpui::Point<Pixels>,
7905 newest_selection_head: Option<DisplayPoint>,
7906 editor_width: Pixels,
7907 style: &EditorStyle,
7908 edits: &Vec<(Range<Anchor>, String)>,
7909 edit_preview: &Option<language::EditPreview>,
7910 snapshot: &language::BufferSnapshot,
7911 window: &mut Window,
7912 cx: &mut App,
7913 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7914 let edit_start = edits
7915 .first()
7916 .unwrap()
7917 .0
7918 .start
7919 .to_display_point(editor_snapshot);
7920 let edit_end = edits
7921 .last()
7922 .unwrap()
7923 .0
7924 .end
7925 .to_display_point(editor_snapshot);
7926
7927 let is_visible = visible_row_range.contains(&edit_start.row())
7928 || visible_row_range.contains(&edit_end.row());
7929 if !is_visible {
7930 return None;
7931 }
7932
7933 let highlighted_edits =
7934 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7935
7936 let styled_text = highlighted_edits.to_styled_text(&style.text);
7937 let line_count = highlighted_edits.text.lines().count();
7938
7939 const BORDER_WIDTH: Pixels = px(1.);
7940
7941 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7942 let has_keybind = keybind.is_some();
7943
7944 let mut element = h_flex()
7945 .items_start()
7946 .child(
7947 h_flex()
7948 .bg(cx.theme().colors().editor_background)
7949 .border(BORDER_WIDTH)
7950 .shadow_sm()
7951 .border_color(cx.theme().colors().border)
7952 .rounded_l_lg()
7953 .when(line_count > 1, |el| el.rounded_br_lg())
7954 .pr_1()
7955 .child(styled_text),
7956 )
7957 .child(
7958 h_flex()
7959 .h(line_height + BORDER_WIDTH * 2.)
7960 .px_1p5()
7961 .gap_1()
7962 // Workaround: For some reason, there's a gap if we don't do this
7963 .ml(-BORDER_WIDTH)
7964 .shadow(smallvec![gpui::BoxShadow {
7965 color: gpui::black().opacity(0.05),
7966 offset: point(px(1.), px(1.)),
7967 blur_radius: px(2.),
7968 spread_radius: px(0.),
7969 }])
7970 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7971 .border(BORDER_WIDTH)
7972 .border_color(cx.theme().colors().border)
7973 .rounded_r_lg()
7974 .id("edit_prediction_diff_popover_keybind")
7975 .when(!has_keybind, |el| {
7976 let status_colors = cx.theme().status();
7977
7978 el.bg(status_colors.error_background)
7979 .border_color(status_colors.error.opacity(0.6))
7980 .child(Icon::new(IconName::Info).color(Color::Error))
7981 .cursor_default()
7982 .hoverable_tooltip(move |_window, cx| {
7983 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7984 })
7985 })
7986 .children(keybind),
7987 )
7988 .into_any();
7989
7990 let longest_row =
7991 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7992 let longest_line_width = if visible_row_range.contains(&longest_row) {
7993 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7994 } else {
7995 layout_line(
7996 longest_row,
7997 editor_snapshot,
7998 style,
7999 editor_width,
8000 |_| false,
8001 window,
8002 cx,
8003 )
8004 .width
8005 };
8006
8007 let viewport_bounds =
8008 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8009 right: -right_margin,
8010 ..Default::default()
8011 });
8012
8013 let x_after_longest =
8014 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8015 - scroll_pixel_position.x;
8016
8017 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8018
8019 // Fully visible if it can be displayed within the window (allow overlapping other
8020 // panes). However, this is only allowed if the popover starts within text_bounds.
8021 let can_position_to_the_right = x_after_longest < text_bounds.right()
8022 && x_after_longest + element_bounds.width < viewport_bounds.right();
8023
8024 let mut origin = if can_position_to_the_right {
8025 point(
8026 x_after_longest,
8027 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8028 - scroll_pixel_position.y,
8029 )
8030 } else {
8031 let cursor_row = newest_selection_head.map(|head| head.row());
8032 let above_edit = edit_start
8033 .row()
8034 .0
8035 .checked_sub(line_count as u32)
8036 .map(DisplayRow);
8037 let below_edit = Some(edit_end.row() + 1);
8038 let above_cursor =
8039 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8040 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8041
8042 // Place the edit popover adjacent to the edit if there is a location
8043 // available that is onscreen and does not obscure the cursor. Otherwise,
8044 // place it adjacent to the cursor.
8045 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8046 .into_iter()
8047 .flatten()
8048 .find(|&start_row| {
8049 let end_row = start_row + line_count as u32;
8050 visible_row_range.contains(&start_row)
8051 && visible_row_range.contains(&end_row)
8052 && cursor_row.map_or(true, |cursor_row| {
8053 !((start_row..end_row).contains(&cursor_row))
8054 })
8055 })?;
8056
8057 content_origin
8058 + point(
8059 -scroll_pixel_position.x,
8060 row_target.as_f32() * line_height - scroll_pixel_position.y,
8061 )
8062 };
8063
8064 origin.x -= BORDER_WIDTH;
8065
8066 window.defer_draw(element, origin, 1);
8067
8068 // Do not return an element, since it will already be drawn due to defer_draw.
8069 None
8070 }
8071
8072 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8073 px(30.)
8074 }
8075
8076 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8077 if self.read_only(cx) {
8078 cx.theme().players().read_only()
8079 } else {
8080 self.style.as_ref().unwrap().local_player
8081 }
8082 }
8083
8084 fn render_edit_prediction_accept_keybind(
8085 &self,
8086 window: &mut Window,
8087 cx: &App,
8088 ) -> Option<AnyElement> {
8089 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8090 let accept_keystroke = accept_binding.keystroke()?;
8091
8092 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8093
8094 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8095 Color::Accent
8096 } else {
8097 Color::Muted
8098 };
8099
8100 h_flex()
8101 .px_0p5()
8102 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8103 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8104 .text_size(TextSize::XSmall.rems(cx))
8105 .child(h_flex().children(ui::render_modifiers(
8106 &accept_keystroke.modifiers,
8107 PlatformStyle::platform(),
8108 Some(modifiers_color),
8109 Some(IconSize::XSmall.rems().into()),
8110 true,
8111 )))
8112 .when(is_platform_style_mac, |parent| {
8113 parent.child(accept_keystroke.key.clone())
8114 })
8115 .when(!is_platform_style_mac, |parent| {
8116 parent.child(
8117 Key::new(
8118 util::capitalize(&accept_keystroke.key),
8119 Some(Color::Default),
8120 )
8121 .size(Some(IconSize::XSmall.rems().into())),
8122 )
8123 })
8124 .into_any()
8125 .into()
8126 }
8127
8128 fn render_edit_prediction_line_popover(
8129 &self,
8130 label: impl Into<SharedString>,
8131 icon: Option<IconName>,
8132 window: &mut Window,
8133 cx: &App,
8134 ) -> Option<Stateful<Div>> {
8135 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8136
8137 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8138 let has_keybind = keybind.is_some();
8139
8140 let result = h_flex()
8141 .id("ep-line-popover")
8142 .py_0p5()
8143 .pl_1()
8144 .pr(padding_right)
8145 .gap_1()
8146 .rounded_md()
8147 .border_1()
8148 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8149 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8150 .shadow_sm()
8151 .when(!has_keybind, |el| {
8152 let status_colors = cx.theme().status();
8153
8154 el.bg(status_colors.error_background)
8155 .border_color(status_colors.error.opacity(0.6))
8156 .pl_2()
8157 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8158 .cursor_default()
8159 .hoverable_tooltip(move |_window, cx| {
8160 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8161 })
8162 })
8163 .children(keybind)
8164 .child(
8165 Label::new(label)
8166 .size(LabelSize::Small)
8167 .when(!has_keybind, |el| {
8168 el.color(cx.theme().status().error.into()).strikethrough()
8169 }),
8170 )
8171 .when(!has_keybind, |el| {
8172 el.child(
8173 h_flex().ml_1().child(
8174 Icon::new(IconName::Info)
8175 .size(IconSize::Small)
8176 .color(cx.theme().status().error.into()),
8177 ),
8178 )
8179 })
8180 .when_some(icon, |element, icon| {
8181 element.child(
8182 div()
8183 .mt(px(1.5))
8184 .child(Icon::new(icon).size(IconSize::Small)),
8185 )
8186 });
8187
8188 Some(result)
8189 }
8190
8191 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8192 let accent_color = cx.theme().colors().text_accent;
8193 let editor_bg_color = cx.theme().colors().editor_background;
8194 editor_bg_color.blend(accent_color.opacity(0.1))
8195 }
8196
8197 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8198 let accent_color = cx.theme().colors().text_accent;
8199 let editor_bg_color = cx.theme().colors().editor_background;
8200 editor_bg_color.blend(accent_color.opacity(0.6))
8201 }
8202
8203 fn render_edit_prediction_cursor_popover(
8204 &self,
8205 min_width: Pixels,
8206 max_width: Pixels,
8207 cursor_point: Point,
8208 style: &EditorStyle,
8209 accept_keystroke: Option<&gpui::Keystroke>,
8210 _window: &Window,
8211 cx: &mut Context<Editor>,
8212 ) -> Option<AnyElement> {
8213 let provider = self.edit_prediction_provider.as_ref()?;
8214
8215 if provider.provider.needs_terms_acceptance(cx) {
8216 return Some(
8217 h_flex()
8218 .min_w(min_width)
8219 .flex_1()
8220 .px_2()
8221 .py_1()
8222 .gap_3()
8223 .elevation_2(cx)
8224 .hover(|style| style.bg(cx.theme().colors().element_hover))
8225 .id("accept-terms")
8226 .cursor_pointer()
8227 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8228 .on_click(cx.listener(|this, _event, window, cx| {
8229 cx.stop_propagation();
8230 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8231 window.dispatch_action(
8232 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8233 cx,
8234 );
8235 }))
8236 .child(
8237 h_flex()
8238 .flex_1()
8239 .gap_2()
8240 .child(Icon::new(IconName::ZedPredict))
8241 .child(Label::new("Accept Terms of Service"))
8242 .child(div().w_full())
8243 .child(
8244 Icon::new(IconName::ArrowUpRight)
8245 .color(Color::Muted)
8246 .size(IconSize::Small),
8247 )
8248 .into_any_element(),
8249 )
8250 .into_any(),
8251 );
8252 }
8253
8254 let is_refreshing = provider.provider.is_refreshing(cx);
8255
8256 fn pending_completion_container() -> Div {
8257 h_flex()
8258 .h_full()
8259 .flex_1()
8260 .gap_2()
8261 .child(Icon::new(IconName::ZedPredict))
8262 }
8263
8264 let completion = match &self.active_inline_completion {
8265 Some(prediction) => {
8266 if !self.has_visible_completions_menu() {
8267 const RADIUS: Pixels = px(6.);
8268 const BORDER_WIDTH: Pixels = px(1.);
8269
8270 return Some(
8271 h_flex()
8272 .elevation_2(cx)
8273 .border(BORDER_WIDTH)
8274 .border_color(cx.theme().colors().border)
8275 .when(accept_keystroke.is_none(), |el| {
8276 el.border_color(cx.theme().status().error)
8277 })
8278 .rounded(RADIUS)
8279 .rounded_tl(px(0.))
8280 .overflow_hidden()
8281 .child(div().px_1p5().child(match &prediction.completion {
8282 InlineCompletion::Move { target, snapshot } => {
8283 use text::ToPoint as _;
8284 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8285 {
8286 Icon::new(IconName::ZedPredictDown)
8287 } else {
8288 Icon::new(IconName::ZedPredictUp)
8289 }
8290 }
8291 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8292 }))
8293 .child(
8294 h_flex()
8295 .gap_1()
8296 .py_1()
8297 .px_2()
8298 .rounded_r(RADIUS - BORDER_WIDTH)
8299 .border_l_1()
8300 .border_color(cx.theme().colors().border)
8301 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8302 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8303 el.child(
8304 Label::new("Hold")
8305 .size(LabelSize::Small)
8306 .when(accept_keystroke.is_none(), |el| {
8307 el.strikethrough()
8308 })
8309 .line_height_style(LineHeightStyle::UiLabel),
8310 )
8311 })
8312 .id("edit_prediction_cursor_popover_keybind")
8313 .when(accept_keystroke.is_none(), |el| {
8314 let status_colors = cx.theme().status();
8315
8316 el.bg(status_colors.error_background)
8317 .border_color(status_colors.error.opacity(0.6))
8318 .child(Icon::new(IconName::Info).color(Color::Error))
8319 .cursor_default()
8320 .hoverable_tooltip(move |_window, cx| {
8321 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8322 .into()
8323 })
8324 })
8325 .when_some(
8326 accept_keystroke.as_ref(),
8327 |el, accept_keystroke| {
8328 el.child(h_flex().children(ui::render_modifiers(
8329 &accept_keystroke.modifiers,
8330 PlatformStyle::platform(),
8331 Some(Color::Default),
8332 Some(IconSize::XSmall.rems().into()),
8333 false,
8334 )))
8335 },
8336 ),
8337 )
8338 .into_any(),
8339 );
8340 }
8341
8342 self.render_edit_prediction_cursor_popover_preview(
8343 prediction,
8344 cursor_point,
8345 style,
8346 cx,
8347 )?
8348 }
8349
8350 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8351 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8352 stale_completion,
8353 cursor_point,
8354 style,
8355 cx,
8356 )?,
8357
8358 None => {
8359 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8360 }
8361 },
8362
8363 None => pending_completion_container().child(Label::new("No Prediction")),
8364 };
8365
8366 let completion = if is_refreshing {
8367 completion
8368 .with_animation(
8369 "loading-completion",
8370 Animation::new(Duration::from_secs(2))
8371 .repeat()
8372 .with_easing(pulsating_between(0.4, 0.8)),
8373 |label, delta| label.opacity(delta),
8374 )
8375 .into_any_element()
8376 } else {
8377 completion.into_any_element()
8378 };
8379
8380 let has_completion = self.active_inline_completion.is_some();
8381
8382 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8383 Some(
8384 h_flex()
8385 .min_w(min_width)
8386 .max_w(max_width)
8387 .flex_1()
8388 .elevation_2(cx)
8389 .border_color(cx.theme().colors().border)
8390 .child(
8391 div()
8392 .flex_1()
8393 .py_1()
8394 .px_2()
8395 .overflow_hidden()
8396 .child(completion),
8397 )
8398 .when_some(accept_keystroke, |el, accept_keystroke| {
8399 if !accept_keystroke.modifiers.modified() {
8400 return el;
8401 }
8402
8403 el.child(
8404 h_flex()
8405 .h_full()
8406 .border_l_1()
8407 .rounded_r_lg()
8408 .border_color(cx.theme().colors().border)
8409 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8410 .gap_1()
8411 .py_1()
8412 .px_2()
8413 .child(
8414 h_flex()
8415 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8416 .when(is_platform_style_mac, |parent| parent.gap_1())
8417 .child(h_flex().children(ui::render_modifiers(
8418 &accept_keystroke.modifiers,
8419 PlatformStyle::platform(),
8420 Some(if !has_completion {
8421 Color::Muted
8422 } else {
8423 Color::Default
8424 }),
8425 None,
8426 false,
8427 ))),
8428 )
8429 .child(Label::new("Preview").into_any_element())
8430 .opacity(if has_completion { 1.0 } else { 0.4 }),
8431 )
8432 })
8433 .into_any(),
8434 )
8435 }
8436
8437 fn render_edit_prediction_cursor_popover_preview(
8438 &self,
8439 completion: &InlineCompletionState,
8440 cursor_point: Point,
8441 style: &EditorStyle,
8442 cx: &mut Context<Editor>,
8443 ) -> Option<Div> {
8444 use text::ToPoint as _;
8445
8446 fn render_relative_row_jump(
8447 prefix: impl Into<String>,
8448 current_row: u32,
8449 target_row: u32,
8450 ) -> Div {
8451 let (row_diff, arrow) = if target_row < current_row {
8452 (current_row - target_row, IconName::ArrowUp)
8453 } else {
8454 (target_row - current_row, IconName::ArrowDown)
8455 };
8456
8457 h_flex()
8458 .child(
8459 Label::new(format!("{}{}", prefix.into(), row_diff))
8460 .color(Color::Muted)
8461 .size(LabelSize::Small),
8462 )
8463 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8464 }
8465
8466 match &completion.completion {
8467 InlineCompletion::Move {
8468 target, snapshot, ..
8469 } => Some(
8470 h_flex()
8471 .px_2()
8472 .gap_2()
8473 .flex_1()
8474 .child(
8475 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8476 Icon::new(IconName::ZedPredictDown)
8477 } else {
8478 Icon::new(IconName::ZedPredictUp)
8479 },
8480 )
8481 .child(Label::new("Jump to Edit")),
8482 ),
8483
8484 InlineCompletion::Edit {
8485 edits,
8486 edit_preview,
8487 snapshot,
8488 display_mode: _,
8489 } => {
8490 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8491
8492 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8493 &snapshot,
8494 &edits,
8495 edit_preview.as_ref()?,
8496 true,
8497 cx,
8498 )
8499 .first_line_preview();
8500
8501 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8502 .with_default_highlights(&style.text, highlighted_edits.highlights);
8503
8504 let preview = h_flex()
8505 .gap_1()
8506 .min_w_16()
8507 .child(styled_text)
8508 .when(has_more_lines, |parent| parent.child("…"));
8509
8510 let left = if first_edit_row != cursor_point.row {
8511 render_relative_row_jump("", cursor_point.row, first_edit_row)
8512 .into_any_element()
8513 } else {
8514 Icon::new(IconName::ZedPredict).into_any_element()
8515 };
8516
8517 Some(
8518 h_flex()
8519 .h_full()
8520 .flex_1()
8521 .gap_2()
8522 .pr_1()
8523 .overflow_x_hidden()
8524 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8525 .child(left)
8526 .child(preview),
8527 )
8528 }
8529 }
8530 }
8531
8532 fn render_context_menu(
8533 &self,
8534 style: &EditorStyle,
8535 max_height_in_lines: u32,
8536 window: &mut Window,
8537 cx: &mut Context<Editor>,
8538 ) -> Option<AnyElement> {
8539 let menu = self.context_menu.borrow();
8540 let menu = menu.as_ref()?;
8541 if !menu.visible() {
8542 return None;
8543 };
8544 Some(menu.render(style, max_height_in_lines, window, cx))
8545 }
8546
8547 fn render_context_menu_aside(
8548 &mut self,
8549 max_size: Size<Pixels>,
8550 window: &mut Window,
8551 cx: &mut Context<Editor>,
8552 ) -> Option<AnyElement> {
8553 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8554 if menu.visible() {
8555 menu.render_aside(self, max_size, window, cx)
8556 } else {
8557 None
8558 }
8559 })
8560 }
8561
8562 fn hide_context_menu(
8563 &mut self,
8564 window: &mut Window,
8565 cx: &mut Context<Self>,
8566 ) -> Option<CodeContextMenu> {
8567 cx.notify();
8568 self.completion_tasks.clear();
8569 let context_menu = self.context_menu.borrow_mut().take();
8570 self.stale_inline_completion_in_menu.take();
8571 self.update_visible_inline_completion(window, cx);
8572 context_menu
8573 }
8574
8575 fn show_snippet_choices(
8576 &mut self,
8577 choices: &Vec<String>,
8578 selection: Range<Anchor>,
8579 cx: &mut Context<Self>,
8580 ) {
8581 if selection.start.buffer_id.is_none() {
8582 return;
8583 }
8584 let buffer_id = selection.start.buffer_id.unwrap();
8585 let buffer = self.buffer().read(cx).buffer(buffer_id);
8586 let id = post_inc(&mut self.next_completion_id);
8587 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8588
8589 if let Some(buffer) = buffer {
8590 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8591 CompletionsMenu::new_snippet_choices(
8592 id,
8593 true,
8594 choices,
8595 selection,
8596 buffer,
8597 snippet_sort_order,
8598 ),
8599 ));
8600 }
8601 }
8602
8603 pub fn insert_snippet(
8604 &mut self,
8605 insertion_ranges: &[Range<usize>],
8606 snippet: Snippet,
8607 window: &mut Window,
8608 cx: &mut Context<Self>,
8609 ) -> Result<()> {
8610 struct Tabstop<T> {
8611 is_end_tabstop: bool,
8612 ranges: Vec<Range<T>>,
8613 choices: Option<Vec<String>>,
8614 }
8615
8616 let tabstops = self.buffer.update(cx, |buffer, cx| {
8617 let snippet_text: Arc<str> = snippet.text.clone().into();
8618 let edits = insertion_ranges
8619 .iter()
8620 .cloned()
8621 .map(|range| (range, snippet_text.clone()));
8622 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8623
8624 let snapshot = &*buffer.read(cx);
8625 let snippet = &snippet;
8626 snippet
8627 .tabstops
8628 .iter()
8629 .map(|tabstop| {
8630 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8631 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8632 });
8633 let mut tabstop_ranges = tabstop
8634 .ranges
8635 .iter()
8636 .flat_map(|tabstop_range| {
8637 let mut delta = 0_isize;
8638 insertion_ranges.iter().map(move |insertion_range| {
8639 let insertion_start = insertion_range.start as isize + delta;
8640 delta +=
8641 snippet.text.len() as isize - insertion_range.len() as isize;
8642
8643 let start = ((insertion_start + tabstop_range.start) as usize)
8644 .min(snapshot.len());
8645 let end = ((insertion_start + tabstop_range.end) as usize)
8646 .min(snapshot.len());
8647 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8648 })
8649 })
8650 .collect::<Vec<_>>();
8651 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8652
8653 Tabstop {
8654 is_end_tabstop,
8655 ranges: tabstop_ranges,
8656 choices: tabstop.choices.clone(),
8657 }
8658 })
8659 .collect::<Vec<_>>()
8660 });
8661 if let Some(tabstop) = tabstops.first() {
8662 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8663 s.select_ranges(tabstop.ranges.iter().cloned());
8664 });
8665
8666 if let Some(choices) = &tabstop.choices {
8667 if let Some(selection) = tabstop.ranges.first() {
8668 self.show_snippet_choices(choices, selection.clone(), cx)
8669 }
8670 }
8671
8672 // If we're already at the last tabstop and it's at the end of the snippet,
8673 // we're done, we don't need to keep the state around.
8674 if !tabstop.is_end_tabstop {
8675 let choices = tabstops
8676 .iter()
8677 .map(|tabstop| tabstop.choices.clone())
8678 .collect();
8679
8680 let ranges = tabstops
8681 .into_iter()
8682 .map(|tabstop| tabstop.ranges)
8683 .collect::<Vec<_>>();
8684
8685 self.snippet_stack.push(SnippetState {
8686 active_index: 0,
8687 ranges,
8688 choices,
8689 });
8690 }
8691
8692 // Check whether the just-entered snippet ends with an auto-closable bracket.
8693 if self.autoclose_regions.is_empty() {
8694 let snapshot = self.buffer.read(cx).snapshot(cx);
8695 for selection in &mut self.selections.all::<Point>(cx) {
8696 let selection_head = selection.head();
8697 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8698 continue;
8699 };
8700
8701 let mut bracket_pair = None;
8702 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8703 let prev_chars = snapshot
8704 .reversed_chars_at(selection_head)
8705 .collect::<String>();
8706 for (pair, enabled) in scope.brackets() {
8707 if enabled
8708 && pair.close
8709 && prev_chars.starts_with(pair.start.as_str())
8710 && next_chars.starts_with(pair.end.as_str())
8711 {
8712 bracket_pair = Some(pair.clone());
8713 break;
8714 }
8715 }
8716 if let Some(pair) = bracket_pair {
8717 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8718 let autoclose_enabled =
8719 self.use_autoclose && snapshot_settings.use_autoclose;
8720 if autoclose_enabled {
8721 let start = snapshot.anchor_after(selection_head);
8722 let end = snapshot.anchor_after(selection_head);
8723 self.autoclose_regions.push(AutocloseRegion {
8724 selection_id: selection.id,
8725 range: start..end,
8726 pair,
8727 });
8728 }
8729 }
8730 }
8731 }
8732 }
8733 Ok(())
8734 }
8735
8736 pub fn move_to_next_snippet_tabstop(
8737 &mut self,
8738 window: &mut Window,
8739 cx: &mut Context<Self>,
8740 ) -> bool {
8741 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8742 }
8743
8744 pub fn move_to_prev_snippet_tabstop(
8745 &mut self,
8746 window: &mut Window,
8747 cx: &mut Context<Self>,
8748 ) -> bool {
8749 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8750 }
8751
8752 pub fn move_to_snippet_tabstop(
8753 &mut self,
8754 bias: Bias,
8755 window: &mut Window,
8756 cx: &mut Context<Self>,
8757 ) -> bool {
8758 if let Some(mut snippet) = self.snippet_stack.pop() {
8759 match bias {
8760 Bias::Left => {
8761 if snippet.active_index > 0 {
8762 snippet.active_index -= 1;
8763 } else {
8764 self.snippet_stack.push(snippet);
8765 return false;
8766 }
8767 }
8768 Bias::Right => {
8769 if snippet.active_index + 1 < snippet.ranges.len() {
8770 snippet.active_index += 1;
8771 } else {
8772 self.snippet_stack.push(snippet);
8773 return false;
8774 }
8775 }
8776 }
8777 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8779 s.select_anchor_ranges(current_ranges.iter().cloned())
8780 });
8781
8782 if let Some(choices) = &snippet.choices[snippet.active_index] {
8783 if let Some(selection) = current_ranges.first() {
8784 self.show_snippet_choices(&choices, selection.clone(), cx);
8785 }
8786 }
8787
8788 // If snippet state is not at the last tabstop, push it back on the stack
8789 if snippet.active_index + 1 < snippet.ranges.len() {
8790 self.snippet_stack.push(snippet);
8791 }
8792 return true;
8793 }
8794 }
8795
8796 false
8797 }
8798
8799 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8800 self.transact(window, cx, |this, window, cx| {
8801 this.select_all(&SelectAll, window, cx);
8802 this.insert("", window, cx);
8803 });
8804 }
8805
8806 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8807 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8808 self.transact(window, cx, |this, window, cx| {
8809 this.select_autoclose_pair(window, cx);
8810 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8811 if !this.linked_edit_ranges.is_empty() {
8812 let selections = this.selections.all::<MultiBufferPoint>(cx);
8813 let snapshot = this.buffer.read(cx).snapshot(cx);
8814
8815 for selection in selections.iter() {
8816 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8817 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8818 if selection_start.buffer_id != selection_end.buffer_id {
8819 continue;
8820 }
8821 if let Some(ranges) =
8822 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8823 {
8824 for (buffer, entries) in ranges {
8825 linked_ranges.entry(buffer).or_default().extend(entries);
8826 }
8827 }
8828 }
8829 }
8830
8831 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8832 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8833 for selection in &mut selections {
8834 if selection.is_empty() {
8835 let old_head = selection.head();
8836 let mut new_head =
8837 movement::left(&display_map, old_head.to_display_point(&display_map))
8838 .to_point(&display_map);
8839 if let Some((buffer, line_buffer_range)) = display_map
8840 .buffer_snapshot
8841 .buffer_line_for_row(MultiBufferRow(old_head.row))
8842 {
8843 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8844 let indent_len = match indent_size.kind {
8845 IndentKind::Space => {
8846 buffer.settings_at(line_buffer_range.start, cx).tab_size
8847 }
8848 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8849 };
8850 if old_head.column <= indent_size.len && old_head.column > 0 {
8851 let indent_len = indent_len.get();
8852 new_head = cmp::min(
8853 new_head,
8854 MultiBufferPoint::new(
8855 old_head.row,
8856 ((old_head.column - 1) / indent_len) * indent_len,
8857 ),
8858 );
8859 }
8860 }
8861
8862 selection.set_head(new_head, SelectionGoal::None);
8863 }
8864 }
8865
8866 this.signature_help_state.set_backspace_pressed(true);
8867 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8868 s.select(selections)
8869 });
8870 this.insert("", window, cx);
8871 let empty_str: Arc<str> = Arc::from("");
8872 for (buffer, edits) in linked_ranges {
8873 let snapshot = buffer.read(cx).snapshot();
8874 use text::ToPoint as TP;
8875
8876 let edits = edits
8877 .into_iter()
8878 .map(|range| {
8879 let end_point = TP::to_point(&range.end, &snapshot);
8880 let mut start_point = TP::to_point(&range.start, &snapshot);
8881
8882 if end_point == start_point {
8883 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8884 .saturating_sub(1);
8885 start_point =
8886 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8887 };
8888
8889 (start_point..end_point, empty_str.clone())
8890 })
8891 .sorted_by_key(|(range, _)| range.start)
8892 .collect::<Vec<_>>();
8893 buffer.update(cx, |this, cx| {
8894 this.edit(edits, None, cx);
8895 })
8896 }
8897 this.refresh_inline_completion(true, false, window, cx);
8898 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8899 });
8900 }
8901
8902 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8903 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8904 self.transact(window, cx, |this, window, cx| {
8905 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8906 s.move_with(|map, selection| {
8907 if selection.is_empty() {
8908 let cursor = movement::right(map, selection.head());
8909 selection.end = cursor;
8910 selection.reversed = true;
8911 selection.goal = SelectionGoal::None;
8912 }
8913 })
8914 });
8915 this.insert("", window, cx);
8916 this.refresh_inline_completion(true, false, window, cx);
8917 });
8918 }
8919
8920 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8922 if self.move_to_prev_snippet_tabstop(window, cx) {
8923 return;
8924 }
8925 self.outdent(&Outdent, window, cx);
8926 }
8927
8928 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8929 if self.move_to_next_snippet_tabstop(window, cx) {
8930 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8931 return;
8932 }
8933 if self.read_only(cx) {
8934 return;
8935 }
8936 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8937 let mut selections = self.selections.all_adjusted(cx);
8938 let buffer = self.buffer.read(cx);
8939 let snapshot = buffer.snapshot(cx);
8940 let rows_iter = selections.iter().map(|s| s.head().row);
8941 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8942
8943 let has_some_cursor_in_whitespace = selections
8944 .iter()
8945 .filter(|selection| selection.is_empty())
8946 .any(|selection| {
8947 let cursor = selection.head();
8948 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8949 cursor.column < current_indent.len
8950 });
8951
8952 let mut edits = Vec::new();
8953 let mut prev_edited_row = 0;
8954 let mut row_delta = 0;
8955 for selection in &mut selections {
8956 if selection.start.row != prev_edited_row {
8957 row_delta = 0;
8958 }
8959 prev_edited_row = selection.end.row;
8960
8961 // If the selection is non-empty, then increase the indentation of the selected lines.
8962 if !selection.is_empty() {
8963 row_delta =
8964 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8965 continue;
8966 }
8967
8968 let cursor = selection.head();
8969 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8970 if let Some(suggested_indent) =
8971 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8972 {
8973 // Don't do anything if already at suggested indent
8974 // and there is any other cursor which is not
8975 if has_some_cursor_in_whitespace
8976 && cursor.column == current_indent.len
8977 && current_indent.len == suggested_indent.len
8978 {
8979 continue;
8980 }
8981
8982 // Adjust line and move cursor to suggested indent
8983 // if cursor is not at suggested indent
8984 if cursor.column < suggested_indent.len
8985 && cursor.column <= current_indent.len
8986 && current_indent.len <= suggested_indent.len
8987 {
8988 selection.start = Point::new(cursor.row, suggested_indent.len);
8989 selection.end = selection.start;
8990 if row_delta == 0 {
8991 edits.extend(Buffer::edit_for_indent_size_adjustment(
8992 cursor.row,
8993 current_indent,
8994 suggested_indent,
8995 ));
8996 row_delta = suggested_indent.len - current_indent.len;
8997 }
8998 continue;
8999 }
9000
9001 // If current indent is more than suggested indent
9002 // only move cursor to current indent and skip indent
9003 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9004 selection.start = Point::new(cursor.row, current_indent.len);
9005 selection.end = selection.start;
9006 continue;
9007 }
9008 }
9009
9010 // Otherwise, insert a hard or soft tab.
9011 let settings = buffer.language_settings_at(cursor, cx);
9012 let tab_size = if settings.hard_tabs {
9013 IndentSize::tab()
9014 } else {
9015 let tab_size = settings.tab_size.get();
9016 let indent_remainder = snapshot
9017 .text_for_range(Point::new(cursor.row, 0)..cursor)
9018 .flat_map(str::chars)
9019 .fold(row_delta % tab_size, |counter: u32, c| {
9020 if c == '\t' {
9021 0
9022 } else {
9023 (counter + 1) % tab_size
9024 }
9025 });
9026
9027 let chars_to_next_tab_stop = tab_size - indent_remainder;
9028 IndentSize::spaces(chars_to_next_tab_stop)
9029 };
9030 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9031 selection.end = selection.start;
9032 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9033 row_delta += tab_size.len;
9034 }
9035
9036 self.transact(window, cx, |this, window, cx| {
9037 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9038 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9039 s.select(selections)
9040 });
9041 this.refresh_inline_completion(true, false, window, cx);
9042 });
9043 }
9044
9045 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9046 if self.read_only(cx) {
9047 return;
9048 }
9049 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9050 let mut selections = self.selections.all::<Point>(cx);
9051 let mut prev_edited_row = 0;
9052 let mut row_delta = 0;
9053 let mut edits = Vec::new();
9054 let buffer = self.buffer.read(cx);
9055 let snapshot = buffer.snapshot(cx);
9056 for selection in &mut selections {
9057 if selection.start.row != prev_edited_row {
9058 row_delta = 0;
9059 }
9060 prev_edited_row = selection.end.row;
9061
9062 row_delta =
9063 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9064 }
9065
9066 self.transact(window, cx, |this, window, cx| {
9067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9068 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9069 s.select(selections)
9070 });
9071 });
9072 }
9073
9074 fn indent_selection(
9075 buffer: &MultiBuffer,
9076 snapshot: &MultiBufferSnapshot,
9077 selection: &mut Selection<Point>,
9078 edits: &mut Vec<(Range<Point>, String)>,
9079 delta_for_start_row: u32,
9080 cx: &App,
9081 ) -> u32 {
9082 let settings = buffer.language_settings_at(selection.start, cx);
9083 let tab_size = settings.tab_size.get();
9084 let indent_kind = if settings.hard_tabs {
9085 IndentKind::Tab
9086 } else {
9087 IndentKind::Space
9088 };
9089 let mut start_row = selection.start.row;
9090 let mut end_row = selection.end.row + 1;
9091
9092 // If a selection ends at the beginning of a line, don't indent
9093 // that last line.
9094 if selection.end.column == 0 && selection.end.row > selection.start.row {
9095 end_row -= 1;
9096 }
9097
9098 // Avoid re-indenting a row that has already been indented by a
9099 // previous selection, but still update this selection's column
9100 // to reflect that indentation.
9101 if delta_for_start_row > 0 {
9102 start_row += 1;
9103 selection.start.column += delta_for_start_row;
9104 if selection.end.row == selection.start.row {
9105 selection.end.column += delta_for_start_row;
9106 }
9107 }
9108
9109 let mut delta_for_end_row = 0;
9110 let has_multiple_rows = start_row + 1 != end_row;
9111 for row in start_row..end_row {
9112 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9113 let indent_delta = match (current_indent.kind, indent_kind) {
9114 (IndentKind::Space, IndentKind::Space) => {
9115 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9116 IndentSize::spaces(columns_to_next_tab_stop)
9117 }
9118 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9119 (_, IndentKind::Tab) => IndentSize::tab(),
9120 };
9121
9122 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9123 0
9124 } else {
9125 selection.start.column
9126 };
9127 let row_start = Point::new(row, start);
9128 edits.push((
9129 row_start..row_start,
9130 indent_delta.chars().collect::<String>(),
9131 ));
9132
9133 // Update this selection's endpoints to reflect the indentation.
9134 if row == selection.start.row {
9135 selection.start.column += indent_delta.len;
9136 }
9137 if row == selection.end.row {
9138 selection.end.column += indent_delta.len;
9139 delta_for_end_row = indent_delta.len;
9140 }
9141 }
9142
9143 if selection.start.row == selection.end.row {
9144 delta_for_start_row + delta_for_end_row
9145 } else {
9146 delta_for_end_row
9147 }
9148 }
9149
9150 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9151 if self.read_only(cx) {
9152 return;
9153 }
9154 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9155 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9156 let selections = self.selections.all::<Point>(cx);
9157 let mut deletion_ranges = Vec::new();
9158 let mut last_outdent = None;
9159 {
9160 let buffer = self.buffer.read(cx);
9161 let snapshot = buffer.snapshot(cx);
9162 for selection in &selections {
9163 let settings = buffer.language_settings_at(selection.start, cx);
9164 let tab_size = settings.tab_size.get();
9165 let mut rows = selection.spanned_rows(false, &display_map);
9166
9167 // Avoid re-outdenting a row that has already been outdented by a
9168 // previous selection.
9169 if let Some(last_row) = last_outdent {
9170 if last_row == rows.start {
9171 rows.start = rows.start.next_row();
9172 }
9173 }
9174 let has_multiple_rows = rows.len() > 1;
9175 for row in rows.iter_rows() {
9176 let indent_size = snapshot.indent_size_for_line(row);
9177 if indent_size.len > 0 {
9178 let deletion_len = match indent_size.kind {
9179 IndentKind::Space => {
9180 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9181 if columns_to_prev_tab_stop == 0 {
9182 tab_size
9183 } else {
9184 columns_to_prev_tab_stop
9185 }
9186 }
9187 IndentKind::Tab => 1,
9188 };
9189 let start = if has_multiple_rows
9190 || deletion_len > selection.start.column
9191 || indent_size.len < selection.start.column
9192 {
9193 0
9194 } else {
9195 selection.start.column - deletion_len
9196 };
9197 deletion_ranges.push(
9198 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9199 );
9200 last_outdent = Some(row);
9201 }
9202 }
9203 }
9204 }
9205
9206 self.transact(window, cx, |this, window, cx| {
9207 this.buffer.update(cx, |buffer, cx| {
9208 let empty_str: Arc<str> = Arc::default();
9209 buffer.edit(
9210 deletion_ranges
9211 .into_iter()
9212 .map(|range| (range, empty_str.clone())),
9213 None,
9214 cx,
9215 );
9216 });
9217 let selections = this.selections.all::<usize>(cx);
9218 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9219 s.select(selections)
9220 });
9221 });
9222 }
9223
9224 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9225 if self.read_only(cx) {
9226 return;
9227 }
9228 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9229 let selections = self
9230 .selections
9231 .all::<usize>(cx)
9232 .into_iter()
9233 .map(|s| s.range());
9234
9235 self.transact(window, cx, |this, window, cx| {
9236 this.buffer.update(cx, |buffer, cx| {
9237 buffer.autoindent_ranges(selections, cx);
9238 });
9239 let selections = this.selections.all::<usize>(cx);
9240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9241 s.select(selections)
9242 });
9243 });
9244 }
9245
9246 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9247 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9248 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9249 let selections = self.selections.all::<Point>(cx);
9250
9251 let mut new_cursors = Vec::new();
9252 let mut edit_ranges = Vec::new();
9253 let mut selections = selections.iter().peekable();
9254 while let Some(selection) = selections.next() {
9255 let mut rows = selection.spanned_rows(false, &display_map);
9256 let goal_display_column = selection.head().to_display_point(&display_map).column();
9257
9258 // Accumulate contiguous regions of rows that we want to delete.
9259 while let Some(next_selection) = selections.peek() {
9260 let next_rows = next_selection.spanned_rows(false, &display_map);
9261 if next_rows.start <= rows.end {
9262 rows.end = next_rows.end;
9263 selections.next().unwrap();
9264 } else {
9265 break;
9266 }
9267 }
9268
9269 let buffer = &display_map.buffer_snapshot;
9270 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9271 let edit_end;
9272 let cursor_buffer_row;
9273 if buffer.max_point().row >= rows.end.0 {
9274 // If there's a line after the range, delete the \n from the end of the row range
9275 // and position the cursor on the next line.
9276 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9277 cursor_buffer_row = rows.end;
9278 } else {
9279 // If there isn't a line after the range, delete the \n from the line before the
9280 // start of the row range and position the cursor there.
9281 edit_start = edit_start.saturating_sub(1);
9282 edit_end = buffer.len();
9283 cursor_buffer_row = rows.start.previous_row();
9284 }
9285
9286 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9287 *cursor.column_mut() =
9288 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9289
9290 new_cursors.push((
9291 selection.id,
9292 buffer.anchor_after(cursor.to_point(&display_map)),
9293 ));
9294 edit_ranges.push(edit_start..edit_end);
9295 }
9296
9297 self.transact(window, cx, |this, window, cx| {
9298 let buffer = this.buffer.update(cx, |buffer, cx| {
9299 let empty_str: Arc<str> = Arc::default();
9300 buffer.edit(
9301 edit_ranges
9302 .into_iter()
9303 .map(|range| (range, empty_str.clone())),
9304 None,
9305 cx,
9306 );
9307 buffer.snapshot(cx)
9308 });
9309 let new_selections = new_cursors
9310 .into_iter()
9311 .map(|(id, cursor)| {
9312 let cursor = cursor.to_point(&buffer);
9313 Selection {
9314 id,
9315 start: cursor,
9316 end: cursor,
9317 reversed: false,
9318 goal: SelectionGoal::None,
9319 }
9320 })
9321 .collect();
9322
9323 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9324 s.select(new_selections);
9325 });
9326 });
9327 }
9328
9329 pub fn join_lines_impl(
9330 &mut self,
9331 insert_whitespace: bool,
9332 window: &mut Window,
9333 cx: &mut Context<Self>,
9334 ) {
9335 if self.read_only(cx) {
9336 return;
9337 }
9338 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9339 for selection in self.selections.all::<Point>(cx) {
9340 let start = MultiBufferRow(selection.start.row);
9341 // Treat single line selections as if they include the next line. Otherwise this action
9342 // would do nothing for single line selections individual cursors.
9343 let end = if selection.start.row == selection.end.row {
9344 MultiBufferRow(selection.start.row + 1)
9345 } else {
9346 MultiBufferRow(selection.end.row)
9347 };
9348
9349 if let Some(last_row_range) = row_ranges.last_mut() {
9350 if start <= last_row_range.end {
9351 last_row_range.end = end;
9352 continue;
9353 }
9354 }
9355 row_ranges.push(start..end);
9356 }
9357
9358 let snapshot = self.buffer.read(cx).snapshot(cx);
9359 let mut cursor_positions = Vec::new();
9360 for row_range in &row_ranges {
9361 let anchor = snapshot.anchor_before(Point::new(
9362 row_range.end.previous_row().0,
9363 snapshot.line_len(row_range.end.previous_row()),
9364 ));
9365 cursor_positions.push(anchor..anchor);
9366 }
9367
9368 self.transact(window, cx, |this, window, cx| {
9369 for row_range in row_ranges.into_iter().rev() {
9370 for row in row_range.iter_rows().rev() {
9371 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9372 let next_line_row = row.next_row();
9373 let indent = snapshot.indent_size_for_line(next_line_row);
9374 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9375
9376 let replace =
9377 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9378 " "
9379 } else {
9380 ""
9381 };
9382
9383 this.buffer.update(cx, |buffer, cx| {
9384 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9385 });
9386 }
9387 }
9388
9389 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9390 s.select_anchor_ranges(cursor_positions)
9391 });
9392 });
9393 }
9394
9395 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9397 self.join_lines_impl(true, window, cx);
9398 }
9399
9400 pub fn sort_lines_case_sensitive(
9401 &mut self,
9402 _: &SortLinesCaseSensitive,
9403 window: &mut Window,
9404 cx: &mut Context<Self>,
9405 ) {
9406 self.manipulate_lines(window, cx, |lines| lines.sort())
9407 }
9408
9409 pub fn sort_lines_case_insensitive(
9410 &mut self,
9411 _: &SortLinesCaseInsensitive,
9412 window: &mut Window,
9413 cx: &mut Context<Self>,
9414 ) {
9415 self.manipulate_lines(window, cx, |lines| {
9416 lines.sort_by_key(|line| line.to_lowercase())
9417 })
9418 }
9419
9420 pub fn unique_lines_case_insensitive(
9421 &mut self,
9422 _: &UniqueLinesCaseInsensitive,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) {
9426 self.manipulate_lines(window, cx, |lines| {
9427 let mut seen = HashSet::default();
9428 lines.retain(|line| seen.insert(line.to_lowercase()));
9429 })
9430 }
9431
9432 pub fn unique_lines_case_sensitive(
9433 &mut self,
9434 _: &UniqueLinesCaseSensitive,
9435 window: &mut Window,
9436 cx: &mut Context<Self>,
9437 ) {
9438 self.manipulate_lines(window, cx, |lines| {
9439 let mut seen = HashSet::default();
9440 lines.retain(|line| seen.insert(*line));
9441 })
9442 }
9443
9444 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9445 let Some(project) = self.project.clone() else {
9446 return;
9447 };
9448 self.reload(project, window, cx)
9449 .detach_and_notify_err(window, cx);
9450 }
9451
9452 pub fn restore_file(
9453 &mut self,
9454 _: &::git::RestoreFile,
9455 window: &mut Window,
9456 cx: &mut Context<Self>,
9457 ) {
9458 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9459 let mut buffer_ids = HashSet::default();
9460 let snapshot = self.buffer().read(cx).snapshot(cx);
9461 for selection in self.selections.all::<usize>(cx) {
9462 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9463 }
9464
9465 let buffer = self.buffer().read(cx);
9466 let ranges = buffer_ids
9467 .into_iter()
9468 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9469 .collect::<Vec<_>>();
9470
9471 self.restore_hunks_in_ranges(ranges, window, cx);
9472 }
9473
9474 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9476 let selections = self
9477 .selections
9478 .all(cx)
9479 .into_iter()
9480 .map(|s| s.range())
9481 .collect();
9482 self.restore_hunks_in_ranges(selections, window, cx);
9483 }
9484
9485 pub fn restore_hunks_in_ranges(
9486 &mut self,
9487 ranges: Vec<Range<Point>>,
9488 window: &mut Window,
9489 cx: &mut Context<Editor>,
9490 ) {
9491 let mut revert_changes = HashMap::default();
9492 let chunk_by = self
9493 .snapshot(window, cx)
9494 .hunks_for_ranges(ranges)
9495 .into_iter()
9496 .chunk_by(|hunk| hunk.buffer_id);
9497 for (buffer_id, hunks) in &chunk_by {
9498 let hunks = hunks.collect::<Vec<_>>();
9499 for hunk in &hunks {
9500 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9501 }
9502 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9503 }
9504 drop(chunk_by);
9505 if !revert_changes.is_empty() {
9506 self.transact(window, cx, |editor, window, cx| {
9507 editor.restore(revert_changes, window, cx);
9508 });
9509 }
9510 }
9511
9512 pub fn open_active_item_in_terminal(
9513 &mut self,
9514 _: &OpenInTerminal,
9515 window: &mut Window,
9516 cx: &mut Context<Self>,
9517 ) {
9518 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9519 let project_path = buffer.read(cx).project_path(cx)?;
9520 let project = self.project.as_ref()?.read(cx);
9521 let entry = project.entry_for_path(&project_path, cx)?;
9522 let parent = match &entry.canonical_path {
9523 Some(canonical_path) => canonical_path.to_path_buf(),
9524 None => project.absolute_path(&project_path, cx)?,
9525 }
9526 .parent()?
9527 .to_path_buf();
9528 Some(parent)
9529 }) {
9530 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9531 }
9532 }
9533
9534 fn set_breakpoint_context_menu(
9535 &mut self,
9536 display_row: DisplayRow,
9537 position: Option<Anchor>,
9538 clicked_point: gpui::Point<Pixels>,
9539 window: &mut Window,
9540 cx: &mut Context<Self>,
9541 ) {
9542 if !cx.has_flag::<DebuggerFeatureFlag>() {
9543 return;
9544 }
9545 let source = self
9546 .buffer
9547 .read(cx)
9548 .snapshot(cx)
9549 .anchor_before(Point::new(display_row.0, 0u32));
9550
9551 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9552
9553 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9554 self,
9555 source,
9556 clicked_point,
9557 context_menu,
9558 window,
9559 cx,
9560 );
9561 }
9562
9563 fn add_edit_breakpoint_block(
9564 &mut self,
9565 anchor: Anchor,
9566 breakpoint: &Breakpoint,
9567 edit_action: BreakpointPromptEditAction,
9568 window: &mut Window,
9569 cx: &mut Context<Self>,
9570 ) {
9571 let weak_editor = cx.weak_entity();
9572 let bp_prompt = cx.new(|cx| {
9573 BreakpointPromptEditor::new(
9574 weak_editor,
9575 anchor,
9576 breakpoint.clone(),
9577 edit_action,
9578 window,
9579 cx,
9580 )
9581 });
9582
9583 let height = bp_prompt.update(cx, |this, cx| {
9584 this.prompt
9585 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9586 });
9587 let cloned_prompt = bp_prompt.clone();
9588 let blocks = vec![BlockProperties {
9589 style: BlockStyle::Sticky,
9590 placement: BlockPlacement::Above(anchor),
9591 height: Some(height),
9592 render: Arc::new(move |cx| {
9593 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9594 cloned_prompt.clone().into_any_element()
9595 }),
9596 priority: 0,
9597 render_in_minimap: true,
9598 }];
9599
9600 let focus_handle = bp_prompt.focus_handle(cx);
9601 window.focus(&focus_handle);
9602
9603 let block_ids = self.insert_blocks(blocks, None, cx);
9604 bp_prompt.update(cx, |prompt, _| {
9605 prompt.add_block_ids(block_ids);
9606 });
9607 }
9608
9609 pub(crate) fn breakpoint_at_row(
9610 &self,
9611 row: u32,
9612 window: &mut Window,
9613 cx: &mut Context<Self>,
9614 ) -> Option<(Anchor, Breakpoint)> {
9615 let snapshot = self.snapshot(window, cx);
9616 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9617
9618 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9619 }
9620
9621 pub(crate) fn breakpoint_at_anchor(
9622 &self,
9623 breakpoint_position: Anchor,
9624 snapshot: &EditorSnapshot,
9625 cx: &mut Context<Self>,
9626 ) -> Option<(Anchor, Breakpoint)> {
9627 let project = self.project.clone()?;
9628
9629 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9630 snapshot
9631 .buffer_snapshot
9632 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9633 })?;
9634
9635 let enclosing_excerpt = breakpoint_position.excerpt_id;
9636 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9637 let buffer_snapshot = buffer.read(cx).snapshot();
9638
9639 let row = buffer_snapshot
9640 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9641 .row;
9642
9643 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9644 let anchor_end = snapshot
9645 .buffer_snapshot
9646 .anchor_after(Point::new(row, line_len));
9647
9648 let bp = self
9649 .breakpoint_store
9650 .as_ref()?
9651 .read_with(cx, |breakpoint_store, cx| {
9652 breakpoint_store
9653 .breakpoints(
9654 &buffer,
9655 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9656 &buffer_snapshot,
9657 cx,
9658 )
9659 .next()
9660 .and_then(|(bp, _)| {
9661 let breakpoint_row = buffer_snapshot
9662 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9663 .row;
9664
9665 if breakpoint_row == row {
9666 snapshot
9667 .buffer_snapshot
9668 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9669 .map(|position| (position, bp.bp.clone()))
9670 } else {
9671 None
9672 }
9673 })
9674 });
9675 bp
9676 }
9677
9678 pub fn edit_log_breakpoint(
9679 &mut self,
9680 _: &EditLogBreakpoint,
9681 window: &mut Window,
9682 cx: &mut Context<Self>,
9683 ) {
9684 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9685 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9686 message: None,
9687 state: BreakpointState::Enabled,
9688 condition: None,
9689 hit_condition: None,
9690 });
9691
9692 self.add_edit_breakpoint_block(
9693 anchor,
9694 &breakpoint,
9695 BreakpointPromptEditAction::Log,
9696 window,
9697 cx,
9698 );
9699 }
9700 }
9701
9702 fn breakpoints_at_cursors(
9703 &self,
9704 window: &mut Window,
9705 cx: &mut Context<Self>,
9706 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9707 let snapshot = self.snapshot(window, cx);
9708 let cursors = self
9709 .selections
9710 .disjoint_anchors()
9711 .into_iter()
9712 .map(|selection| {
9713 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9714
9715 let breakpoint_position = self
9716 .breakpoint_at_row(cursor_position.row, window, cx)
9717 .map(|bp| bp.0)
9718 .unwrap_or_else(|| {
9719 snapshot
9720 .display_snapshot
9721 .buffer_snapshot
9722 .anchor_after(Point::new(cursor_position.row, 0))
9723 });
9724
9725 let breakpoint = self
9726 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9727 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9728
9729 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9730 })
9731 // 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.
9732 .collect::<HashMap<Anchor, _>>();
9733
9734 cursors.into_iter().collect()
9735 }
9736
9737 pub fn enable_breakpoint(
9738 &mut self,
9739 _: &crate::actions::EnableBreakpoint,
9740 window: &mut Window,
9741 cx: &mut Context<Self>,
9742 ) {
9743 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9744 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9745 continue;
9746 };
9747 self.edit_breakpoint_at_anchor(
9748 anchor,
9749 breakpoint,
9750 BreakpointEditAction::InvertState,
9751 cx,
9752 );
9753 }
9754 }
9755
9756 pub fn disable_breakpoint(
9757 &mut self,
9758 _: &crate::actions::DisableBreakpoint,
9759 window: &mut Window,
9760 cx: &mut Context<Self>,
9761 ) {
9762 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9763 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9764 continue;
9765 };
9766 self.edit_breakpoint_at_anchor(
9767 anchor,
9768 breakpoint,
9769 BreakpointEditAction::InvertState,
9770 cx,
9771 );
9772 }
9773 }
9774
9775 pub fn toggle_breakpoint(
9776 &mut self,
9777 _: &crate::actions::ToggleBreakpoint,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) {
9781 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9782 if let Some(breakpoint) = breakpoint {
9783 self.edit_breakpoint_at_anchor(
9784 anchor,
9785 breakpoint,
9786 BreakpointEditAction::Toggle,
9787 cx,
9788 );
9789 } else {
9790 self.edit_breakpoint_at_anchor(
9791 anchor,
9792 Breakpoint::new_standard(),
9793 BreakpointEditAction::Toggle,
9794 cx,
9795 );
9796 }
9797 }
9798 }
9799
9800 pub fn edit_breakpoint_at_anchor(
9801 &mut self,
9802 breakpoint_position: Anchor,
9803 breakpoint: Breakpoint,
9804 edit_action: BreakpointEditAction,
9805 cx: &mut Context<Self>,
9806 ) {
9807 let Some(breakpoint_store) = &self.breakpoint_store else {
9808 return;
9809 };
9810
9811 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9812 if breakpoint_position == Anchor::min() {
9813 self.buffer()
9814 .read(cx)
9815 .excerpt_buffer_ids()
9816 .into_iter()
9817 .next()
9818 } else {
9819 None
9820 }
9821 }) else {
9822 return;
9823 };
9824
9825 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9826 return;
9827 };
9828
9829 breakpoint_store.update(cx, |breakpoint_store, cx| {
9830 breakpoint_store.toggle_breakpoint(
9831 buffer,
9832 BreakpointWithPosition {
9833 position: breakpoint_position.text_anchor,
9834 bp: breakpoint,
9835 },
9836 edit_action,
9837 cx,
9838 );
9839 });
9840
9841 cx.notify();
9842 }
9843
9844 #[cfg(any(test, feature = "test-support"))]
9845 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9846 self.breakpoint_store.clone()
9847 }
9848
9849 pub fn prepare_restore_change(
9850 &self,
9851 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9852 hunk: &MultiBufferDiffHunk,
9853 cx: &mut App,
9854 ) -> Option<()> {
9855 if hunk.is_created_file() {
9856 return None;
9857 }
9858 let buffer = self.buffer.read(cx);
9859 let diff = buffer.diff_for(hunk.buffer_id)?;
9860 let buffer = buffer.buffer(hunk.buffer_id)?;
9861 let buffer = buffer.read(cx);
9862 let original_text = diff
9863 .read(cx)
9864 .base_text()
9865 .as_rope()
9866 .slice(hunk.diff_base_byte_range.clone());
9867 let buffer_snapshot = buffer.snapshot();
9868 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9869 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9870 probe
9871 .0
9872 .start
9873 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9874 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9875 }) {
9876 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9877 Some(())
9878 } else {
9879 None
9880 }
9881 }
9882
9883 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9884 self.manipulate_lines(window, cx, |lines| lines.reverse())
9885 }
9886
9887 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9888 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9889 }
9890
9891 fn manipulate_lines<Fn>(
9892 &mut self,
9893 window: &mut Window,
9894 cx: &mut Context<Self>,
9895 mut callback: Fn,
9896 ) where
9897 Fn: FnMut(&mut Vec<&str>),
9898 {
9899 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9900
9901 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9902 let buffer = self.buffer.read(cx).snapshot(cx);
9903
9904 let mut edits = Vec::new();
9905
9906 let selections = self.selections.all::<Point>(cx);
9907 let mut selections = selections.iter().peekable();
9908 let mut contiguous_row_selections = Vec::new();
9909 let mut new_selections = Vec::new();
9910 let mut added_lines = 0;
9911 let mut removed_lines = 0;
9912
9913 while let Some(selection) = selections.next() {
9914 let (start_row, end_row) = consume_contiguous_rows(
9915 &mut contiguous_row_selections,
9916 selection,
9917 &display_map,
9918 &mut selections,
9919 );
9920
9921 let start_point = Point::new(start_row.0, 0);
9922 let end_point = Point::new(
9923 end_row.previous_row().0,
9924 buffer.line_len(end_row.previous_row()),
9925 );
9926 let text = buffer
9927 .text_for_range(start_point..end_point)
9928 .collect::<String>();
9929
9930 let mut lines = text.split('\n').collect_vec();
9931
9932 let lines_before = lines.len();
9933 callback(&mut lines);
9934 let lines_after = lines.len();
9935
9936 edits.push((start_point..end_point, lines.join("\n")));
9937
9938 // Selections must change based on added and removed line count
9939 let start_row =
9940 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9941 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9942 new_selections.push(Selection {
9943 id: selection.id,
9944 start: start_row,
9945 end: end_row,
9946 goal: SelectionGoal::None,
9947 reversed: selection.reversed,
9948 });
9949
9950 if lines_after > lines_before {
9951 added_lines += lines_after - lines_before;
9952 } else if lines_before > lines_after {
9953 removed_lines += lines_before - lines_after;
9954 }
9955 }
9956
9957 self.transact(window, cx, |this, window, cx| {
9958 let buffer = this.buffer.update(cx, |buffer, cx| {
9959 buffer.edit(edits, None, cx);
9960 buffer.snapshot(cx)
9961 });
9962
9963 // Recalculate offsets on newly edited buffer
9964 let new_selections = new_selections
9965 .iter()
9966 .map(|s| {
9967 let start_point = Point::new(s.start.0, 0);
9968 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9969 Selection {
9970 id: s.id,
9971 start: buffer.point_to_offset(start_point),
9972 end: buffer.point_to_offset(end_point),
9973 goal: s.goal,
9974 reversed: s.reversed,
9975 }
9976 })
9977 .collect();
9978
9979 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9980 s.select(new_selections);
9981 });
9982
9983 this.request_autoscroll(Autoscroll::fit(), cx);
9984 });
9985 }
9986
9987 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9988 self.manipulate_text(window, cx, |text| {
9989 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9990 if has_upper_case_characters {
9991 text.to_lowercase()
9992 } else {
9993 text.to_uppercase()
9994 }
9995 })
9996 }
9997
9998 pub fn convert_to_upper_case(
9999 &mut self,
10000 _: &ConvertToUpperCase,
10001 window: &mut Window,
10002 cx: &mut Context<Self>,
10003 ) {
10004 self.manipulate_text(window, cx, |text| text.to_uppercase())
10005 }
10006
10007 pub fn convert_to_lower_case(
10008 &mut self,
10009 _: &ConvertToLowerCase,
10010 window: &mut Window,
10011 cx: &mut Context<Self>,
10012 ) {
10013 self.manipulate_text(window, cx, |text| text.to_lowercase())
10014 }
10015
10016 pub fn convert_to_title_case(
10017 &mut self,
10018 _: &ConvertToTitleCase,
10019 window: &mut Window,
10020 cx: &mut Context<Self>,
10021 ) {
10022 self.manipulate_text(window, cx, |text| {
10023 text.split('\n')
10024 .map(|line| line.to_case(Case::Title))
10025 .join("\n")
10026 })
10027 }
10028
10029 pub fn convert_to_snake_case(
10030 &mut self,
10031 _: &ConvertToSnakeCase,
10032 window: &mut Window,
10033 cx: &mut Context<Self>,
10034 ) {
10035 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10036 }
10037
10038 pub fn convert_to_kebab_case(
10039 &mut self,
10040 _: &ConvertToKebabCase,
10041 window: &mut Window,
10042 cx: &mut Context<Self>,
10043 ) {
10044 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10045 }
10046
10047 pub fn convert_to_upper_camel_case(
10048 &mut self,
10049 _: &ConvertToUpperCamelCase,
10050 window: &mut Window,
10051 cx: &mut Context<Self>,
10052 ) {
10053 self.manipulate_text(window, cx, |text| {
10054 text.split('\n')
10055 .map(|line| line.to_case(Case::UpperCamel))
10056 .join("\n")
10057 })
10058 }
10059
10060 pub fn convert_to_lower_camel_case(
10061 &mut self,
10062 _: &ConvertToLowerCamelCase,
10063 window: &mut Window,
10064 cx: &mut Context<Self>,
10065 ) {
10066 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10067 }
10068
10069 pub fn convert_to_opposite_case(
10070 &mut self,
10071 _: &ConvertToOppositeCase,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 self.manipulate_text(window, cx, |text| {
10076 text.chars()
10077 .fold(String::with_capacity(text.len()), |mut t, c| {
10078 if c.is_uppercase() {
10079 t.extend(c.to_lowercase());
10080 } else {
10081 t.extend(c.to_uppercase());
10082 }
10083 t
10084 })
10085 })
10086 }
10087
10088 pub fn convert_to_rot13(
10089 &mut self,
10090 _: &ConvertToRot13,
10091 window: &mut Window,
10092 cx: &mut Context<Self>,
10093 ) {
10094 self.manipulate_text(window, cx, |text| {
10095 text.chars()
10096 .map(|c| match c {
10097 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10098 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10099 _ => c,
10100 })
10101 .collect()
10102 })
10103 }
10104
10105 pub fn convert_to_rot47(
10106 &mut self,
10107 _: &ConvertToRot47,
10108 window: &mut Window,
10109 cx: &mut Context<Self>,
10110 ) {
10111 self.manipulate_text(window, cx, |text| {
10112 text.chars()
10113 .map(|c| {
10114 let code_point = c as u32;
10115 if code_point >= 33 && code_point <= 126 {
10116 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10117 }
10118 c
10119 })
10120 .collect()
10121 })
10122 }
10123
10124 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10125 where
10126 Fn: FnMut(&str) -> String,
10127 {
10128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10129 let buffer = self.buffer.read(cx).snapshot(cx);
10130
10131 let mut new_selections = Vec::new();
10132 let mut edits = Vec::new();
10133 let mut selection_adjustment = 0i32;
10134
10135 for selection in self.selections.all::<usize>(cx) {
10136 let selection_is_empty = selection.is_empty();
10137
10138 let (start, end) = if selection_is_empty {
10139 let word_range = movement::surrounding_word(
10140 &display_map,
10141 selection.start.to_display_point(&display_map),
10142 );
10143 let start = word_range.start.to_offset(&display_map, Bias::Left);
10144 let end = word_range.end.to_offset(&display_map, Bias::Left);
10145 (start, end)
10146 } else {
10147 (selection.start, selection.end)
10148 };
10149
10150 let text = buffer.text_for_range(start..end).collect::<String>();
10151 let old_length = text.len() as i32;
10152 let text = callback(&text);
10153
10154 new_selections.push(Selection {
10155 start: (start as i32 - selection_adjustment) as usize,
10156 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10157 goal: SelectionGoal::None,
10158 ..selection
10159 });
10160
10161 selection_adjustment += old_length - text.len() as i32;
10162
10163 edits.push((start..end, text));
10164 }
10165
10166 self.transact(window, cx, |this, window, cx| {
10167 this.buffer.update(cx, |buffer, cx| {
10168 buffer.edit(edits, None, cx);
10169 });
10170
10171 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10172 s.select(new_selections);
10173 });
10174
10175 this.request_autoscroll(Autoscroll::fit(), cx);
10176 });
10177 }
10178
10179 pub fn duplicate(
10180 &mut self,
10181 upwards: bool,
10182 whole_lines: bool,
10183 window: &mut Window,
10184 cx: &mut Context<Self>,
10185 ) {
10186 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10187
10188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10189 let buffer = &display_map.buffer_snapshot;
10190 let selections = self.selections.all::<Point>(cx);
10191
10192 let mut edits = Vec::new();
10193 let mut selections_iter = selections.iter().peekable();
10194 while let Some(selection) = selections_iter.next() {
10195 let mut rows = selection.spanned_rows(false, &display_map);
10196 // duplicate line-wise
10197 if whole_lines || selection.start == selection.end {
10198 // Avoid duplicating the same lines twice.
10199 while let Some(next_selection) = selections_iter.peek() {
10200 let next_rows = next_selection.spanned_rows(false, &display_map);
10201 if next_rows.start < rows.end {
10202 rows.end = next_rows.end;
10203 selections_iter.next().unwrap();
10204 } else {
10205 break;
10206 }
10207 }
10208
10209 // Copy the text from the selected row region and splice it either at the start
10210 // or end of the region.
10211 let start = Point::new(rows.start.0, 0);
10212 let end = Point::new(
10213 rows.end.previous_row().0,
10214 buffer.line_len(rows.end.previous_row()),
10215 );
10216 let text = buffer
10217 .text_for_range(start..end)
10218 .chain(Some("\n"))
10219 .collect::<String>();
10220 let insert_location = if upwards {
10221 Point::new(rows.end.0, 0)
10222 } else {
10223 start
10224 };
10225 edits.push((insert_location..insert_location, text));
10226 } else {
10227 // duplicate character-wise
10228 let start = selection.start;
10229 let end = selection.end;
10230 let text = buffer.text_for_range(start..end).collect::<String>();
10231 edits.push((selection.end..selection.end, text));
10232 }
10233 }
10234
10235 self.transact(window, cx, |this, _, cx| {
10236 this.buffer.update(cx, |buffer, cx| {
10237 buffer.edit(edits, None, cx);
10238 });
10239
10240 this.request_autoscroll(Autoscroll::fit(), cx);
10241 });
10242 }
10243
10244 pub fn duplicate_line_up(
10245 &mut self,
10246 _: &DuplicateLineUp,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 self.duplicate(true, true, window, cx);
10251 }
10252
10253 pub fn duplicate_line_down(
10254 &mut self,
10255 _: &DuplicateLineDown,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 self.duplicate(false, true, window, cx);
10260 }
10261
10262 pub fn duplicate_selection(
10263 &mut self,
10264 _: &DuplicateSelection,
10265 window: &mut Window,
10266 cx: &mut Context<Self>,
10267 ) {
10268 self.duplicate(false, false, window, cx);
10269 }
10270
10271 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10272 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10273
10274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10275 let buffer = self.buffer.read(cx).snapshot(cx);
10276
10277 let mut edits = Vec::new();
10278 let mut unfold_ranges = Vec::new();
10279 let mut refold_creases = Vec::new();
10280
10281 let selections = self.selections.all::<Point>(cx);
10282 let mut selections = selections.iter().peekable();
10283 let mut contiguous_row_selections = Vec::new();
10284 let mut new_selections = Vec::new();
10285
10286 while let Some(selection) = selections.next() {
10287 // Find all the selections that span a contiguous row range
10288 let (start_row, end_row) = consume_contiguous_rows(
10289 &mut contiguous_row_selections,
10290 selection,
10291 &display_map,
10292 &mut selections,
10293 );
10294
10295 // Move the text spanned by the row range to be before the line preceding the row range
10296 if start_row.0 > 0 {
10297 let range_to_move = Point::new(
10298 start_row.previous_row().0,
10299 buffer.line_len(start_row.previous_row()),
10300 )
10301 ..Point::new(
10302 end_row.previous_row().0,
10303 buffer.line_len(end_row.previous_row()),
10304 );
10305 let insertion_point = display_map
10306 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10307 .0;
10308
10309 // Don't move lines across excerpts
10310 if buffer
10311 .excerpt_containing(insertion_point..range_to_move.end)
10312 .is_some()
10313 {
10314 let text = buffer
10315 .text_for_range(range_to_move.clone())
10316 .flat_map(|s| s.chars())
10317 .skip(1)
10318 .chain(['\n'])
10319 .collect::<String>();
10320
10321 edits.push((
10322 buffer.anchor_after(range_to_move.start)
10323 ..buffer.anchor_before(range_to_move.end),
10324 String::new(),
10325 ));
10326 let insertion_anchor = buffer.anchor_after(insertion_point);
10327 edits.push((insertion_anchor..insertion_anchor, text));
10328
10329 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10330
10331 // Move selections up
10332 new_selections.extend(contiguous_row_selections.drain(..).map(
10333 |mut selection| {
10334 selection.start.row -= row_delta;
10335 selection.end.row -= row_delta;
10336 selection
10337 },
10338 ));
10339
10340 // Move folds up
10341 unfold_ranges.push(range_to_move.clone());
10342 for fold in display_map.folds_in_range(
10343 buffer.anchor_before(range_to_move.start)
10344 ..buffer.anchor_after(range_to_move.end),
10345 ) {
10346 let mut start = fold.range.start.to_point(&buffer);
10347 let mut end = fold.range.end.to_point(&buffer);
10348 start.row -= row_delta;
10349 end.row -= row_delta;
10350 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10351 }
10352 }
10353 }
10354
10355 // If we didn't move line(s), preserve the existing selections
10356 new_selections.append(&mut contiguous_row_selections);
10357 }
10358
10359 self.transact(window, cx, |this, window, cx| {
10360 this.unfold_ranges(&unfold_ranges, true, true, cx);
10361 this.buffer.update(cx, |buffer, cx| {
10362 for (range, text) in edits {
10363 buffer.edit([(range, text)], None, cx);
10364 }
10365 });
10366 this.fold_creases(refold_creases, true, window, cx);
10367 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10368 s.select(new_selections);
10369 })
10370 });
10371 }
10372
10373 pub fn move_line_down(
10374 &mut self,
10375 _: &MoveLineDown,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10380
10381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10382 let buffer = self.buffer.read(cx).snapshot(cx);
10383
10384 let mut edits = Vec::new();
10385 let mut unfold_ranges = Vec::new();
10386 let mut refold_creases = Vec::new();
10387
10388 let selections = self.selections.all::<Point>(cx);
10389 let mut selections = selections.iter().peekable();
10390 let mut contiguous_row_selections = Vec::new();
10391 let mut new_selections = Vec::new();
10392
10393 while let Some(selection) = selections.next() {
10394 // Find all the selections that span a contiguous row range
10395 let (start_row, end_row) = consume_contiguous_rows(
10396 &mut contiguous_row_selections,
10397 selection,
10398 &display_map,
10399 &mut selections,
10400 );
10401
10402 // Move the text spanned by the row range to be after the last line of the row range
10403 if end_row.0 <= buffer.max_point().row {
10404 let range_to_move =
10405 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10406 let insertion_point = display_map
10407 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10408 .0;
10409
10410 // Don't move lines across excerpt boundaries
10411 if buffer
10412 .excerpt_containing(range_to_move.start..insertion_point)
10413 .is_some()
10414 {
10415 let mut text = String::from("\n");
10416 text.extend(buffer.text_for_range(range_to_move.clone()));
10417 text.pop(); // Drop trailing newline
10418 edits.push((
10419 buffer.anchor_after(range_to_move.start)
10420 ..buffer.anchor_before(range_to_move.end),
10421 String::new(),
10422 ));
10423 let insertion_anchor = buffer.anchor_after(insertion_point);
10424 edits.push((insertion_anchor..insertion_anchor, text));
10425
10426 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10427
10428 // Move selections down
10429 new_selections.extend(contiguous_row_selections.drain(..).map(
10430 |mut selection| {
10431 selection.start.row += row_delta;
10432 selection.end.row += row_delta;
10433 selection
10434 },
10435 ));
10436
10437 // Move folds down
10438 unfold_ranges.push(range_to_move.clone());
10439 for fold in display_map.folds_in_range(
10440 buffer.anchor_before(range_to_move.start)
10441 ..buffer.anchor_after(range_to_move.end),
10442 ) {
10443 let mut start = fold.range.start.to_point(&buffer);
10444 let mut end = fold.range.end.to_point(&buffer);
10445 start.row += row_delta;
10446 end.row += row_delta;
10447 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10448 }
10449 }
10450 }
10451
10452 // If we didn't move line(s), preserve the existing selections
10453 new_selections.append(&mut contiguous_row_selections);
10454 }
10455
10456 self.transact(window, cx, |this, window, cx| {
10457 this.unfold_ranges(&unfold_ranges, true, true, cx);
10458 this.buffer.update(cx, |buffer, cx| {
10459 for (range, text) in edits {
10460 buffer.edit([(range, text)], None, cx);
10461 }
10462 });
10463 this.fold_creases(refold_creases, true, window, cx);
10464 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10465 s.select(new_selections)
10466 });
10467 });
10468 }
10469
10470 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10471 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10472 let text_layout_details = &self.text_layout_details(window);
10473 self.transact(window, cx, |this, window, cx| {
10474 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10475 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10476 s.move_with(|display_map, selection| {
10477 if !selection.is_empty() {
10478 return;
10479 }
10480
10481 let mut head = selection.head();
10482 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10483 if head.column() == display_map.line_len(head.row()) {
10484 transpose_offset = display_map
10485 .buffer_snapshot
10486 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10487 }
10488
10489 if transpose_offset == 0 {
10490 return;
10491 }
10492
10493 *head.column_mut() += 1;
10494 head = display_map.clip_point(head, Bias::Right);
10495 let goal = SelectionGoal::HorizontalPosition(
10496 display_map
10497 .x_for_display_point(head, text_layout_details)
10498 .into(),
10499 );
10500 selection.collapse_to(head, goal);
10501
10502 let transpose_start = display_map
10503 .buffer_snapshot
10504 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10505 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10506 let transpose_end = display_map
10507 .buffer_snapshot
10508 .clip_offset(transpose_offset + 1, Bias::Right);
10509 if let Some(ch) =
10510 display_map.buffer_snapshot.chars_at(transpose_start).next()
10511 {
10512 edits.push((transpose_start..transpose_offset, String::new()));
10513 edits.push((transpose_end..transpose_end, ch.to_string()));
10514 }
10515 }
10516 });
10517 edits
10518 });
10519 this.buffer
10520 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10521 let selections = this.selections.all::<usize>(cx);
10522 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10523 s.select(selections);
10524 });
10525 });
10526 }
10527
10528 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10529 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10530 self.rewrap_impl(RewrapOptions::default(), cx)
10531 }
10532
10533 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10534 let buffer = self.buffer.read(cx).snapshot(cx);
10535 let selections = self.selections.all::<Point>(cx);
10536 let mut selections = selections.iter().peekable();
10537
10538 let mut edits = Vec::new();
10539 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10540
10541 while let Some(selection) = selections.next() {
10542 let mut start_row = selection.start.row;
10543 let mut end_row = selection.end.row;
10544
10545 // Skip selections that overlap with a range that has already been rewrapped.
10546 let selection_range = start_row..end_row;
10547 if rewrapped_row_ranges
10548 .iter()
10549 .any(|range| range.overlaps(&selection_range))
10550 {
10551 continue;
10552 }
10553
10554 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10555
10556 // Since not all lines in the selection may be at the same indent
10557 // level, choose the indent size that is the most common between all
10558 // of the lines.
10559 //
10560 // If there is a tie, we use the deepest indent.
10561 let (indent_size, indent_end) = {
10562 let mut indent_size_occurrences = HashMap::default();
10563 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10564
10565 for row in start_row..=end_row {
10566 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10567 rows_by_indent_size.entry(indent).or_default().push(row);
10568 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10569 }
10570
10571 let indent_size = indent_size_occurrences
10572 .into_iter()
10573 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10574 .map(|(indent, _)| indent)
10575 .unwrap_or_default();
10576 let row = rows_by_indent_size[&indent_size][0];
10577 let indent_end = Point::new(row, indent_size.len);
10578
10579 (indent_size, indent_end)
10580 };
10581
10582 let mut line_prefix = indent_size.chars().collect::<String>();
10583
10584 let mut inside_comment = false;
10585 if let Some(comment_prefix) =
10586 buffer
10587 .language_scope_at(selection.head())
10588 .and_then(|language| {
10589 language
10590 .line_comment_prefixes()
10591 .iter()
10592 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10593 .cloned()
10594 })
10595 {
10596 line_prefix.push_str(&comment_prefix);
10597 inside_comment = true;
10598 }
10599
10600 let language_settings = buffer.language_settings_at(selection.head(), cx);
10601 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10602 RewrapBehavior::InComments => inside_comment,
10603 RewrapBehavior::InSelections => !selection.is_empty(),
10604 RewrapBehavior::Anywhere => true,
10605 };
10606
10607 let should_rewrap = options.override_language_settings
10608 || allow_rewrap_based_on_language
10609 || self.hard_wrap.is_some();
10610 if !should_rewrap {
10611 continue;
10612 }
10613
10614 if selection.is_empty() {
10615 'expand_upwards: while start_row > 0 {
10616 let prev_row = start_row - 1;
10617 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10618 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10619 {
10620 start_row = prev_row;
10621 } else {
10622 break 'expand_upwards;
10623 }
10624 }
10625
10626 'expand_downwards: while end_row < buffer.max_point().row {
10627 let next_row = end_row + 1;
10628 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10629 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10630 {
10631 end_row = next_row;
10632 } else {
10633 break 'expand_downwards;
10634 }
10635 }
10636 }
10637
10638 let start = Point::new(start_row, 0);
10639 let start_offset = start.to_offset(&buffer);
10640 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10641 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10642 let Some(lines_without_prefixes) = selection_text
10643 .lines()
10644 .map(|line| {
10645 line.strip_prefix(&line_prefix)
10646 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10647 .with_context(|| {
10648 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10649 })
10650 })
10651 .collect::<Result<Vec<_>, _>>()
10652 .log_err()
10653 else {
10654 continue;
10655 };
10656
10657 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10658 buffer
10659 .language_settings_at(Point::new(start_row, 0), cx)
10660 .preferred_line_length as usize
10661 });
10662 let wrapped_text = wrap_with_prefix(
10663 line_prefix,
10664 lines_without_prefixes.join("\n"),
10665 wrap_column,
10666 tab_size,
10667 options.preserve_existing_whitespace,
10668 );
10669
10670 // TODO: should always use char-based diff while still supporting cursor behavior that
10671 // matches vim.
10672 let mut diff_options = DiffOptions::default();
10673 if options.override_language_settings {
10674 diff_options.max_word_diff_len = 0;
10675 diff_options.max_word_diff_line_count = 0;
10676 } else {
10677 diff_options.max_word_diff_len = usize::MAX;
10678 diff_options.max_word_diff_line_count = usize::MAX;
10679 }
10680
10681 for (old_range, new_text) in
10682 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10683 {
10684 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10685 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10686 edits.push((edit_start..edit_end, new_text));
10687 }
10688
10689 rewrapped_row_ranges.push(start_row..=end_row);
10690 }
10691
10692 self.buffer
10693 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10694 }
10695
10696 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10697 let mut text = String::new();
10698 let buffer = self.buffer.read(cx).snapshot(cx);
10699 let mut selections = self.selections.all::<Point>(cx);
10700 let mut clipboard_selections = Vec::with_capacity(selections.len());
10701 {
10702 let max_point = buffer.max_point();
10703 let mut is_first = true;
10704 for selection in &mut selections {
10705 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10706 if is_entire_line {
10707 selection.start = Point::new(selection.start.row, 0);
10708 if !selection.is_empty() && selection.end.column == 0 {
10709 selection.end = cmp::min(max_point, selection.end);
10710 } else {
10711 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10712 }
10713 selection.goal = SelectionGoal::None;
10714 }
10715 if is_first {
10716 is_first = false;
10717 } else {
10718 text += "\n";
10719 }
10720 let mut len = 0;
10721 for chunk in buffer.text_for_range(selection.start..selection.end) {
10722 text.push_str(chunk);
10723 len += chunk.len();
10724 }
10725 clipboard_selections.push(ClipboardSelection {
10726 len,
10727 is_entire_line,
10728 first_line_indent: buffer
10729 .indent_size_for_line(MultiBufferRow(selection.start.row))
10730 .len,
10731 });
10732 }
10733 }
10734
10735 self.transact(window, cx, |this, window, cx| {
10736 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10737 s.select(selections);
10738 });
10739 this.insert("", window, cx);
10740 });
10741 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10742 }
10743
10744 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10745 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10746 let item = self.cut_common(window, cx);
10747 cx.write_to_clipboard(item);
10748 }
10749
10750 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10751 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10752 self.change_selections(None, window, cx, |s| {
10753 s.move_with(|snapshot, sel| {
10754 if sel.is_empty() {
10755 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10756 }
10757 });
10758 });
10759 let item = self.cut_common(window, cx);
10760 cx.set_global(KillRing(item))
10761 }
10762
10763 pub fn kill_ring_yank(
10764 &mut self,
10765 _: &KillRingYank,
10766 window: &mut Window,
10767 cx: &mut Context<Self>,
10768 ) {
10769 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10770 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10771 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10772 (kill_ring.text().to_string(), kill_ring.metadata_json())
10773 } else {
10774 return;
10775 }
10776 } else {
10777 return;
10778 };
10779 self.do_paste(&text, metadata, false, window, cx);
10780 }
10781
10782 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10783 self.do_copy(true, cx);
10784 }
10785
10786 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10787 self.do_copy(false, cx);
10788 }
10789
10790 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10791 let selections = self.selections.all::<Point>(cx);
10792 let buffer = self.buffer.read(cx).read(cx);
10793 let mut text = String::new();
10794
10795 let mut clipboard_selections = Vec::with_capacity(selections.len());
10796 {
10797 let max_point = buffer.max_point();
10798 let mut is_first = true;
10799 for selection in &selections {
10800 let mut start = selection.start;
10801 let mut end = selection.end;
10802 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10803 if is_entire_line {
10804 start = Point::new(start.row, 0);
10805 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10806 }
10807
10808 let mut trimmed_selections = Vec::new();
10809 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10810 let row = MultiBufferRow(start.row);
10811 let first_indent = buffer.indent_size_for_line(row);
10812 if first_indent.len == 0 || start.column > first_indent.len {
10813 trimmed_selections.push(start..end);
10814 } else {
10815 trimmed_selections.push(
10816 Point::new(row.0, first_indent.len)
10817 ..Point::new(row.0, buffer.line_len(row)),
10818 );
10819 for row in start.row + 1..=end.row {
10820 let mut line_len = buffer.line_len(MultiBufferRow(row));
10821 if row == end.row {
10822 line_len = end.column;
10823 }
10824 if line_len == 0 {
10825 trimmed_selections
10826 .push(Point::new(row, 0)..Point::new(row, line_len));
10827 continue;
10828 }
10829 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10830 if row_indent_size.len >= first_indent.len {
10831 trimmed_selections.push(
10832 Point::new(row, first_indent.len)..Point::new(row, line_len),
10833 );
10834 } else {
10835 trimmed_selections.clear();
10836 trimmed_selections.push(start..end);
10837 break;
10838 }
10839 }
10840 }
10841 } else {
10842 trimmed_selections.push(start..end);
10843 }
10844
10845 for trimmed_range in trimmed_selections {
10846 if is_first {
10847 is_first = false;
10848 } else {
10849 text += "\n";
10850 }
10851 let mut len = 0;
10852 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10853 text.push_str(chunk);
10854 len += chunk.len();
10855 }
10856 clipboard_selections.push(ClipboardSelection {
10857 len,
10858 is_entire_line,
10859 first_line_indent: buffer
10860 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10861 .len,
10862 });
10863 }
10864 }
10865 }
10866
10867 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10868 text,
10869 clipboard_selections,
10870 ));
10871 }
10872
10873 pub fn do_paste(
10874 &mut self,
10875 text: &String,
10876 clipboard_selections: Option<Vec<ClipboardSelection>>,
10877 handle_entire_lines: bool,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 if self.read_only(cx) {
10882 return;
10883 }
10884
10885 let clipboard_text = Cow::Borrowed(text);
10886
10887 self.transact(window, cx, |this, window, cx| {
10888 if let Some(mut clipboard_selections) = clipboard_selections {
10889 let old_selections = this.selections.all::<usize>(cx);
10890 let all_selections_were_entire_line =
10891 clipboard_selections.iter().all(|s| s.is_entire_line);
10892 let first_selection_indent_column =
10893 clipboard_selections.first().map(|s| s.first_line_indent);
10894 if clipboard_selections.len() != old_selections.len() {
10895 clipboard_selections.drain(..);
10896 }
10897 let cursor_offset = this.selections.last::<usize>(cx).head();
10898 let mut auto_indent_on_paste = true;
10899
10900 this.buffer.update(cx, |buffer, cx| {
10901 let snapshot = buffer.read(cx);
10902 auto_indent_on_paste = snapshot
10903 .language_settings_at(cursor_offset, cx)
10904 .auto_indent_on_paste;
10905
10906 let mut start_offset = 0;
10907 let mut edits = Vec::new();
10908 let mut original_indent_columns = Vec::new();
10909 for (ix, selection) in old_selections.iter().enumerate() {
10910 let to_insert;
10911 let entire_line;
10912 let original_indent_column;
10913 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10914 let end_offset = start_offset + clipboard_selection.len;
10915 to_insert = &clipboard_text[start_offset..end_offset];
10916 entire_line = clipboard_selection.is_entire_line;
10917 start_offset = end_offset + 1;
10918 original_indent_column = Some(clipboard_selection.first_line_indent);
10919 } else {
10920 to_insert = clipboard_text.as_str();
10921 entire_line = all_selections_were_entire_line;
10922 original_indent_column = first_selection_indent_column
10923 }
10924
10925 // If the corresponding selection was empty when this slice of the
10926 // clipboard text was written, then the entire line containing the
10927 // selection was copied. If this selection is also currently empty,
10928 // then paste the line before the current line of the buffer.
10929 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10930 let column = selection.start.to_point(&snapshot).column as usize;
10931 let line_start = selection.start - column;
10932 line_start..line_start
10933 } else {
10934 selection.range()
10935 };
10936
10937 edits.push((range, to_insert));
10938 original_indent_columns.push(original_indent_column);
10939 }
10940 drop(snapshot);
10941
10942 buffer.edit(
10943 edits,
10944 if auto_indent_on_paste {
10945 Some(AutoindentMode::Block {
10946 original_indent_columns,
10947 })
10948 } else {
10949 None
10950 },
10951 cx,
10952 );
10953 });
10954
10955 let selections = this.selections.all::<usize>(cx);
10956 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10957 s.select(selections)
10958 });
10959 } else {
10960 this.insert(&clipboard_text, window, cx);
10961 }
10962 });
10963 }
10964
10965 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10966 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10967 if let Some(item) = cx.read_from_clipboard() {
10968 let entries = item.entries();
10969
10970 match entries.first() {
10971 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10972 // of all the pasted entries.
10973 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10974 .do_paste(
10975 clipboard_string.text(),
10976 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10977 true,
10978 window,
10979 cx,
10980 ),
10981 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10982 }
10983 }
10984 }
10985
10986 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10987 if self.read_only(cx) {
10988 return;
10989 }
10990
10991 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10992
10993 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10994 if let Some((selections, _)) =
10995 self.selection_history.transaction(transaction_id).cloned()
10996 {
10997 self.change_selections(None, window, cx, |s| {
10998 s.select_anchors(selections.to_vec());
10999 });
11000 } else {
11001 log::error!(
11002 "No entry in selection_history found for undo. \
11003 This may correspond to a bug where undo does not update the selection. \
11004 If this is occurring, please add details to \
11005 https://github.com/zed-industries/zed/issues/22692"
11006 );
11007 }
11008 self.request_autoscroll(Autoscroll::fit(), cx);
11009 self.unmark_text(window, cx);
11010 self.refresh_inline_completion(true, false, window, cx);
11011 cx.emit(EditorEvent::Edited { transaction_id });
11012 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11013 }
11014 }
11015
11016 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11017 if self.read_only(cx) {
11018 return;
11019 }
11020
11021 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11022
11023 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11024 if let Some((_, Some(selections))) =
11025 self.selection_history.transaction(transaction_id).cloned()
11026 {
11027 self.change_selections(None, window, cx, |s| {
11028 s.select_anchors(selections.to_vec());
11029 });
11030 } else {
11031 log::error!(
11032 "No entry in selection_history found for redo. \
11033 This may correspond to a bug where undo does not update the selection. \
11034 If this is occurring, please add details to \
11035 https://github.com/zed-industries/zed/issues/22692"
11036 );
11037 }
11038 self.request_autoscroll(Autoscroll::fit(), cx);
11039 self.unmark_text(window, cx);
11040 self.refresh_inline_completion(true, false, window, cx);
11041 cx.emit(EditorEvent::Edited { transaction_id });
11042 }
11043 }
11044
11045 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11046 self.buffer
11047 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11048 }
11049
11050 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11051 self.buffer
11052 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11053 }
11054
11055 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.move_with(|map, selection| {
11059 let cursor = if selection.is_empty() {
11060 movement::left(map, selection.start)
11061 } else {
11062 selection.start
11063 };
11064 selection.collapse_to(cursor, SelectionGoal::None);
11065 });
11066 })
11067 }
11068
11069 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11070 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11071 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11072 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11073 })
11074 }
11075
11076 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11078 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11079 s.move_with(|map, selection| {
11080 let cursor = if selection.is_empty() {
11081 movement::right(map, selection.end)
11082 } else {
11083 selection.end
11084 };
11085 selection.collapse_to(cursor, SelectionGoal::None)
11086 });
11087 })
11088 }
11089
11090 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11091 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11092 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11093 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11094 })
11095 }
11096
11097 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11098 if self.take_rename(true, window, cx).is_some() {
11099 return;
11100 }
11101
11102 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11103 cx.propagate();
11104 return;
11105 }
11106
11107 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11108
11109 let text_layout_details = &self.text_layout_details(window);
11110 let selection_count = self.selections.count();
11111 let first_selection = self.selections.first_anchor();
11112
11113 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11114 s.move_with(|map, selection| {
11115 if !selection.is_empty() {
11116 selection.goal = SelectionGoal::None;
11117 }
11118 let (cursor, goal) = movement::up(
11119 map,
11120 selection.start,
11121 selection.goal,
11122 false,
11123 text_layout_details,
11124 );
11125 selection.collapse_to(cursor, goal);
11126 });
11127 });
11128
11129 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11130 {
11131 cx.propagate();
11132 }
11133 }
11134
11135 pub fn move_up_by_lines(
11136 &mut self,
11137 action: &MoveUpByLines,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 if self.take_rename(true, window, cx).is_some() {
11142 return;
11143 }
11144
11145 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11146 cx.propagate();
11147 return;
11148 }
11149
11150 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11151
11152 let text_layout_details = &self.text_layout_details(window);
11153
11154 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11155 s.move_with(|map, selection| {
11156 if !selection.is_empty() {
11157 selection.goal = SelectionGoal::None;
11158 }
11159 let (cursor, goal) = movement::up_by_rows(
11160 map,
11161 selection.start,
11162 action.lines,
11163 selection.goal,
11164 false,
11165 text_layout_details,
11166 );
11167 selection.collapse_to(cursor, goal);
11168 });
11169 })
11170 }
11171
11172 pub fn move_down_by_lines(
11173 &mut self,
11174 action: &MoveDownByLines,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 if self.take_rename(true, window, cx).is_some() {
11179 return;
11180 }
11181
11182 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11183 cx.propagate();
11184 return;
11185 }
11186
11187 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11188
11189 let text_layout_details = &self.text_layout_details(window);
11190
11191 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11192 s.move_with(|map, selection| {
11193 if !selection.is_empty() {
11194 selection.goal = SelectionGoal::None;
11195 }
11196 let (cursor, goal) = movement::down_by_rows(
11197 map,
11198 selection.start,
11199 action.lines,
11200 selection.goal,
11201 false,
11202 text_layout_details,
11203 );
11204 selection.collapse_to(cursor, goal);
11205 });
11206 })
11207 }
11208
11209 pub fn select_down_by_lines(
11210 &mut self,
11211 action: &SelectDownByLines,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11216 let text_layout_details = &self.text_layout_details(window);
11217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11218 s.move_heads_with(|map, head, goal| {
11219 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11220 })
11221 })
11222 }
11223
11224 pub fn select_up_by_lines(
11225 &mut self,
11226 action: &SelectUpByLines,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11231 let text_layout_details = &self.text_layout_details(window);
11232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11233 s.move_heads_with(|map, head, goal| {
11234 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11235 })
11236 })
11237 }
11238
11239 pub fn select_page_up(
11240 &mut self,
11241 _: &SelectPageUp,
11242 window: &mut Window,
11243 cx: &mut Context<Self>,
11244 ) {
11245 let Some(row_count) = self.visible_row_count() else {
11246 return;
11247 };
11248
11249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11250
11251 let text_layout_details = &self.text_layout_details(window);
11252
11253 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11254 s.move_heads_with(|map, head, goal| {
11255 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11256 })
11257 })
11258 }
11259
11260 pub fn move_page_up(
11261 &mut self,
11262 action: &MovePageUp,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 ) {
11266 if self.take_rename(true, window, cx).is_some() {
11267 return;
11268 }
11269
11270 if self
11271 .context_menu
11272 .borrow_mut()
11273 .as_mut()
11274 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11275 .unwrap_or(false)
11276 {
11277 return;
11278 }
11279
11280 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11281 cx.propagate();
11282 return;
11283 }
11284
11285 let Some(row_count) = self.visible_row_count() else {
11286 return;
11287 };
11288
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11290
11291 let autoscroll = if action.center_cursor {
11292 Autoscroll::center()
11293 } else {
11294 Autoscroll::fit()
11295 };
11296
11297 let text_layout_details = &self.text_layout_details(window);
11298
11299 self.change_selections(Some(autoscroll), window, cx, |s| {
11300 s.move_with(|map, selection| {
11301 if !selection.is_empty() {
11302 selection.goal = SelectionGoal::None;
11303 }
11304 let (cursor, goal) = movement::up_by_rows(
11305 map,
11306 selection.end,
11307 row_count,
11308 selection.goal,
11309 false,
11310 text_layout_details,
11311 );
11312 selection.collapse_to(cursor, goal);
11313 });
11314 });
11315 }
11316
11317 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11318 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11319 let text_layout_details = &self.text_layout_details(window);
11320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11321 s.move_heads_with(|map, head, goal| {
11322 movement::up(map, head, goal, false, text_layout_details)
11323 })
11324 })
11325 }
11326
11327 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11328 self.take_rename(true, window, cx);
11329
11330 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11331 cx.propagate();
11332 return;
11333 }
11334
11335 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11336
11337 let text_layout_details = &self.text_layout_details(window);
11338 let selection_count = self.selections.count();
11339 let first_selection = self.selections.first_anchor();
11340
11341 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11342 s.move_with(|map, selection| {
11343 if !selection.is_empty() {
11344 selection.goal = SelectionGoal::None;
11345 }
11346 let (cursor, goal) = movement::down(
11347 map,
11348 selection.end,
11349 selection.goal,
11350 false,
11351 text_layout_details,
11352 );
11353 selection.collapse_to(cursor, goal);
11354 });
11355 });
11356
11357 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11358 {
11359 cx.propagate();
11360 }
11361 }
11362
11363 pub fn select_page_down(
11364 &mut self,
11365 _: &SelectPageDown,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 let Some(row_count) = self.visible_row_count() else {
11370 return;
11371 };
11372
11373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11374
11375 let text_layout_details = &self.text_layout_details(window);
11376
11377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11378 s.move_heads_with(|map, head, goal| {
11379 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11380 })
11381 })
11382 }
11383
11384 pub fn move_page_down(
11385 &mut self,
11386 action: &MovePageDown,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 if self.take_rename(true, window, cx).is_some() {
11391 return;
11392 }
11393
11394 if self
11395 .context_menu
11396 .borrow_mut()
11397 .as_mut()
11398 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11399 .unwrap_or(false)
11400 {
11401 return;
11402 }
11403
11404 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11405 cx.propagate();
11406 return;
11407 }
11408
11409 let Some(row_count) = self.visible_row_count() else {
11410 return;
11411 };
11412
11413 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11414
11415 let autoscroll = if action.center_cursor {
11416 Autoscroll::center()
11417 } else {
11418 Autoscroll::fit()
11419 };
11420
11421 let text_layout_details = &self.text_layout_details(window);
11422 self.change_selections(Some(autoscroll), window, cx, |s| {
11423 s.move_with(|map, selection| {
11424 if !selection.is_empty() {
11425 selection.goal = SelectionGoal::None;
11426 }
11427 let (cursor, goal) = movement::down_by_rows(
11428 map,
11429 selection.end,
11430 row_count,
11431 selection.goal,
11432 false,
11433 text_layout_details,
11434 );
11435 selection.collapse_to(cursor, goal);
11436 });
11437 });
11438 }
11439
11440 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11441 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11442 let text_layout_details = &self.text_layout_details(window);
11443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11444 s.move_heads_with(|map, head, goal| {
11445 movement::down(map, head, goal, false, text_layout_details)
11446 })
11447 });
11448 }
11449
11450 pub fn context_menu_first(
11451 &mut self,
11452 _: &ContextMenuFirst,
11453 _window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11457 context_menu.select_first(self.completion_provider.as_deref(), cx);
11458 }
11459 }
11460
11461 pub fn context_menu_prev(
11462 &mut self,
11463 _: &ContextMenuPrevious,
11464 _window: &mut Window,
11465 cx: &mut Context<Self>,
11466 ) {
11467 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11468 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11469 }
11470 }
11471
11472 pub fn context_menu_next(
11473 &mut self,
11474 _: &ContextMenuNext,
11475 _window: &mut Window,
11476 cx: &mut Context<Self>,
11477 ) {
11478 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11479 context_menu.select_next(self.completion_provider.as_deref(), cx);
11480 }
11481 }
11482
11483 pub fn context_menu_last(
11484 &mut self,
11485 _: &ContextMenuLast,
11486 _window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11490 context_menu.select_last(self.completion_provider.as_deref(), cx);
11491 }
11492 }
11493
11494 pub fn move_to_previous_word_start(
11495 &mut self,
11496 _: &MoveToPreviousWordStart,
11497 window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11501 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11502 s.move_cursors_with(|map, head, _| {
11503 (
11504 movement::previous_word_start(map, head),
11505 SelectionGoal::None,
11506 )
11507 });
11508 })
11509 }
11510
11511 pub fn move_to_previous_subword_start(
11512 &mut self,
11513 _: &MoveToPreviousSubwordStart,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11518 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11519 s.move_cursors_with(|map, head, _| {
11520 (
11521 movement::previous_subword_start(map, head),
11522 SelectionGoal::None,
11523 )
11524 });
11525 })
11526 }
11527
11528 pub fn select_to_previous_word_start(
11529 &mut self,
11530 _: &SelectToPreviousWordStart,
11531 window: &mut Window,
11532 cx: &mut Context<Self>,
11533 ) {
11534 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11536 s.move_heads_with(|map, head, _| {
11537 (
11538 movement::previous_word_start(map, head),
11539 SelectionGoal::None,
11540 )
11541 });
11542 })
11543 }
11544
11545 pub fn select_to_previous_subword_start(
11546 &mut self,
11547 _: &SelectToPreviousSubwordStart,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11552 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11553 s.move_heads_with(|map, head, _| {
11554 (
11555 movement::previous_subword_start(map, head),
11556 SelectionGoal::None,
11557 )
11558 });
11559 })
11560 }
11561
11562 pub fn delete_to_previous_word_start(
11563 &mut self,
11564 action: &DeleteToPreviousWordStart,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11569 self.transact(window, cx, |this, window, cx| {
11570 this.select_autoclose_pair(window, cx);
11571 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11572 s.move_with(|map, selection| {
11573 if selection.is_empty() {
11574 let cursor = if action.ignore_newlines {
11575 movement::previous_word_start(map, selection.head())
11576 } else {
11577 movement::previous_word_start_or_newline(map, selection.head())
11578 };
11579 selection.set_head(cursor, SelectionGoal::None);
11580 }
11581 });
11582 });
11583 this.insert("", window, cx);
11584 });
11585 }
11586
11587 pub fn delete_to_previous_subword_start(
11588 &mut self,
11589 _: &DeleteToPreviousSubwordStart,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11594 self.transact(window, cx, |this, window, cx| {
11595 this.select_autoclose_pair(window, cx);
11596 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11597 s.move_with(|map, selection| {
11598 if selection.is_empty() {
11599 let cursor = movement::previous_subword_start(map, selection.head());
11600 selection.set_head(cursor, SelectionGoal::None);
11601 }
11602 });
11603 });
11604 this.insert("", window, cx);
11605 });
11606 }
11607
11608 pub fn move_to_next_word_end(
11609 &mut self,
11610 _: &MoveToNextWordEnd,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11616 s.move_cursors_with(|map, head, _| {
11617 (movement::next_word_end(map, head), SelectionGoal::None)
11618 });
11619 })
11620 }
11621
11622 pub fn move_to_next_subword_end(
11623 &mut self,
11624 _: &MoveToNextSubwordEnd,
11625 window: &mut Window,
11626 cx: &mut Context<Self>,
11627 ) {
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_cursors_with(|map, head, _| {
11631 (movement::next_subword_end(map, head), SelectionGoal::None)
11632 });
11633 })
11634 }
11635
11636 pub fn select_to_next_word_end(
11637 &mut self,
11638 _: &SelectToNextWordEnd,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, _| {
11645 (movement::next_word_end(map, head), SelectionGoal::None)
11646 });
11647 })
11648 }
11649
11650 pub fn select_to_next_subword_end(
11651 &mut self,
11652 _: &SelectToNextSubwordEnd,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_heads_with(|map, head, _| {
11659 (movement::next_subword_end(map, head), SelectionGoal::None)
11660 });
11661 })
11662 }
11663
11664 pub fn delete_to_next_word_end(
11665 &mut self,
11666 action: &DeleteToNextWordEnd,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11671 self.transact(window, cx, |this, window, cx| {
11672 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11673 s.move_with(|map, selection| {
11674 if selection.is_empty() {
11675 let cursor = if action.ignore_newlines {
11676 movement::next_word_end(map, selection.head())
11677 } else {
11678 movement::next_word_end_or_newline(map, selection.head())
11679 };
11680 selection.set_head(cursor, SelectionGoal::None);
11681 }
11682 });
11683 });
11684 this.insert("", window, cx);
11685 });
11686 }
11687
11688 pub fn delete_to_next_subword_end(
11689 &mut self,
11690 _: &DeleteToNextSubwordEnd,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 ) {
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11695 self.transact(window, cx, |this, window, cx| {
11696 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11697 s.move_with(|map, selection| {
11698 if selection.is_empty() {
11699 let cursor = movement::next_subword_end(map, selection.head());
11700 selection.set_head(cursor, SelectionGoal::None);
11701 }
11702 });
11703 });
11704 this.insert("", window, cx);
11705 });
11706 }
11707
11708 pub fn move_to_beginning_of_line(
11709 &mut self,
11710 action: &MoveToBeginningOfLine,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11715 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11716 s.move_cursors_with(|map, head, _| {
11717 (
11718 movement::indented_line_beginning(
11719 map,
11720 head,
11721 action.stop_at_soft_wraps,
11722 action.stop_at_indent,
11723 ),
11724 SelectionGoal::None,
11725 )
11726 });
11727 })
11728 }
11729
11730 pub fn select_to_beginning_of_line(
11731 &mut self,
11732 action: &SelectToBeginningOfLine,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11737 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11738 s.move_heads_with(|map, head, _| {
11739 (
11740 movement::indented_line_beginning(
11741 map,
11742 head,
11743 action.stop_at_soft_wraps,
11744 action.stop_at_indent,
11745 ),
11746 SelectionGoal::None,
11747 )
11748 });
11749 });
11750 }
11751
11752 pub fn delete_to_beginning_of_line(
11753 &mut self,
11754 action: &DeleteToBeginningOfLine,
11755 window: &mut Window,
11756 cx: &mut Context<Self>,
11757 ) {
11758 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11759 self.transact(window, cx, |this, window, cx| {
11760 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_with(|_, selection| {
11762 selection.reversed = true;
11763 });
11764 });
11765
11766 this.select_to_beginning_of_line(
11767 &SelectToBeginningOfLine {
11768 stop_at_soft_wraps: false,
11769 stop_at_indent: action.stop_at_indent,
11770 },
11771 window,
11772 cx,
11773 );
11774 this.backspace(&Backspace, window, cx);
11775 });
11776 }
11777
11778 pub fn move_to_end_of_line(
11779 &mut self,
11780 action: &MoveToEndOfLine,
11781 window: &mut Window,
11782 cx: &mut Context<Self>,
11783 ) {
11784 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11786 s.move_cursors_with(|map, head, _| {
11787 (
11788 movement::line_end(map, head, action.stop_at_soft_wraps),
11789 SelectionGoal::None,
11790 )
11791 });
11792 })
11793 }
11794
11795 pub fn select_to_end_of_line(
11796 &mut self,
11797 action: &SelectToEndOfLine,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11803 s.move_heads_with(|map, head, _| {
11804 (
11805 movement::line_end(map, head, action.stop_at_soft_wraps),
11806 SelectionGoal::None,
11807 )
11808 });
11809 })
11810 }
11811
11812 pub fn delete_to_end_of_line(
11813 &mut self,
11814 _: &DeleteToEndOfLine,
11815 window: &mut Window,
11816 cx: &mut Context<Self>,
11817 ) {
11818 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11819 self.transact(window, cx, |this, window, cx| {
11820 this.select_to_end_of_line(
11821 &SelectToEndOfLine {
11822 stop_at_soft_wraps: false,
11823 },
11824 window,
11825 cx,
11826 );
11827 this.delete(&Delete, window, cx);
11828 });
11829 }
11830
11831 pub fn cut_to_end_of_line(
11832 &mut self,
11833 _: &CutToEndOfLine,
11834 window: &mut Window,
11835 cx: &mut Context<Self>,
11836 ) {
11837 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11838 self.transact(window, cx, |this, window, cx| {
11839 this.select_to_end_of_line(
11840 &SelectToEndOfLine {
11841 stop_at_soft_wraps: false,
11842 },
11843 window,
11844 cx,
11845 );
11846 this.cut(&Cut, window, cx);
11847 });
11848 }
11849
11850 pub fn move_to_start_of_paragraph(
11851 &mut self,
11852 _: &MoveToStartOfParagraph,
11853 window: &mut Window,
11854 cx: &mut Context<Self>,
11855 ) {
11856 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11857 cx.propagate();
11858 return;
11859 }
11860 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11862 s.move_with(|map, selection| {
11863 selection.collapse_to(
11864 movement::start_of_paragraph(map, selection.head(), 1),
11865 SelectionGoal::None,
11866 )
11867 });
11868 })
11869 }
11870
11871 pub fn move_to_end_of_paragraph(
11872 &mut self,
11873 _: &MoveToEndOfParagraph,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11878 cx.propagate();
11879 return;
11880 }
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11883 s.move_with(|map, selection| {
11884 selection.collapse_to(
11885 movement::end_of_paragraph(map, selection.head(), 1),
11886 SelectionGoal::None,
11887 )
11888 });
11889 })
11890 }
11891
11892 pub fn select_to_start_of_paragraph(
11893 &mut self,
11894 _: &SelectToStartOfParagraph,
11895 window: &mut Window,
11896 cx: &mut Context<Self>,
11897 ) {
11898 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11899 cx.propagate();
11900 return;
11901 }
11902 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11903 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11904 s.move_heads_with(|map, head, _| {
11905 (
11906 movement::start_of_paragraph(map, head, 1),
11907 SelectionGoal::None,
11908 )
11909 });
11910 })
11911 }
11912
11913 pub fn select_to_end_of_paragraph(
11914 &mut self,
11915 _: &SelectToEndOfParagraph,
11916 window: &mut Window,
11917 cx: &mut Context<Self>,
11918 ) {
11919 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11920 cx.propagate();
11921 return;
11922 }
11923 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11925 s.move_heads_with(|map, head, _| {
11926 (
11927 movement::end_of_paragraph(map, head, 1),
11928 SelectionGoal::None,
11929 )
11930 });
11931 })
11932 }
11933
11934 pub fn move_to_start_of_excerpt(
11935 &mut self,
11936 _: &MoveToStartOfExcerpt,
11937 window: &mut Window,
11938 cx: &mut Context<Self>,
11939 ) {
11940 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11941 cx.propagate();
11942 return;
11943 }
11944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11945 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11946 s.move_with(|map, selection| {
11947 selection.collapse_to(
11948 movement::start_of_excerpt(
11949 map,
11950 selection.head(),
11951 workspace::searchable::Direction::Prev,
11952 ),
11953 SelectionGoal::None,
11954 )
11955 });
11956 })
11957 }
11958
11959 pub fn move_to_start_of_next_excerpt(
11960 &mut self,
11961 _: &MoveToStartOfNextExcerpt,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11966 cx.propagate();
11967 return;
11968 }
11969
11970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11971 s.move_with(|map, selection| {
11972 selection.collapse_to(
11973 movement::start_of_excerpt(
11974 map,
11975 selection.head(),
11976 workspace::searchable::Direction::Next,
11977 ),
11978 SelectionGoal::None,
11979 )
11980 });
11981 })
11982 }
11983
11984 pub fn move_to_end_of_excerpt(
11985 &mut self,
11986 _: &MoveToEndOfExcerpt,
11987 window: &mut Window,
11988 cx: &mut Context<Self>,
11989 ) {
11990 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11991 cx.propagate();
11992 return;
11993 }
11994 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11995 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11996 s.move_with(|map, selection| {
11997 selection.collapse_to(
11998 movement::end_of_excerpt(
11999 map,
12000 selection.head(),
12001 workspace::searchable::Direction::Next,
12002 ),
12003 SelectionGoal::None,
12004 )
12005 });
12006 })
12007 }
12008
12009 pub fn move_to_end_of_previous_excerpt(
12010 &mut self,
12011 _: &MoveToEndOfPreviousExcerpt,
12012 window: &mut Window,
12013 cx: &mut Context<Self>,
12014 ) {
12015 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12016 cx.propagate();
12017 return;
12018 }
12019 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12020 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12021 s.move_with(|map, selection| {
12022 selection.collapse_to(
12023 movement::end_of_excerpt(
12024 map,
12025 selection.head(),
12026 workspace::searchable::Direction::Prev,
12027 ),
12028 SelectionGoal::None,
12029 )
12030 });
12031 })
12032 }
12033
12034 pub fn select_to_start_of_excerpt(
12035 &mut self,
12036 _: &SelectToStartOfExcerpt,
12037 window: &mut Window,
12038 cx: &mut Context<Self>,
12039 ) {
12040 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12041 cx.propagate();
12042 return;
12043 }
12044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12046 s.move_heads_with(|map, head, _| {
12047 (
12048 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12049 SelectionGoal::None,
12050 )
12051 });
12052 })
12053 }
12054
12055 pub fn select_to_start_of_next_excerpt(
12056 &mut self,
12057 _: &SelectToStartOfNextExcerpt,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12062 cx.propagate();
12063 return;
12064 }
12065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12067 s.move_heads_with(|map, head, _| {
12068 (
12069 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12070 SelectionGoal::None,
12071 )
12072 });
12073 })
12074 }
12075
12076 pub fn select_to_end_of_excerpt(
12077 &mut self,
12078 _: &SelectToEndOfExcerpt,
12079 window: &mut Window,
12080 cx: &mut Context<Self>,
12081 ) {
12082 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12083 cx.propagate();
12084 return;
12085 }
12086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12088 s.move_heads_with(|map, head, _| {
12089 (
12090 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12091 SelectionGoal::None,
12092 )
12093 });
12094 })
12095 }
12096
12097 pub fn select_to_end_of_previous_excerpt(
12098 &mut self,
12099 _: &SelectToEndOfPreviousExcerpt,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12104 cx.propagate();
12105 return;
12106 }
12107 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12109 s.move_heads_with(|map, head, _| {
12110 (
12111 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12112 SelectionGoal::None,
12113 )
12114 });
12115 })
12116 }
12117
12118 pub fn move_to_beginning(
12119 &mut self,
12120 _: &MoveToBeginning,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12125 cx.propagate();
12126 return;
12127 }
12128 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12129 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12130 s.select_ranges(vec![0..0]);
12131 });
12132 }
12133
12134 pub fn select_to_beginning(
12135 &mut self,
12136 _: &SelectToBeginning,
12137 window: &mut Window,
12138 cx: &mut Context<Self>,
12139 ) {
12140 let mut selection = self.selections.last::<Point>(cx);
12141 selection.set_head(Point::zero(), SelectionGoal::None);
12142 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12143 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12144 s.select(vec![selection]);
12145 });
12146 }
12147
12148 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12149 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12150 cx.propagate();
12151 return;
12152 }
12153 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12154 let cursor = self.buffer.read(cx).read(cx).len();
12155 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12156 s.select_ranges(vec![cursor..cursor])
12157 });
12158 }
12159
12160 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12161 self.nav_history = nav_history;
12162 }
12163
12164 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12165 self.nav_history.as_ref()
12166 }
12167
12168 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12169 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12170 }
12171
12172 fn push_to_nav_history(
12173 &mut self,
12174 cursor_anchor: Anchor,
12175 new_position: Option<Point>,
12176 is_deactivate: bool,
12177 cx: &mut Context<Self>,
12178 ) {
12179 if let Some(nav_history) = self.nav_history.as_mut() {
12180 let buffer = self.buffer.read(cx).read(cx);
12181 let cursor_position = cursor_anchor.to_point(&buffer);
12182 let scroll_state = self.scroll_manager.anchor();
12183 let scroll_top_row = scroll_state.top_row(&buffer);
12184 drop(buffer);
12185
12186 if let Some(new_position) = new_position {
12187 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12188 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12189 return;
12190 }
12191 }
12192
12193 nav_history.push(
12194 Some(NavigationData {
12195 cursor_anchor,
12196 cursor_position,
12197 scroll_anchor: scroll_state,
12198 scroll_top_row,
12199 }),
12200 cx,
12201 );
12202 cx.emit(EditorEvent::PushedToNavHistory {
12203 anchor: cursor_anchor,
12204 is_deactivate,
12205 })
12206 }
12207 }
12208
12209 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12210 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12211 let buffer = self.buffer.read(cx).snapshot(cx);
12212 let mut selection = self.selections.first::<usize>(cx);
12213 selection.set_head(buffer.len(), SelectionGoal::None);
12214 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12215 s.select(vec![selection]);
12216 });
12217 }
12218
12219 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12220 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12221 let end = self.buffer.read(cx).read(cx).len();
12222 self.change_selections(None, window, cx, |s| {
12223 s.select_ranges(vec![0..end]);
12224 });
12225 }
12226
12227 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12228 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12230 let mut selections = self.selections.all::<Point>(cx);
12231 let max_point = display_map.buffer_snapshot.max_point();
12232 for selection in &mut selections {
12233 let rows = selection.spanned_rows(true, &display_map);
12234 selection.start = Point::new(rows.start.0, 0);
12235 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12236 selection.reversed = false;
12237 }
12238 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12239 s.select(selections);
12240 });
12241 }
12242
12243 pub fn split_selection_into_lines(
12244 &mut self,
12245 _: &SplitSelectionIntoLines,
12246 window: &mut Window,
12247 cx: &mut Context<Self>,
12248 ) {
12249 let selections = self
12250 .selections
12251 .all::<Point>(cx)
12252 .into_iter()
12253 .map(|selection| selection.start..selection.end)
12254 .collect::<Vec<_>>();
12255 self.unfold_ranges(&selections, true, true, cx);
12256
12257 let mut new_selection_ranges = Vec::new();
12258 {
12259 let buffer = self.buffer.read(cx).read(cx);
12260 for selection in selections {
12261 for row in selection.start.row..selection.end.row {
12262 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12263 new_selection_ranges.push(cursor..cursor);
12264 }
12265
12266 let is_multiline_selection = selection.start.row != selection.end.row;
12267 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12268 // so this action feels more ergonomic when paired with other selection operations
12269 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12270 if !should_skip_last {
12271 new_selection_ranges.push(selection.end..selection.end);
12272 }
12273 }
12274 }
12275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12276 s.select_ranges(new_selection_ranges);
12277 });
12278 }
12279
12280 pub fn add_selection_above(
12281 &mut self,
12282 _: &AddSelectionAbove,
12283 window: &mut Window,
12284 cx: &mut Context<Self>,
12285 ) {
12286 self.add_selection(true, window, cx);
12287 }
12288
12289 pub fn add_selection_below(
12290 &mut self,
12291 _: &AddSelectionBelow,
12292 window: &mut Window,
12293 cx: &mut Context<Self>,
12294 ) {
12295 self.add_selection(false, window, cx);
12296 }
12297
12298 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12299 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12300
12301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12302 let mut selections = self.selections.all::<Point>(cx);
12303 let text_layout_details = self.text_layout_details(window);
12304 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12305 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12306 let range = oldest_selection.display_range(&display_map).sorted();
12307
12308 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12309 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12310 let positions = start_x.min(end_x)..start_x.max(end_x);
12311
12312 selections.clear();
12313 let mut stack = Vec::new();
12314 for row in range.start.row().0..=range.end.row().0 {
12315 if let Some(selection) = self.selections.build_columnar_selection(
12316 &display_map,
12317 DisplayRow(row),
12318 &positions,
12319 oldest_selection.reversed,
12320 &text_layout_details,
12321 ) {
12322 stack.push(selection.id);
12323 selections.push(selection);
12324 }
12325 }
12326
12327 if above {
12328 stack.reverse();
12329 }
12330
12331 AddSelectionsState { above, stack }
12332 });
12333
12334 let last_added_selection = *state.stack.last().unwrap();
12335 let mut new_selections = Vec::new();
12336 if above == state.above {
12337 let end_row = if above {
12338 DisplayRow(0)
12339 } else {
12340 display_map.max_point().row()
12341 };
12342
12343 'outer: for selection in selections {
12344 if selection.id == last_added_selection {
12345 let range = selection.display_range(&display_map).sorted();
12346 debug_assert_eq!(range.start.row(), range.end.row());
12347 let mut row = range.start.row();
12348 let positions =
12349 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12350 px(start)..px(end)
12351 } else {
12352 let start_x =
12353 display_map.x_for_display_point(range.start, &text_layout_details);
12354 let end_x =
12355 display_map.x_for_display_point(range.end, &text_layout_details);
12356 start_x.min(end_x)..start_x.max(end_x)
12357 };
12358
12359 while row != end_row {
12360 if above {
12361 row.0 -= 1;
12362 } else {
12363 row.0 += 1;
12364 }
12365
12366 if let Some(new_selection) = self.selections.build_columnar_selection(
12367 &display_map,
12368 row,
12369 &positions,
12370 selection.reversed,
12371 &text_layout_details,
12372 ) {
12373 state.stack.push(new_selection.id);
12374 if above {
12375 new_selections.push(new_selection);
12376 new_selections.push(selection);
12377 } else {
12378 new_selections.push(selection);
12379 new_selections.push(new_selection);
12380 }
12381
12382 continue 'outer;
12383 }
12384 }
12385 }
12386
12387 new_selections.push(selection);
12388 }
12389 } else {
12390 new_selections = selections;
12391 new_selections.retain(|s| s.id != last_added_selection);
12392 state.stack.pop();
12393 }
12394
12395 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12396 s.select(new_selections);
12397 });
12398 if state.stack.len() > 1 {
12399 self.add_selections_state = Some(state);
12400 }
12401 }
12402
12403 fn select_match_ranges(
12404 &mut self,
12405 range: Range<usize>,
12406 reversed: bool,
12407 replace_newest: bool,
12408 auto_scroll: Option<Autoscroll>,
12409 window: &mut Window,
12410 cx: &mut Context<Editor>,
12411 ) {
12412 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12413 self.change_selections(auto_scroll, window, cx, |s| {
12414 if replace_newest {
12415 s.delete(s.newest_anchor().id);
12416 }
12417 if reversed {
12418 s.insert_range(range.end..range.start);
12419 } else {
12420 s.insert_range(range);
12421 }
12422 });
12423 }
12424
12425 pub fn select_next_match_internal(
12426 &mut self,
12427 display_map: &DisplaySnapshot,
12428 replace_newest: bool,
12429 autoscroll: Option<Autoscroll>,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) -> Result<()> {
12433 let buffer = &display_map.buffer_snapshot;
12434 let mut selections = self.selections.all::<usize>(cx);
12435 if let Some(mut select_next_state) = self.select_next_state.take() {
12436 let query = &select_next_state.query;
12437 if !select_next_state.done {
12438 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12439 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12440 let mut next_selected_range = None;
12441
12442 let bytes_after_last_selection =
12443 buffer.bytes_in_range(last_selection.end..buffer.len());
12444 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12445 let query_matches = query
12446 .stream_find_iter(bytes_after_last_selection)
12447 .map(|result| (last_selection.end, result))
12448 .chain(
12449 query
12450 .stream_find_iter(bytes_before_first_selection)
12451 .map(|result| (0, result)),
12452 );
12453
12454 for (start_offset, query_match) in query_matches {
12455 let query_match = query_match.unwrap(); // can only fail due to I/O
12456 let offset_range =
12457 start_offset + query_match.start()..start_offset + query_match.end();
12458 let display_range = offset_range.start.to_display_point(display_map)
12459 ..offset_range.end.to_display_point(display_map);
12460
12461 if !select_next_state.wordwise
12462 || (!movement::is_inside_word(display_map, display_range.start)
12463 && !movement::is_inside_word(display_map, display_range.end))
12464 {
12465 // TODO: This is n^2, because we might check all the selections
12466 if !selections
12467 .iter()
12468 .any(|selection| selection.range().overlaps(&offset_range))
12469 {
12470 next_selected_range = Some(offset_range);
12471 break;
12472 }
12473 }
12474 }
12475
12476 if let Some(next_selected_range) = next_selected_range {
12477 self.select_match_ranges(
12478 next_selected_range,
12479 last_selection.reversed,
12480 replace_newest,
12481 autoscroll,
12482 window,
12483 cx,
12484 );
12485 } else {
12486 select_next_state.done = true;
12487 }
12488 }
12489
12490 self.select_next_state = Some(select_next_state);
12491 } else {
12492 let mut only_carets = true;
12493 let mut same_text_selected = true;
12494 let mut selected_text = None;
12495
12496 let mut selections_iter = selections.iter().peekable();
12497 while let Some(selection) = selections_iter.next() {
12498 if selection.start != selection.end {
12499 only_carets = false;
12500 }
12501
12502 if same_text_selected {
12503 if selected_text.is_none() {
12504 selected_text =
12505 Some(buffer.text_for_range(selection.range()).collect::<String>());
12506 }
12507
12508 if let Some(next_selection) = selections_iter.peek() {
12509 if next_selection.range().len() == selection.range().len() {
12510 let next_selected_text = buffer
12511 .text_for_range(next_selection.range())
12512 .collect::<String>();
12513 if Some(next_selected_text) != selected_text {
12514 same_text_selected = false;
12515 selected_text = None;
12516 }
12517 } else {
12518 same_text_selected = false;
12519 selected_text = None;
12520 }
12521 }
12522 }
12523 }
12524
12525 if only_carets {
12526 for selection in &mut selections {
12527 let word_range = movement::surrounding_word(
12528 display_map,
12529 selection.start.to_display_point(display_map),
12530 );
12531 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12532 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12533 selection.goal = SelectionGoal::None;
12534 selection.reversed = false;
12535 self.select_match_ranges(
12536 selection.start..selection.end,
12537 selection.reversed,
12538 replace_newest,
12539 autoscroll,
12540 window,
12541 cx,
12542 );
12543 }
12544
12545 if selections.len() == 1 {
12546 let selection = selections
12547 .last()
12548 .expect("ensured that there's only one selection");
12549 let query = buffer
12550 .text_for_range(selection.start..selection.end)
12551 .collect::<String>();
12552 let is_empty = query.is_empty();
12553 let select_state = SelectNextState {
12554 query: AhoCorasick::new(&[query])?,
12555 wordwise: true,
12556 done: is_empty,
12557 };
12558 self.select_next_state = Some(select_state);
12559 } else {
12560 self.select_next_state = None;
12561 }
12562 } else if let Some(selected_text) = selected_text {
12563 self.select_next_state = Some(SelectNextState {
12564 query: AhoCorasick::new(&[selected_text])?,
12565 wordwise: false,
12566 done: false,
12567 });
12568 self.select_next_match_internal(
12569 display_map,
12570 replace_newest,
12571 autoscroll,
12572 window,
12573 cx,
12574 )?;
12575 }
12576 }
12577 Ok(())
12578 }
12579
12580 pub fn select_all_matches(
12581 &mut self,
12582 _action: &SelectAllMatches,
12583 window: &mut Window,
12584 cx: &mut Context<Self>,
12585 ) -> Result<()> {
12586 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12587
12588 self.push_to_selection_history();
12589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12590
12591 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12592 let Some(select_next_state) = self.select_next_state.as_mut() else {
12593 return Ok(());
12594 };
12595 if select_next_state.done {
12596 return Ok(());
12597 }
12598
12599 let mut new_selections = Vec::new();
12600
12601 let reversed = self.selections.oldest::<usize>(cx).reversed;
12602 let buffer = &display_map.buffer_snapshot;
12603 let query_matches = select_next_state
12604 .query
12605 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12606
12607 for query_match in query_matches.into_iter() {
12608 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12609 let offset_range = if reversed {
12610 query_match.end()..query_match.start()
12611 } else {
12612 query_match.start()..query_match.end()
12613 };
12614 let display_range = offset_range.start.to_display_point(&display_map)
12615 ..offset_range.end.to_display_point(&display_map);
12616
12617 if !select_next_state.wordwise
12618 || (!movement::is_inside_word(&display_map, display_range.start)
12619 && !movement::is_inside_word(&display_map, display_range.end))
12620 {
12621 new_selections.push(offset_range.start..offset_range.end);
12622 }
12623 }
12624
12625 select_next_state.done = true;
12626 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12627 self.change_selections(None, window, cx, |selections| {
12628 selections.select_ranges(new_selections)
12629 });
12630
12631 Ok(())
12632 }
12633
12634 pub fn select_next(
12635 &mut self,
12636 action: &SelectNext,
12637 window: &mut Window,
12638 cx: &mut Context<Self>,
12639 ) -> Result<()> {
12640 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12641 self.push_to_selection_history();
12642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12643 self.select_next_match_internal(
12644 &display_map,
12645 action.replace_newest,
12646 Some(Autoscroll::newest()),
12647 window,
12648 cx,
12649 )?;
12650 Ok(())
12651 }
12652
12653 pub fn select_previous(
12654 &mut self,
12655 action: &SelectPrevious,
12656 window: &mut Window,
12657 cx: &mut Context<Self>,
12658 ) -> Result<()> {
12659 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12660 self.push_to_selection_history();
12661 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12662 let buffer = &display_map.buffer_snapshot;
12663 let mut selections = self.selections.all::<usize>(cx);
12664 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12665 let query = &select_prev_state.query;
12666 if !select_prev_state.done {
12667 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12668 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12669 let mut next_selected_range = None;
12670 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12671 let bytes_before_last_selection =
12672 buffer.reversed_bytes_in_range(0..last_selection.start);
12673 let bytes_after_first_selection =
12674 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12675 let query_matches = query
12676 .stream_find_iter(bytes_before_last_selection)
12677 .map(|result| (last_selection.start, result))
12678 .chain(
12679 query
12680 .stream_find_iter(bytes_after_first_selection)
12681 .map(|result| (buffer.len(), result)),
12682 );
12683 for (end_offset, query_match) in query_matches {
12684 let query_match = query_match.unwrap(); // can only fail due to I/O
12685 let offset_range =
12686 end_offset - query_match.end()..end_offset - query_match.start();
12687 let display_range = offset_range.start.to_display_point(&display_map)
12688 ..offset_range.end.to_display_point(&display_map);
12689
12690 if !select_prev_state.wordwise
12691 || (!movement::is_inside_word(&display_map, display_range.start)
12692 && !movement::is_inside_word(&display_map, display_range.end))
12693 {
12694 next_selected_range = Some(offset_range);
12695 break;
12696 }
12697 }
12698
12699 if let Some(next_selected_range) = next_selected_range {
12700 self.select_match_ranges(
12701 next_selected_range,
12702 last_selection.reversed,
12703 action.replace_newest,
12704 Some(Autoscroll::newest()),
12705 window,
12706 cx,
12707 );
12708 } else {
12709 select_prev_state.done = true;
12710 }
12711 }
12712
12713 self.select_prev_state = Some(select_prev_state);
12714 } else {
12715 let mut only_carets = true;
12716 let mut same_text_selected = true;
12717 let mut selected_text = None;
12718
12719 let mut selections_iter = selections.iter().peekable();
12720 while let Some(selection) = selections_iter.next() {
12721 if selection.start != selection.end {
12722 only_carets = false;
12723 }
12724
12725 if same_text_selected {
12726 if selected_text.is_none() {
12727 selected_text =
12728 Some(buffer.text_for_range(selection.range()).collect::<String>());
12729 }
12730
12731 if let Some(next_selection) = selections_iter.peek() {
12732 if next_selection.range().len() == selection.range().len() {
12733 let next_selected_text = buffer
12734 .text_for_range(next_selection.range())
12735 .collect::<String>();
12736 if Some(next_selected_text) != selected_text {
12737 same_text_selected = false;
12738 selected_text = None;
12739 }
12740 } else {
12741 same_text_selected = false;
12742 selected_text = None;
12743 }
12744 }
12745 }
12746 }
12747
12748 if only_carets {
12749 for selection in &mut selections {
12750 let word_range = movement::surrounding_word(
12751 &display_map,
12752 selection.start.to_display_point(&display_map),
12753 );
12754 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12755 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12756 selection.goal = SelectionGoal::None;
12757 selection.reversed = false;
12758 self.select_match_ranges(
12759 selection.start..selection.end,
12760 selection.reversed,
12761 action.replace_newest,
12762 Some(Autoscroll::newest()),
12763 window,
12764 cx,
12765 );
12766 }
12767 if selections.len() == 1 {
12768 let selection = selections
12769 .last()
12770 .expect("ensured that there's only one selection");
12771 let query = buffer
12772 .text_for_range(selection.start..selection.end)
12773 .collect::<String>();
12774 let is_empty = query.is_empty();
12775 let select_state = SelectNextState {
12776 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12777 wordwise: true,
12778 done: is_empty,
12779 };
12780 self.select_prev_state = Some(select_state);
12781 } else {
12782 self.select_prev_state = None;
12783 }
12784 } else if let Some(selected_text) = selected_text {
12785 self.select_prev_state = Some(SelectNextState {
12786 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12787 wordwise: false,
12788 done: false,
12789 });
12790 self.select_previous(action, window, cx)?;
12791 }
12792 }
12793 Ok(())
12794 }
12795
12796 pub fn find_next_match(
12797 &mut self,
12798 _: &FindNextMatch,
12799 window: &mut Window,
12800 cx: &mut Context<Self>,
12801 ) -> Result<()> {
12802 let selections = self.selections.disjoint_anchors();
12803 match selections.first() {
12804 Some(first) if selections.len() >= 2 => {
12805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12806 s.select_ranges([first.range()]);
12807 });
12808 }
12809 _ => self.select_next(
12810 &SelectNext {
12811 replace_newest: true,
12812 },
12813 window,
12814 cx,
12815 )?,
12816 }
12817 Ok(())
12818 }
12819
12820 pub fn find_previous_match(
12821 &mut self,
12822 _: &FindPreviousMatch,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) -> Result<()> {
12826 let selections = self.selections.disjoint_anchors();
12827 match selections.last() {
12828 Some(last) if selections.len() >= 2 => {
12829 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12830 s.select_ranges([last.range()]);
12831 });
12832 }
12833 _ => self.select_previous(
12834 &SelectPrevious {
12835 replace_newest: true,
12836 },
12837 window,
12838 cx,
12839 )?,
12840 }
12841 Ok(())
12842 }
12843
12844 pub fn toggle_comments(
12845 &mut self,
12846 action: &ToggleComments,
12847 window: &mut Window,
12848 cx: &mut Context<Self>,
12849 ) {
12850 if self.read_only(cx) {
12851 return;
12852 }
12853 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12854 let text_layout_details = &self.text_layout_details(window);
12855 self.transact(window, cx, |this, window, cx| {
12856 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12857 let mut edits = Vec::new();
12858 let mut selection_edit_ranges = Vec::new();
12859 let mut last_toggled_row = None;
12860 let snapshot = this.buffer.read(cx).read(cx);
12861 let empty_str: Arc<str> = Arc::default();
12862 let mut suffixes_inserted = Vec::new();
12863 let ignore_indent = action.ignore_indent;
12864
12865 fn comment_prefix_range(
12866 snapshot: &MultiBufferSnapshot,
12867 row: MultiBufferRow,
12868 comment_prefix: &str,
12869 comment_prefix_whitespace: &str,
12870 ignore_indent: bool,
12871 ) -> Range<Point> {
12872 let indent_size = if ignore_indent {
12873 0
12874 } else {
12875 snapshot.indent_size_for_line(row).len
12876 };
12877
12878 let start = Point::new(row.0, indent_size);
12879
12880 let mut line_bytes = snapshot
12881 .bytes_in_range(start..snapshot.max_point())
12882 .flatten()
12883 .copied();
12884
12885 // If this line currently begins with the line comment prefix, then record
12886 // the range containing the prefix.
12887 if line_bytes
12888 .by_ref()
12889 .take(comment_prefix.len())
12890 .eq(comment_prefix.bytes())
12891 {
12892 // Include any whitespace that matches the comment prefix.
12893 let matching_whitespace_len = line_bytes
12894 .zip(comment_prefix_whitespace.bytes())
12895 .take_while(|(a, b)| a == b)
12896 .count() as u32;
12897 let end = Point::new(
12898 start.row,
12899 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12900 );
12901 start..end
12902 } else {
12903 start..start
12904 }
12905 }
12906
12907 fn comment_suffix_range(
12908 snapshot: &MultiBufferSnapshot,
12909 row: MultiBufferRow,
12910 comment_suffix: &str,
12911 comment_suffix_has_leading_space: bool,
12912 ) -> Range<Point> {
12913 let end = Point::new(row.0, snapshot.line_len(row));
12914 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12915
12916 let mut line_end_bytes = snapshot
12917 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12918 .flatten()
12919 .copied();
12920
12921 let leading_space_len = if suffix_start_column > 0
12922 && line_end_bytes.next() == Some(b' ')
12923 && comment_suffix_has_leading_space
12924 {
12925 1
12926 } else {
12927 0
12928 };
12929
12930 // If this line currently begins with the line comment prefix, then record
12931 // the range containing the prefix.
12932 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12933 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12934 start..end
12935 } else {
12936 end..end
12937 }
12938 }
12939
12940 // TODO: Handle selections that cross excerpts
12941 for selection in &mut selections {
12942 let start_column = snapshot
12943 .indent_size_for_line(MultiBufferRow(selection.start.row))
12944 .len;
12945 let language = if let Some(language) =
12946 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12947 {
12948 language
12949 } else {
12950 continue;
12951 };
12952
12953 selection_edit_ranges.clear();
12954
12955 // If multiple selections contain a given row, avoid processing that
12956 // row more than once.
12957 let mut start_row = MultiBufferRow(selection.start.row);
12958 if last_toggled_row == Some(start_row) {
12959 start_row = start_row.next_row();
12960 }
12961 let end_row =
12962 if selection.end.row > selection.start.row && selection.end.column == 0 {
12963 MultiBufferRow(selection.end.row - 1)
12964 } else {
12965 MultiBufferRow(selection.end.row)
12966 };
12967 last_toggled_row = Some(end_row);
12968
12969 if start_row > end_row {
12970 continue;
12971 }
12972
12973 // If the language has line comments, toggle those.
12974 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12975
12976 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12977 if ignore_indent {
12978 full_comment_prefixes = full_comment_prefixes
12979 .into_iter()
12980 .map(|s| Arc::from(s.trim_end()))
12981 .collect();
12982 }
12983
12984 if !full_comment_prefixes.is_empty() {
12985 let first_prefix = full_comment_prefixes
12986 .first()
12987 .expect("prefixes is non-empty");
12988 let prefix_trimmed_lengths = full_comment_prefixes
12989 .iter()
12990 .map(|p| p.trim_end_matches(' ').len())
12991 .collect::<SmallVec<[usize; 4]>>();
12992
12993 let mut all_selection_lines_are_comments = true;
12994
12995 for row in start_row.0..=end_row.0 {
12996 let row = MultiBufferRow(row);
12997 if start_row < end_row && snapshot.is_line_blank(row) {
12998 continue;
12999 }
13000
13001 let prefix_range = full_comment_prefixes
13002 .iter()
13003 .zip(prefix_trimmed_lengths.iter().copied())
13004 .map(|(prefix, trimmed_prefix_len)| {
13005 comment_prefix_range(
13006 snapshot.deref(),
13007 row,
13008 &prefix[..trimmed_prefix_len],
13009 &prefix[trimmed_prefix_len..],
13010 ignore_indent,
13011 )
13012 })
13013 .max_by_key(|range| range.end.column - range.start.column)
13014 .expect("prefixes is non-empty");
13015
13016 if prefix_range.is_empty() {
13017 all_selection_lines_are_comments = false;
13018 }
13019
13020 selection_edit_ranges.push(prefix_range);
13021 }
13022
13023 if all_selection_lines_are_comments {
13024 edits.extend(
13025 selection_edit_ranges
13026 .iter()
13027 .cloned()
13028 .map(|range| (range, empty_str.clone())),
13029 );
13030 } else {
13031 let min_column = selection_edit_ranges
13032 .iter()
13033 .map(|range| range.start.column)
13034 .min()
13035 .unwrap_or(0);
13036 edits.extend(selection_edit_ranges.iter().map(|range| {
13037 let position = Point::new(range.start.row, min_column);
13038 (position..position, first_prefix.clone())
13039 }));
13040 }
13041 } else if let Some((full_comment_prefix, comment_suffix)) =
13042 language.block_comment_delimiters()
13043 {
13044 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13045 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13046 let prefix_range = comment_prefix_range(
13047 snapshot.deref(),
13048 start_row,
13049 comment_prefix,
13050 comment_prefix_whitespace,
13051 ignore_indent,
13052 );
13053 let suffix_range = comment_suffix_range(
13054 snapshot.deref(),
13055 end_row,
13056 comment_suffix.trim_start_matches(' '),
13057 comment_suffix.starts_with(' '),
13058 );
13059
13060 if prefix_range.is_empty() || suffix_range.is_empty() {
13061 edits.push((
13062 prefix_range.start..prefix_range.start,
13063 full_comment_prefix.clone(),
13064 ));
13065 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13066 suffixes_inserted.push((end_row, comment_suffix.len()));
13067 } else {
13068 edits.push((prefix_range, empty_str.clone()));
13069 edits.push((suffix_range, empty_str.clone()));
13070 }
13071 } else {
13072 continue;
13073 }
13074 }
13075
13076 drop(snapshot);
13077 this.buffer.update(cx, |buffer, cx| {
13078 buffer.edit(edits, None, cx);
13079 });
13080
13081 // Adjust selections so that they end before any comment suffixes that
13082 // were inserted.
13083 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13084 let mut selections = this.selections.all::<Point>(cx);
13085 let snapshot = this.buffer.read(cx).read(cx);
13086 for selection in &mut selections {
13087 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13088 match row.cmp(&MultiBufferRow(selection.end.row)) {
13089 Ordering::Less => {
13090 suffixes_inserted.next();
13091 continue;
13092 }
13093 Ordering::Greater => break,
13094 Ordering::Equal => {
13095 if selection.end.column == snapshot.line_len(row) {
13096 if selection.is_empty() {
13097 selection.start.column -= suffix_len as u32;
13098 }
13099 selection.end.column -= suffix_len as u32;
13100 }
13101 break;
13102 }
13103 }
13104 }
13105 }
13106
13107 drop(snapshot);
13108 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13109 s.select(selections)
13110 });
13111
13112 let selections = this.selections.all::<Point>(cx);
13113 let selections_on_single_row = selections.windows(2).all(|selections| {
13114 selections[0].start.row == selections[1].start.row
13115 && selections[0].end.row == selections[1].end.row
13116 && selections[0].start.row == selections[0].end.row
13117 });
13118 let selections_selecting = selections
13119 .iter()
13120 .any(|selection| selection.start != selection.end);
13121 let advance_downwards = action.advance_downwards
13122 && selections_on_single_row
13123 && !selections_selecting
13124 && !matches!(this.mode, EditorMode::SingleLine { .. });
13125
13126 if advance_downwards {
13127 let snapshot = this.buffer.read(cx).snapshot(cx);
13128
13129 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13130 s.move_cursors_with(|display_snapshot, display_point, _| {
13131 let mut point = display_point.to_point(display_snapshot);
13132 point.row += 1;
13133 point = snapshot.clip_point(point, Bias::Left);
13134 let display_point = point.to_display_point(display_snapshot);
13135 let goal = SelectionGoal::HorizontalPosition(
13136 display_snapshot
13137 .x_for_display_point(display_point, text_layout_details)
13138 .into(),
13139 );
13140 (display_point, goal)
13141 })
13142 });
13143 }
13144 });
13145 }
13146
13147 pub fn select_enclosing_symbol(
13148 &mut self,
13149 _: &SelectEnclosingSymbol,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13154
13155 let buffer = self.buffer.read(cx).snapshot(cx);
13156 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13157
13158 fn update_selection(
13159 selection: &Selection<usize>,
13160 buffer_snap: &MultiBufferSnapshot,
13161 ) -> Option<Selection<usize>> {
13162 let cursor = selection.head();
13163 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13164 for symbol in symbols.iter().rev() {
13165 let start = symbol.range.start.to_offset(buffer_snap);
13166 let end = symbol.range.end.to_offset(buffer_snap);
13167 let new_range = start..end;
13168 if start < selection.start || end > selection.end {
13169 return Some(Selection {
13170 id: selection.id,
13171 start: new_range.start,
13172 end: new_range.end,
13173 goal: SelectionGoal::None,
13174 reversed: selection.reversed,
13175 });
13176 }
13177 }
13178 None
13179 }
13180
13181 let mut selected_larger_symbol = false;
13182 let new_selections = old_selections
13183 .iter()
13184 .map(|selection| match update_selection(selection, &buffer) {
13185 Some(new_selection) => {
13186 if new_selection.range() != selection.range() {
13187 selected_larger_symbol = true;
13188 }
13189 new_selection
13190 }
13191 None => selection.clone(),
13192 })
13193 .collect::<Vec<_>>();
13194
13195 if selected_larger_symbol {
13196 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13197 s.select(new_selections);
13198 });
13199 }
13200 }
13201
13202 pub fn select_larger_syntax_node(
13203 &mut self,
13204 _: &SelectLargerSyntaxNode,
13205 window: &mut Window,
13206 cx: &mut Context<Self>,
13207 ) {
13208 let Some(visible_row_count) = self.visible_row_count() else {
13209 return;
13210 };
13211 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13212 if old_selections.is_empty() {
13213 return;
13214 }
13215
13216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13217
13218 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13219 let buffer = self.buffer.read(cx).snapshot(cx);
13220
13221 let mut selected_larger_node = false;
13222 let mut new_selections = old_selections
13223 .iter()
13224 .map(|selection| {
13225 let old_range = selection.start..selection.end;
13226
13227 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13228 // manually select word at selection
13229 if ["string_content", "inline"].contains(&node.kind()) {
13230 let word_range = {
13231 let display_point = buffer
13232 .offset_to_point(old_range.start)
13233 .to_display_point(&display_map);
13234 let Range { start, end } =
13235 movement::surrounding_word(&display_map, display_point);
13236 start.to_point(&display_map).to_offset(&buffer)
13237 ..end.to_point(&display_map).to_offset(&buffer)
13238 };
13239 // ignore if word is already selected
13240 if !word_range.is_empty() && old_range != word_range {
13241 let last_word_range = {
13242 let display_point = buffer
13243 .offset_to_point(old_range.end)
13244 .to_display_point(&display_map);
13245 let Range { start, end } =
13246 movement::surrounding_word(&display_map, display_point);
13247 start.to_point(&display_map).to_offset(&buffer)
13248 ..end.to_point(&display_map).to_offset(&buffer)
13249 };
13250 // only select word if start and end point belongs to same word
13251 if word_range == last_word_range {
13252 selected_larger_node = true;
13253 return Selection {
13254 id: selection.id,
13255 start: word_range.start,
13256 end: word_range.end,
13257 goal: SelectionGoal::None,
13258 reversed: selection.reversed,
13259 };
13260 }
13261 }
13262 }
13263 }
13264
13265 let mut new_range = old_range.clone();
13266 while let Some((_node, containing_range)) =
13267 buffer.syntax_ancestor(new_range.clone())
13268 {
13269 new_range = match containing_range {
13270 MultiOrSingleBufferOffsetRange::Single(_) => break,
13271 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13272 };
13273 if !display_map.intersects_fold(new_range.start)
13274 && !display_map.intersects_fold(new_range.end)
13275 {
13276 break;
13277 }
13278 }
13279
13280 selected_larger_node |= new_range != old_range;
13281 Selection {
13282 id: selection.id,
13283 start: new_range.start,
13284 end: new_range.end,
13285 goal: SelectionGoal::None,
13286 reversed: selection.reversed,
13287 }
13288 })
13289 .collect::<Vec<_>>();
13290
13291 if !selected_larger_node {
13292 return; // don't put this call in the history
13293 }
13294
13295 // scroll based on transformation done to the last selection created by the user
13296 let (last_old, last_new) = old_selections
13297 .last()
13298 .zip(new_selections.last().cloned())
13299 .expect("old_selections isn't empty");
13300
13301 // revert selection
13302 let is_selection_reversed = {
13303 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13304 new_selections.last_mut().expect("checked above").reversed =
13305 should_newest_selection_be_reversed;
13306 should_newest_selection_be_reversed
13307 };
13308
13309 if selected_larger_node {
13310 self.select_syntax_node_history.disable_clearing = true;
13311 self.change_selections(None, window, cx, |s| {
13312 s.select(new_selections.clone());
13313 });
13314 self.select_syntax_node_history.disable_clearing = false;
13315 }
13316
13317 let start_row = last_new.start.to_display_point(&display_map).row().0;
13318 let end_row = last_new.end.to_display_point(&display_map).row().0;
13319 let selection_height = end_row - start_row + 1;
13320 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13321
13322 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13323 let scroll_behavior = if fits_on_the_screen {
13324 self.request_autoscroll(Autoscroll::fit(), cx);
13325 SelectSyntaxNodeScrollBehavior::FitSelection
13326 } else if is_selection_reversed {
13327 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13328 SelectSyntaxNodeScrollBehavior::CursorTop
13329 } else {
13330 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13331 SelectSyntaxNodeScrollBehavior::CursorBottom
13332 };
13333
13334 self.select_syntax_node_history.push((
13335 old_selections,
13336 scroll_behavior,
13337 is_selection_reversed,
13338 ));
13339 }
13340
13341 pub fn select_smaller_syntax_node(
13342 &mut self,
13343 _: &SelectSmallerSyntaxNode,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13348
13349 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13350 self.select_syntax_node_history.pop()
13351 {
13352 if let Some(selection) = selections.last_mut() {
13353 selection.reversed = is_selection_reversed;
13354 }
13355
13356 self.select_syntax_node_history.disable_clearing = true;
13357 self.change_selections(None, window, cx, |s| {
13358 s.select(selections.to_vec());
13359 });
13360 self.select_syntax_node_history.disable_clearing = false;
13361
13362 match scroll_behavior {
13363 SelectSyntaxNodeScrollBehavior::CursorTop => {
13364 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13365 }
13366 SelectSyntaxNodeScrollBehavior::FitSelection => {
13367 self.request_autoscroll(Autoscroll::fit(), cx);
13368 }
13369 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13370 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13371 }
13372 }
13373 }
13374 }
13375
13376 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13377 if !EditorSettings::get_global(cx).gutter.runnables {
13378 self.clear_tasks();
13379 return Task::ready(());
13380 }
13381 let project = self.project.as_ref().map(Entity::downgrade);
13382 let task_sources = self.lsp_task_sources(cx);
13383 cx.spawn_in(window, async move |editor, cx| {
13384 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13385 let Some(project) = project.and_then(|p| p.upgrade()) else {
13386 return;
13387 };
13388 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13389 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13390 }) else {
13391 return;
13392 };
13393
13394 let hide_runnables = project
13395 .update(cx, |project, cx| {
13396 // Do not display any test indicators in non-dev server remote projects.
13397 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13398 })
13399 .unwrap_or(true);
13400 if hide_runnables {
13401 return;
13402 }
13403 let new_rows =
13404 cx.background_spawn({
13405 let snapshot = display_snapshot.clone();
13406 async move {
13407 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13408 }
13409 })
13410 .await;
13411 let Ok(lsp_tasks) =
13412 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13413 else {
13414 return;
13415 };
13416 let lsp_tasks = lsp_tasks.await;
13417
13418 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13419 lsp_tasks
13420 .into_iter()
13421 .flat_map(|(kind, tasks)| {
13422 tasks.into_iter().filter_map(move |(location, task)| {
13423 Some((kind.clone(), location?, task))
13424 })
13425 })
13426 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13427 let buffer = location.target.buffer;
13428 let buffer_snapshot = buffer.read(cx).snapshot();
13429 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13430 |(excerpt_id, snapshot, _)| {
13431 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13432 display_snapshot
13433 .buffer_snapshot
13434 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13435 } else {
13436 None
13437 }
13438 },
13439 );
13440 if let Some(offset) = offset {
13441 let task_buffer_range =
13442 location.target.range.to_point(&buffer_snapshot);
13443 let context_buffer_range =
13444 task_buffer_range.to_offset(&buffer_snapshot);
13445 let context_range = BufferOffset(context_buffer_range.start)
13446 ..BufferOffset(context_buffer_range.end);
13447
13448 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13449 .or_insert_with(|| RunnableTasks {
13450 templates: Vec::new(),
13451 offset,
13452 column: task_buffer_range.start.column,
13453 extra_variables: HashMap::default(),
13454 context_range,
13455 })
13456 .templates
13457 .push((kind, task.original_task().clone()));
13458 }
13459
13460 acc
13461 })
13462 }) else {
13463 return;
13464 };
13465
13466 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13467 editor
13468 .update(cx, |editor, _| {
13469 editor.clear_tasks();
13470 for (key, mut value) in rows {
13471 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13472 value.templates.extend(lsp_tasks.templates);
13473 }
13474
13475 editor.insert_tasks(key, value);
13476 }
13477 for (key, value) in lsp_tasks_by_rows {
13478 editor.insert_tasks(key, value);
13479 }
13480 })
13481 .ok();
13482 })
13483 }
13484 fn fetch_runnable_ranges(
13485 snapshot: &DisplaySnapshot,
13486 range: Range<Anchor>,
13487 ) -> Vec<language::RunnableRange> {
13488 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13489 }
13490
13491 fn runnable_rows(
13492 project: Entity<Project>,
13493 snapshot: DisplaySnapshot,
13494 runnable_ranges: Vec<RunnableRange>,
13495 mut cx: AsyncWindowContext,
13496 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13497 runnable_ranges
13498 .into_iter()
13499 .filter_map(|mut runnable| {
13500 let tasks = cx
13501 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13502 .ok()?;
13503 if tasks.is_empty() {
13504 return None;
13505 }
13506
13507 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13508
13509 let row = snapshot
13510 .buffer_snapshot
13511 .buffer_line_for_row(MultiBufferRow(point.row))?
13512 .1
13513 .start
13514 .row;
13515
13516 let context_range =
13517 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13518 Some((
13519 (runnable.buffer_id, row),
13520 RunnableTasks {
13521 templates: tasks,
13522 offset: snapshot
13523 .buffer_snapshot
13524 .anchor_before(runnable.run_range.start),
13525 context_range,
13526 column: point.column,
13527 extra_variables: runnable.extra_captures,
13528 },
13529 ))
13530 })
13531 .collect()
13532 }
13533
13534 fn templates_with_tags(
13535 project: &Entity<Project>,
13536 runnable: &mut Runnable,
13537 cx: &mut App,
13538 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13539 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13540 let (worktree_id, file) = project
13541 .buffer_for_id(runnable.buffer, cx)
13542 .and_then(|buffer| buffer.read(cx).file())
13543 .map(|file| (file.worktree_id(cx), file.clone()))
13544 .unzip();
13545
13546 (
13547 project.task_store().read(cx).task_inventory().cloned(),
13548 worktree_id,
13549 file,
13550 )
13551 });
13552
13553 let mut templates_with_tags = mem::take(&mut runnable.tags)
13554 .into_iter()
13555 .flat_map(|RunnableTag(tag)| {
13556 inventory
13557 .as_ref()
13558 .into_iter()
13559 .flat_map(|inventory| {
13560 inventory.read(cx).list_tasks(
13561 file.clone(),
13562 Some(runnable.language.clone()),
13563 worktree_id,
13564 cx,
13565 )
13566 })
13567 .filter(move |(_, template)| {
13568 template.tags.iter().any(|source_tag| source_tag == &tag)
13569 })
13570 })
13571 .sorted_by_key(|(kind, _)| kind.to_owned())
13572 .collect::<Vec<_>>();
13573 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13574 // Strongest source wins; if we have worktree tag binding, prefer that to
13575 // global and language bindings;
13576 // if we have a global binding, prefer that to language binding.
13577 let first_mismatch = templates_with_tags
13578 .iter()
13579 .position(|(tag_source, _)| tag_source != leading_tag_source);
13580 if let Some(index) = first_mismatch {
13581 templates_with_tags.truncate(index);
13582 }
13583 }
13584
13585 templates_with_tags
13586 }
13587
13588 pub fn move_to_enclosing_bracket(
13589 &mut self,
13590 _: &MoveToEnclosingBracket,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13596 s.move_offsets_with(|snapshot, selection| {
13597 let Some(enclosing_bracket_ranges) =
13598 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13599 else {
13600 return;
13601 };
13602
13603 let mut best_length = usize::MAX;
13604 let mut best_inside = false;
13605 let mut best_in_bracket_range = false;
13606 let mut best_destination = None;
13607 for (open, close) in enclosing_bracket_ranges {
13608 let close = close.to_inclusive();
13609 let length = close.end() - open.start;
13610 let inside = selection.start >= open.end && selection.end <= *close.start();
13611 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13612 || close.contains(&selection.head());
13613
13614 // If best is next to a bracket and current isn't, skip
13615 if !in_bracket_range && best_in_bracket_range {
13616 continue;
13617 }
13618
13619 // Prefer smaller lengths unless best is inside and current isn't
13620 if length > best_length && (best_inside || !inside) {
13621 continue;
13622 }
13623
13624 best_length = length;
13625 best_inside = inside;
13626 best_in_bracket_range = in_bracket_range;
13627 best_destination = Some(
13628 if close.contains(&selection.start) && close.contains(&selection.end) {
13629 if inside { open.end } else { open.start }
13630 } else if inside {
13631 *close.start()
13632 } else {
13633 *close.end()
13634 },
13635 );
13636 }
13637
13638 if let Some(destination) = best_destination {
13639 selection.collapse_to(destination, SelectionGoal::None);
13640 }
13641 })
13642 });
13643 }
13644
13645 pub fn undo_selection(
13646 &mut self,
13647 _: &UndoSelection,
13648 window: &mut Window,
13649 cx: &mut Context<Self>,
13650 ) {
13651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13652 self.end_selection(window, cx);
13653 self.selection_history.mode = SelectionHistoryMode::Undoing;
13654 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13655 self.change_selections(None, window, cx, |s| {
13656 s.select_anchors(entry.selections.to_vec())
13657 });
13658 self.select_next_state = entry.select_next_state;
13659 self.select_prev_state = entry.select_prev_state;
13660 self.add_selections_state = entry.add_selections_state;
13661 self.request_autoscroll(Autoscroll::newest(), cx);
13662 }
13663 self.selection_history.mode = SelectionHistoryMode::Normal;
13664 }
13665
13666 pub fn redo_selection(
13667 &mut self,
13668 _: &RedoSelection,
13669 window: &mut Window,
13670 cx: &mut Context<Self>,
13671 ) {
13672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13673 self.end_selection(window, cx);
13674 self.selection_history.mode = SelectionHistoryMode::Redoing;
13675 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13676 self.change_selections(None, window, cx, |s| {
13677 s.select_anchors(entry.selections.to_vec())
13678 });
13679 self.select_next_state = entry.select_next_state;
13680 self.select_prev_state = entry.select_prev_state;
13681 self.add_selections_state = entry.add_selections_state;
13682 self.request_autoscroll(Autoscroll::newest(), cx);
13683 }
13684 self.selection_history.mode = SelectionHistoryMode::Normal;
13685 }
13686
13687 pub fn expand_excerpts(
13688 &mut self,
13689 action: &ExpandExcerpts,
13690 _: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13694 }
13695
13696 pub fn expand_excerpts_down(
13697 &mut self,
13698 action: &ExpandExcerptsDown,
13699 _: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13703 }
13704
13705 pub fn expand_excerpts_up(
13706 &mut self,
13707 action: &ExpandExcerptsUp,
13708 _: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13712 }
13713
13714 pub fn expand_excerpts_for_direction(
13715 &mut self,
13716 lines: u32,
13717 direction: ExpandExcerptDirection,
13718
13719 cx: &mut Context<Self>,
13720 ) {
13721 let selections = self.selections.disjoint_anchors();
13722
13723 let lines = if lines == 0 {
13724 EditorSettings::get_global(cx).expand_excerpt_lines
13725 } else {
13726 lines
13727 };
13728
13729 self.buffer.update(cx, |buffer, cx| {
13730 let snapshot = buffer.snapshot(cx);
13731 let mut excerpt_ids = selections
13732 .iter()
13733 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13734 .collect::<Vec<_>>();
13735 excerpt_ids.sort();
13736 excerpt_ids.dedup();
13737 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13738 })
13739 }
13740
13741 pub fn expand_excerpt(
13742 &mut self,
13743 excerpt: ExcerptId,
13744 direction: ExpandExcerptDirection,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 let current_scroll_position = self.scroll_position(cx);
13749 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13750 let mut should_scroll_up = false;
13751
13752 if direction == ExpandExcerptDirection::Down {
13753 let multi_buffer = self.buffer.read(cx);
13754 let snapshot = multi_buffer.snapshot(cx);
13755 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13756 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13757 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13758 let buffer_snapshot = buffer.read(cx).snapshot();
13759 let excerpt_end_row =
13760 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13761 let last_row = buffer_snapshot.max_point().row;
13762 let lines_below = last_row.saturating_sub(excerpt_end_row);
13763 should_scroll_up = lines_below >= lines_to_expand;
13764 }
13765 }
13766 }
13767 }
13768
13769 self.buffer.update(cx, |buffer, cx| {
13770 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13771 });
13772
13773 if should_scroll_up {
13774 let new_scroll_position =
13775 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13776 self.set_scroll_position(new_scroll_position, window, cx);
13777 }
13778 }
13779
13780 pub fn go_to_singleton_buffer_point(
13781 &mut self,
13782 point: Point,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 self.go_to_singleton_buffer_range(point..point, window, cx);
13787 }
13788
13789 pub fn go_to_singleton_buffer_range(
13790 &mut self,
13791 range: Range<Point>,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 ) {
13795 let multibuffer = self.buffer().read(cx);
13796 let Some(buffer) = multibuffer.as_singleton() else {
13797 return;
13798 };
13799 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13800 return;
13801 };
13802 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13803 return;
13804 };
13805 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13806 s.select_anchor_ranges([start..end])
13807 });
13808 }
13809
13810 pub fn go_to_diagnostic(
13811 &mut self,
13812 _: &GoToDiagnostic,
13813 window: &mut Window,
13814 cx: &mut Context<Self>,
13815 ) {
13816 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13817 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13818 }
13819
13820 pub fn go_to_prev_diagnostic(
13821 &mut self,
13822 _: &GoToPreviousDiagnostic,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13827 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13828 }
13829
13830 pub fn go_to_diagnostic_impl(
13831 &mut self,
13832 direction: Direction,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) {
13836 let buffer = self.buffer.read(cx).snapshot(cx);
13837 let selection = self.selections.newest::<usize>(cx);
13838
13839 let mut active_group_id = None;
13840 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13841 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13842 active_group_id = Some(active_group.group_id);
13843 }
13844 }
13845
13846 fn filtered(
13847 snapshot: EditorSnapshot,
13848 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13849 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13850 diagnostics
13851 .filter(|entry| entry.range.start != entry.range.end)
13852 .filter(|entry| !entry.diagnostic.is_unnecessary)
13853 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13854 }
13855
13856 let snapshot = self.snapshot(window, cx);
13857 let before = filtered(
13858 snapshot.clone(),
13859 buffer
13860 .diagnostics_in_range(0..selection.start)
13861 .filter(|entry| entry.range.start <= selection.start),
13862 );
13863 let after = filtered(
13864 snapshot,
13865 buffer
13866 .diagnostics_in_range(selection.start..buffer.len())
13867 .filter(|entry| entry.range.start >= selection.start),
13868 );
13869
13870 let mut found: Option<DiagnosticEntry<usize>> = None;
13871 if direction == Direction::Prev {
13872 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13873 {
13874 for diagnostic in prev_diagnostics.into_iter().rev() {
13875 if diagnostic.range.start != selection.start
13876 || active_group_id
13877 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13878 {
13879 found = Some(diagnostic);
13880 break 'outer;
13881 }
13882 }
13883 }
13884 } else {
13885 for diagnostic in after.chain(before) {
13886 if diagnostic.range.start != selection.start
13887 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13888 {
13889 found = Some(diagnostic);
13890 break;
13891 }
13892 }
13893 }
13894 let Some(next_diagnostic) = found else {
13895 return;
13896 };
13897
13898 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13899 return;
13900 };
13901 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13902 s.select_ranges(vec![
13903 next_diagnostic.range.start..next_diagnostic.range.start,
13904 ])
13905 });
13906 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13907 self.refresh_inline_completion(false, true, window, cx);
13908 }
13909
13910 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13911 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13912 let snapshot = self.snapshot(window, cx);
13913 let selection = self.selections.newest::<Point>(cx);
13914 self.go_to_hunk_before_or_after_position(
13915 &snapshot,
13916 selection.head(),
13917 Direction::Next,
13918 window,
13919 cx,
13920 );
13921 }
13922
13923 pub fn go_to_hunk_before_or_after_position(
13924 &mut self,
13925 snapshot: &EditorSnapshot,
13926 position: Point,
13927 direction: Direction,
13928 window: &mut Window,
13929 cx: &mut Context<Editor>,
13930 ) {
13931 let row = if direction == Direction::Next {
13932 self.hunk_after_position(snapshot, position)
13933 .map(|hunk| hunk.row_range.start)
13934 } else {
13935 self.hunk_before_position(snapshot, position)
13936 };
13937
13938 if let Some(row) = row {
13939 let destination = Point::new(row.0, 0);
13940 let autoscroll = Autoscroll::center();
13941
13942 self.unfold_ranges(&[destination..destination], false, false, cx);
13943 self.change_selections(Some(autoscroll), window, cx, |s| {
13944 s.select_ranges([destination..destination]);
13945 });
13946 }
13947 }
13948
13949 fn hunk_after_position(
13950 &mut self,
13951 snapshot: &EditorSnapshot,
13952 position: Point,
13953 ) -> Option<MultiBufferDiffHunk> {
13954 snapshot
13955 .buffer_snapshot
13956 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13957 .find(|hunk| hunk.row_range.start.0 > position.row)
13958 .or_else(|| {
13959 snapshot
13960 .buffer_snapshot
13961 .diff_hunks_in_range(Point::zero()..position)
13962 .find(|hunk| hunk.row_range.end.0 < position.row)
13963 })
13964 }
13965
13966 fn go_to_prev_hunk(
13967 &mut self,
13968 _: &GoToPreviousHunk,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13973 let snapshot = self.snapshot(window, cx);
13974 let selection = self.selections.newest::<Point>(cx);
13975 self.go_to_hunk_before_or_after_position(
13976 &snapshot,
13977 selection.head(),
13978 Direction::Prev,
13979 window,
13980 cx,
13981 );
13982 }
13983
13984 fn hunk_before_position(
13985 &mut self,
13986 snapshot: &EditorSnapshot,
13987 position: Point,
13988 ) -> Option<MultiBufferRow> {
13989 snapshot
13990 .buffer_snapshot
13991 .diff_hunk_before(position)
13992 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13993 }
13994
13995 fn go_to_next_change(
13996 &mut self,
13997 _: &GoToNextChange,
13998 window: &mut Window,
13999 cx: &mut Context<Self>,
14000 ) {
14001 if let Some(selections) = self
14002 .change_list
14003 .next_change(1, Direction::Next)
14004 .map(|s| s.to_vec())
14005 {
14006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14007 let map = s.display_map();
14008 s.select_display_ranges(selections.iter().map(|a| {
14009 let point = a.to_display_point(&map);
14010 point..point
14011 }))
14012 })
14013 }
14014 }
14015
14016 fn go_to_previous_change(
14017 &mut self,
14018 _: &GoToPreviousChange,
14019 window: &mut Window,
14020 cx: &mut Context<Self>,
14021 ) {
14022 if let Some(selections) = self
14023 .change_list
14024 .next_change(1, Direction::Prev)
14025 .map(|s| s.to_vec())
14026 {
14027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14028 let map = s.display_map();
14029 s.select_display_ranges(selections.iter().map(|a| {
14030 let point = a.to_display_point(&map);
14031 point..point
14032 }))
14033 })
14034 }
14035 }
14036
14037 fn go_to_line<T: 'static>(
14038 &mut self,
14039 position: Anchor,
14040 highlight_color: Option<Hsla>,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 let snapshot = self.snapshot(window, cx).display_snapshot;
14045 let position = position.to_point(&snapshot.buffer_snapshot);
14046 let start = snapshot
14047 .buffer_snapshot
14048 .clip_point(Point::new(position.row, 0), Bias::Left);
14049 let end = start + Point::new(1, 0);
14050 let start = snapshot.buffer_snapshot.anchor_before(start);
14051 let end = snapshot.buffer_snapshot.anchor_before(end);
14052
14053 self.highlight_rows::<T>(
14054 start..end,
14055 highlight_color
14056 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14057 Default::default(),
14058 cx,
14059 );
14060
14061 if self.buffer.read(cx).is_singleton() {
14062 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14063 }
14064 }
14065
14066 pub fn go_to_definition(
14067 &mut self,
14068 _: &GoToDefinition,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) -> Task<Result<Navigated>> {
14072 let definition =
14073 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14074 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14075 cx.spawn_in(window, async move |editor, cx| {
14076 if definition.await? == Navigated::Yes {
14077 return Ok(Navigated::Yes);
14078 }
14079 match fallback_strategy {
14080 GoToDefinitionFallback::None => Ok(Navigated::No),
14081 GoToDefinitionFallback::FindAllReferences => {
14082 match editor.update_in(cx, |editor, window, cx| {
14083 editor.find_all_references(&FindAllReferences, window, cx)
14084 })? {
14085 Some(references) => references.await,
14086 None => Ok(Navigated::No),
14087 }
14088 }
14089 }
14090 })
14091 }
14092
14093 pub fn go_to_declaration(
14094 &mut self,
14095 _: &GoToDeclaration,
14096 window: &mut Window,
14097 cx: &mut Context<Self>,
14098 ) -> Task<Result<Navigated>> {
14099 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14100 }
14101
14102 pub fn go_to_declaration_split(
14103 &mut self,
14104 _: &GoToDeclaration,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) -> Task<Result<Navigated>> {
14108 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14109 }
14110
14111 pub fn go_to_implementation(
14112 &mut self,
14113 _: &GoToImplementation,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) -> Task<Result<Navigated>> {
14117 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14118 }
14119
14120 pub fn go_to_implementation_split(
14121 &mut self,
14122 _: &GoToImplementationSplit,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) -> Task<Result<Navigated>> {
14126 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14127 }
14128
14129 pub fn go_to_type_definition(
14130 &mut self,
14131 _: &GoToTypeDefinition,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) -> Task<Result<Navigated>> {
14135 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14136 }
14137
14138 pub fn go_to_definition_split(
14139 &mut self,
14140 _: &GoToDefinitionSplit,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) -> Task<Result<Navigated>> {
14144 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14145 }
14146
14147 pub fn go_to_type_definition_split(
14148 &mut self,
14149 _: &GoToTypeDefinitionSplit,
14150 window: &mut Window,
14151 cx: &mut Context<Self>,
14152 ) -> Task<Result<Navigated>> {
14153 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14154 }
14155
14156 fn go_to_definition_of_kind(
14157 &mut self,
14158 kind: GotoDefinitionKind,
14159 split: bool,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) -> Task<Result<Navigated>> {
14163 let Some(provider) = self.semantics_provider.clone() else {
14164 return Task::ready(Ok(Navigated::No));
14165 };
14166 let head = self.selections.newest::<usize>(cx).head();
14167 let buffer = self.buffer.read(cx);
14168 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14169 text_anchor
14170 } else {
14171 return Task::ready(Ok(Navigated::No));
14172 };
14173
14174 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14175 return Task::ready(Ok(Navigated::No));
14176 };
14177
14178 cx.spawn_in(window, async move |editor, cx| {
14179 let definitions = definitions.await?;
14180 let navigated = editor
14181 .update_in(cx, |editor, window, cx| {
14182 editor.navigate_to_hover_links(
14183 Some(kind),
14184 definitions
14185 .into_iter()
14186 .filter(|location| {
14187 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14188 })
14189 .map(HoverLink::Text)
14190 .collect::<Vec<_>>(),
14191 split,
14192 window,
14193 cx,
14194 )
14195 })?
14196 .await?;
14197 anyhow::Ok(navigated)
14198 })
14199 }
14200
14201 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14202 let selection = self.selections.newest_anchor();
14203 let head = selection.head();
14204 let tail = selection.tail();
14205
14206 let Some((buffer, start_position)) =
14207 self.buffer.read(cx).text_anchor_for_position(head, cx)
14208 else {
14209 return;
14210 };
14211
14212 let end_position = if head != tail {
14213 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14214 return;
14215 };
14216 Some(pos)
14217 } else {
14218 None
14219 };
14220
14221 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14222 let url = if let Some(end_pos) = end_position {
14223 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14224 } else {
14225 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14226 };
14227
14228 if let Some(url) = url {
14229 editor.update(cx, |_, cx| {
14230 cx.open_url(&url);
14231 })
14232 } else {
14233 Ok(())
14234 }
14235 });
14236
14237 url_finder.detach();
14238 }
14239
14240 pub fn open_selected_filename(
14241 &mut self,
14242 _: &OpenSelectedFilename,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 let Some(workspace) = self.workspace() else {
14247 return;
14248 };
14249
14250 let position = self.selections.newest_anchor().head();
14251
14252 let Some((buffer, buffer_position)) =
14253 self.buffer.read(cx).text_anchor_for_position(position, cx)
14254 else {
14255 return;
14256 };
14257
14258 let project = self.project.clone();
14259
14260 cx.spawn_in(window, async move |_, cx| {
14261 let result = find_file(&buffer, project, buffer_position, cx).await;
14262
14263 if let Some((_, path)) = result {
14264 workspace
14265 .update_in(cx, |workspace, window, cx| {
14266 workspace.open_resolved_path(path, window, cx)
14267 })?
14268 .await?;
14269 }
14270 anyhow::Ok(())
14271 })
14272 .detach();
14273 }
14274
14275 pub(crate) fn navigate_to_hover_links(
14276 &mut self,
14277 kind: Option<GotoDefinitionKind>,
14278 mut definitions: Vec<HoverLink>,
14279 split: bool,
14280 window: &mut Window,
14281 cx: &mut Context<Editor>,
14282 ) -> Task<Result<Navigated>> {
14283 // If there is one definition, just open it directly
14284 if definitions.len() == 1 {
14285 let definition = definitions.pop().unwrap();
14286
14287 enum TargetTaskResult {
14288 Location(Option<Location>),
14289 AlreadyNavigated,
14290 }
14291
14292 let target_task = match definition {
14293 HoverLink::Text(link) => {
14294 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14295 }
14296 HoverLink::InlayHint(lsp_location, server_id) => {
14297 let computation =
14298 self.compute_target_location(lsp_location, server_id, window, cx);
14299 cx.background_spawn(async move {
14300 let location = computation.await?;
14301 Ok(TargetTaskResult::Location(location))
14302 })
14303 }
14304 HoverLink::Url(url) => {
14305 cx.open_url(&url);
14306 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14307 }
14308 HoverLink::File(path) => {
14309 if let Some(workspace) = self.workspace() {
14310 cx.spawn_in(window, async move |_, cx| {
14311 workspace
14312 .update_in(cx, |workspace, window, cx| {
14313 workspace.open_resolved_path(path, window, cx)
14314 })?
14315 .await
14316 .map(|_| TargetTaskResult::AlreadyNavigated)
14317 })
14318 } else {
14319 Task::ready(Ok(TargetTaskResult::Location(None)))
14320 }
14321 }
14322 };
14323 cx.spawn_in(window, async move |editor, cx| {
14324 let target = match target_task.await.context("target resolution task")? {
14325 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14326 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14327 TargetTaskResult::Location(Some(target)) => target,
14328 };
14329
14330 editor.update_in(cx, |editor, window, cx| {
14331 let Some(workspace) = editor.workspace() else {
14332 return Navigated::No;
14333 };
14334 let pane = workspace.read(cx).active_pane().clone();
14335
14336 let range = target.range.to_point(target.buffer.read(cx));
14337 let range = editor.range_for_match(&range);
14338 let range = collapse_multiline_range(range);
14339
14340 if !split
14341 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14342 {
14343 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14344 } else {
14345 window.defer(cx, move |window, cx| {
14346 let target_editor: Entity<Self> =
14347 workspace.update(cx, |workspace, cx| {
14348 let pane = if split {
14349 workspace.adjacent_pane(window, cx)
14350 } else {
14351 workspace.active_pane().clone()
14352 };
14353
14354 workspace.open_project_item(
14355 pane,
14356 target.buffer.clone(),
14357 true,
14358 true,
14359 window,
14360 cx,
14361 )
14362 });
14363 target_editor.update(cx, |target_editor, cx| {
14364 // When selecting a definition in a different buffer, disable the nav history
14365 // to avoid creating a history entry at the previous cursor location.
14366 pane.update(cx, |pane, _| pane.disable_history());
14367 target_editor.go_to_singleton_buffer_range(range, window, cx);
14368 pane.update(cx, |pane, _| pane.enable_history());
14369 });
14370 });
14371 }
14372 Navigated::Yes
14373 })
14374 })
14375 } else if !definitions.is_empty() {
14376 cx.spawn_in(window, async move |editor, cx| {
14377 let (title, location_tasks, workspace) = editor
14378 .update_in(cx, |editor, window, cx| {
14379 let tab_kind = match kind {
14380 Some(GotoDefinitionKind::Implementation) => "Implementations",
14381 _ => "Definitions",
14382 };
14383 let title = definitions
14384 .iter()
14385 .find_map(|definition| match definition {
14386 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14387 let buffer = origin.buffer.read(cx);
14388 format!(
14389 "{} for {}",
14390 tab_kind,
14391 buffer
14392 .text_for_range(origin.range.clone())
14393 .collect::<String>()
14394 )
14395 }),
14396 HoverLink::InlayHint(_, _) => None,
14397 HoverLink::Url(_) => None,
14398 HoverLink::File(_) => None,
14399 })
14400 .unwrap_or(tab_kind.to_string());
14401 let location_tasks = definitions
14402 .into_iter()
14403 .map(|definition| match definition {
14404 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14405 HoverLink::InlayHint(lsp_location, server_id) => editor
14406 .compute_target_location(lsp_location, server_id, window, cx),
14407 HoverLink::Url(_) => Task::ready(Ok(None)),
14408 HoverLink::File(_) => Task::ready(Ok(None)),
14409 })
14410 .collect::<Vec<_>>();
14411 (title, location_tasks, editor.workspace().clone())
14412 })
14413 .context("location tasks preparation")?;
14414
14415 let locations = future::join_all(location_tasks)
14416 .await
14417 .into_iter()
14418 .filter_map(|location| location.transpose())
14419 .collect::<Result<_>>()
14420 .context("location tasks")?;
14421
14422 let Some(workspace) = workspace else {
14423 return Ok(Navigated::No);
14424 };
14425 let opened = workspace
14426 .update_in(cx, |workspace, window, cx| {
14427 Self::open_locations_in_multibuffer(
14428 workspace,
14429 locations,
14430 title,
14431 split,
14432 MultibufferSelectionMode::First,
14433 window,
14434 cx,
14435 )
14436 })
14437 .ok();
14438
14439 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14440 })
14441 } else {
14442 Task::ready(Ok(Navigated::No))
14443 }
14444 }
14445
14446 fn compute_target_location(
14447 &self,
14448 lsp_location: lsp::Location,
14449 server_id: LanguageServerId,
14450 window: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) -> Task<anyhow::Result<Option<Location>>> {
14453 let Some(project) = self.project.clone() else {
14454 return Task::ready(Ok(None));
14455 };
14456
14457 cx.spawn_in(window, async move |editor, cx| {
14458 let location_task = editor.update(cx, |_, cx| {
14459 project.update(cx, |project, cx| {
14460 let language_server_name = project
14461 .language_server_statuses(cx)
14462 .find(|(id, _)| server_id == *id)
14463 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14464 language_server_name.map(|language_server_name| {
14465 project.open_local_buffer_via_lsp(
14466 lsp_location.uri.clone(),
14467 server_id,
14468 language_server_name,
14469 cx,
14470 )
14471 })
14472 })
14473 })?;
14474 let location = match location_task {
14475 Some(task) => Some({
14476 let target_buffer_handle = task.await.context("open local buffer")?;
14477 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14478 let target_start = target_buffer
14479 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14480 let target_end = target_buffer
14481 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14482 target_buffer.anchor_after(target_start)
14483 ..target_buffer.anchor_before(target_end)
14484 })?;
14485 Location {
14486 buffer: target_buffer_handle,
14487 range,
14488 }
14489 }),
14490 None => None,
14491 };
14492 Ok(location)
14493 })
14494 }
14495
14496 pub fn find_all_references(
14497 &mut self,
14498 _: &FindAllReferences,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) -> Option<Task<Result<Navigated>>> {
14502 let selection = self.selections.newest::<usize>(cx);
14503 let multi_buffer = self.buffer.read(cx);
14504 let head = selection.head();
14505
14506 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14507 let head_anchor = multi_buffer_snapshot.anchor_at(
14508 head,
14509 if head < selection.tail() {
14510 Bias::Right
14511 } else {
14512 Bias::Left
14513 },
14514 );
14515
14516 match self
14517 .find_all_references_task_sources
14518 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14519 {
14520 Ok(_) => {
14521 log::info!(
14522 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14523 );
14524 return None;
14525 }
14526 Err(i) => {
14527 self.find_all_references_task_sources.insert(i, head_anchor);
14528 }
14529 }
14530
14531 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14532 let workspace = self.workspace()?;
14533 let project = workspace.read(cx).project().clone();
14534 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14535 Some(cx.spawn_in(window, async move |editor, cx| {
14536 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14537 if let Ok(i) = editor
14538 .find_all_references_task_sources
14539 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14540 {
14541 editor.find_all_references_task_sources.remove(i);
14542 }
14543 });
14544
14545 let locations = references.await?;
14546 if locations.is_empty() {
14547 return anyhow::Ok(Navigated::No);
14548 }
14549
14550 workspace.update_in(cx, |workspace, window, cx| {
14551 let title = locations
14552 .first()
14553 .as_ref()
14554 .map(|location| {
14555 let buffer = location.buffer.read(cx);
14556 format!(
14557 "References to `{}`",
14558 buffer
14559 .text_for_range(location.range.clone())
14560 .collect::<String>()
14561 )
14562 })
14563 .unwrap();
14564 Self::open_locations_in_multibuffer(
14565 workspace,
14566 locations,
14567 title,
14568 false,
14569 MultibufferSelectionMode::First,
14570 window,
14571 cx,
14572 );
14573 Navigated::Yes
14574 })
14575 }))
14576 }
14577
14578 /// Opens a multibuffer with the given project locations in it
14579 pub fn open_locations_in_multibuffer(
14580 workspace: &mut Workspace,
14581 mut locations: Vec<Location>,
14582 title: String,
14583 split: bool,
14584 multibuffer_selection_mode: MultibufferSelectionMode,
14585 window: &mut Window,
14586 cx: &mut Context<Workspace>,
14587 ) {
14588 // If there are multiple definitions, open them in a multibuffer
14589 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14590 let mut locations = locations.into_iter().peekable();
14591 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14592 let capability = workspace.project().read(cx).capability();
14593
14594 let excerpt_buffer = cx.new(|cx| {
14595 let mut multibuffer = MultiBuffer::new(capability);
14596 while let Some(location) = locations.next() {
14597 let buffer = location.buffer.read(cx);
14598 let mut ranges_for_buffer = Vec::new();
14599 let range = location.range.to_point(buffer);
14600 ranges_for_buffer.push(range.clone());
14601
14602 while let Some(next_location) = locations.peek() {
14603 if next_location.buffer == location.buffer {
14604 ranges_for_buffer.push(next_location.range.to_point(buffer));
14605 locations.next();
14606 } else {
14607 break;
14608 }
14609 }
14610
14611 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14612 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14613 PathKey::for_buffer(&location.buffer, cx),
14614 location.buffer.clone(),
14615 ranges_for_buffer,
14616 DEFAULT_MULTIBUFFER_CONTEXT,
14617 cx,
14618 );
14619 ranges.extend(new_ranges)
14620 }
14621
14622 multibuffer.with_title(title)
14623 });
14624
14625 let editor = cx.new(|cx| {
14626 Editor::for_multibuffer(
14627 excerpt_buffer,
14628 Some(workspace.project().clone()),
14629 window,
14630 cx,
14631 )
14632 });
14633 editor.update(cx, |editor, cx| {
14634 match multibuffer_selection_mode {
14635 MultibufferSelectionMode::First => {
14636 if let Some(first_range) = ranges.first() {
14637 editor.change_selections(None, window, cx, |selections| {
14638 selections.clear_disjoint();
14639 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14640 });
14641 }
14642 editor.highlight_background::<Self>(
14643 &ranges,
14644 |theme| theme.editor_highlighted_line_background,
14645 cx,
14646 );
14647 }
14648 MultibufferSelectionMode::All => {
14649 editor.change_selections(None, window, cx, |selections| {
14650 selections.clear_disjoint();
14651 selections.select_anchor_ranges(ranges);
14652 });
14653 }
14654 }
14655 editor.register_buffers_with_language_servers(cx);
14656 });
14657
14658 let item = Box::new(editor);
14659 let item_id = item.item_id();
14660
14661 if split {
14662 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14663 } else {
14664 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14665 let (preview_item_id, preview_item_idx) =
14666 workspace.active_pane().update(cx, |pane, _| {
14667 (pane.preview_item_id(), pane.preview_item_idx())
14668 });
14669
14670 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14671
14672 if let Some(preview_item_id) = preview_item_id {
14673 workspace.active_pane().update(cx, |pane, cx| {
14674 pane.remove_item(preview_item_id, false, false, window, cx);
14675 });
14676 }
14677 } else {
14678 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14679 }
14680 }
14681 workspace.active_pane().update(cx, |pane, cx| {
14682 pane.set_preview_item_id(Some(item_id), cx);
14683 });
14684 }
14685
14686 pub fn rename(
14687 &mut self,
14688 _: &Rename,
14689 window: &mut Window,
14690 cx: &mut Context<Self>,
14691 ) -> Option<Task<Result<()>>> {
14692 use language::ToOffset as _;
14693
14694 let provider = self.semantics_provider.clone()?;
14695 let selection = self.selections.newest_anchor().clone();
14696 let (cursor_buffer, cursor_buffer_position) = self
14697 .buffer
14698 .read(cx)
14699 .text_anchor_for_position(selection.head(), cx)?;
14700 let (tail_buffer, cursor_buffer_position_end) = self
14701 .buffer
14702 .read(cx)
14703 .text_anchor_for_position(selection.tail(), cx)?;
14704 if tail_buffer != cursor_buffer {
14705 return None;
14706 }
14707
14708 let snapshot = cursor_buffer.read(cx).snapshot();
14709 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14710 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14711 let prepare_rename = provider
14712 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14713 .unwrap_or_else(|| Task::ready(Ok(None)));
14714 drop(snapshot);
14715
14716 Some(cx.spawn_in(window, async move |this, cx| {
14717 let rename_range = if let Some(range) = prepare_rename.await? {
14718 Some(range)
14719 } else {
14720 this.update(cx, |this, cx| {
14721 let buffer = this.buffer.read(cx).snapshot(cx);
14722 let mut buffer_highlights = this
14723 .document_highlights_for_position(selection.head(), &buffer)
14724 .filter(|highlight| {
14725 highlight.start.excerpt_id == selection.head().excerpt_id
14726 && highlight.end.excerpt_id == selection.head().excerpt_id
14727 });
14728 buffer_highlights
14729 .next()
14730 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14731 })?
14732 };
14733 if let Some(rename_range) = rename_range {
14734 this.update_in(cx, |this, window, cx| {
14735 let snapshot = cursor_buffer.read(cx).snapshot();
14736 let rename_buffer_range = rename_range.to_offset(&snapshot);
14737 let cursor_offset_in_rename_range =
14738 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14739 let cursor_offset_in_rename_range_end =
14740 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14741
14742 this.take_rename(false, window, cx);
14743 let buffer = this.buffer.read(cx).read(cx);
14744 let cursor_offset = selection.head().to_offset(&buffer);
14745 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14746 let rename_end = rename_start + rename_buffer_range.len();
14747 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14748 let mut old_highlight_id = None;
14749 let old_name: Arc<str> = buffer
14750 .chunks(rename_start..rename_end, true)
14751 .map(|chunk| {
14752 if old_highlight_id.is_none() {
14753 old_highlight_id = chunk.syntax_highlight_id;
14754 }
14755 chunk.text
14756 })
14757 .collect::<String>()
14758 .into();
14759
14760 drop(buffer);
14761
14762 // Position the selection in the rename editor so that it matches the current selection.
14763 this.show_local_selections = false;
14764 let rename_editor = cx.new(|cx| {
14765 let mut editor = Editor::single_line(window, cx);
14766 editor.buffer.update(cx, |buffer, cx| {
14767 buffer.edit([(0..0, old_name.clone())], None, cx)
14768 });
14769 let rename_selection_range = match cursor_offset_in_rename_range
14770 .cmp(&cursor_offset_in_rename_range_end)
14771 {
14772 Ordering::Equal => {
14773 editor.select_all(&SelectAll, window, cx);
14774 return editor;
14775 }
14776 Ordering::Less => {
14777 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14778 }
14779 Ordering::Greater => {
14780 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14781 }
14782 };
14783 if rename_selection_range.end > old_name.len() {
14784 editor.select_all(&SelectAll, window, cx);
14785 } else {
14786 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14787 s.select_ranges([rename_selection_range]);
14788 });
14789 }
14790 editor
14791 });
14792 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14793 if e == &EditorEvent::Focused {
14794 cx.emit(EditorEvent::FocusedIn)
14795 }
14796 })
14797 .detach();
14798
14799 let write_highlights =
14800 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14801 let read_highlights =
14802 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14803 let ranges = write_highlights
14804 .iter()
14805 .flat_map(|(_, ranges)| ranges.iter())
14806 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14807 .cloned()
14808 .collect();
14809
14810 this.highlight_text::<Rename>(
14811 ranges,
14812 HighlightStyle {
14813 fade_out: Some(0.6),
14814 ..Default::default()
14815 },
14816 cx,
14817 );
14818 let rename_focus_handle = rename_editor.focus_handle(cx);
14819 window.focus(&rename_focus_handle);
14820 let block_id = this.insert_blocks(
14821 [BlockProperties {
14822 style: BlockStyle::Flex,
14823 placement: BlockPlacement::Below(range.start),
14824 height: Some(1),
14825 render: Arc::new({
14826 let rename_editor = rename_editor.clone();
14827 move |cx: &mut BlockContext| {
14828 let mut text_style = cx.editor_style.text.clone();
14829 if let Some(highlight_style) = old_highlight_id
14830 .and_then(|h| h.style(&cx.editor_style.syntax))
14831 {
14832 text_style = text_style.highlight(highlight_style);
14833 }
14834 div()
14835 .block_mouse_down()
14836 .pl(cx.anchor_x)
14837 .child(EditorElement::new(
14838 &rename_editor,
14839 EditorStyle {
14840 background: cx.theme().system().transparent,
14841 local_player: cx.editor_style.local_player,
14842 text: text_style,
14843 scrollbar_width: cx.editor_style.scrollbar_width,
14844 syntax: cx.editor_style.syntax.clone(),
14845 status: cx.editor_style.status.clone(),
14846 inlay_hints_style: HighlightStyle {
14847 font_weight: Some(FontWeight::BOLD),
14848 ..make_inlay_hints_style(cx.app)
14849 },
14850 inline_completion_styles: make_suggestion_styles(
14851 cx.app,
14852 ),
14853 ..EditorStyle::default()
14854 },
14855 ))
14856 .into_any_element()
14857 }
14858 }),
14859 priority: 0,
14860 render_in_minimap: true,
14861 }],
14862 Some(Autoscroll::fit()),
14863 cx,
14864 )[0];
14865 this.pending_rename = Some(RenameState {
14866 range,
14867 old_name,
14868 editor: rename_editor,
14869 block_id,
14870 });
14871 })?;
14872 }
14873
14874 Ok(())
14875 }))
14876 }
14877
14878 pub fn confirm_rename(
14879 &mut self,
14880 _: &ConfirmRename,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) -> Option<Task<Result<()>>> {
14884 let rename = self.take_rename(false, window, cx)?;
14885 let workspace = self.workspace()?.downgrade();
14886 let (buffer, start) = self
14887 .buffer
14888 .read(cx)
14889 .text_anchor_for_position(rename.range.start, cx)?;
14890 let (end_buffer, _) = self
14891 .buffer
14892 .read(cx)
14893 .text_anchor_for_position(rename.range.end, cx)?;
14894 if buffer != end_buffer {
14895 return None;
14896 }
14897
14898 let old_name = rename.old_name;
14899 let new_name = rename.editor.read(cx).text(cx);
14900
14901 let rename = self.semantics_provider.as_ref()?.perform_rename(
14902 &buffer,
14903 start,
14904 new_name.clone(),
14905 cx,
14906 )?;
14907
14908 Some(cx.spawn_in(window, async move |editor, cx| {
14909 let project_transaction = rename.await?;
14910 Self::open_project_transaction(
14911 &editor,
14912 workspace,
14913 project_transaction,
14914 format!("Rename: {} → {}", old_name, new_name),
14915 cx,
14916 )
14917 .await?;
14918
14919 editor.update(cx, |editor, cx| {
14920 editor.refresh_document_highlights(cx);
14921 })?;
14922 Ok(())
14923 }))
14924 }
14925
14926 fn take_rename(
14927 &mut self,
14928 moving_cursor: bool,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) -> Option<RenameState> {
14932 let rename = self.pending_rename.take()?;
14933 if rename.editor.focus_handle(cx).is_focused(window) {
14934 window.focus(&self.focus_handle);
14935 }
14936
14937 self.remove_blocks(
14938 [rename.block_id].into_iter().collect(),
14939 Some(Autoscroll::fit()),
14940 cx,
14941 );
14942 self.clear_highlights::<Rename>(cx);
14943 self.show_local_selections = true;
14944
14945 if moving_cursor {
14946 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14947 editor.selections.newest::<usize>(cx).head()
14948 });
14949
14950 // Update the selection to match the position of the selection inside
14951 // the rename editor.
14952 let snapshot = self.buffer.read(cx).read(cx);
14953 let rename_range = rename.range.to_offset(&snapshot);
14954 let cursor_in_editor = snapshot
14955 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14956 .min(rename_range.end);
14957 drop(snapshot);
14958
14959 self.change_selections(None, window, cx, |s| {
14960 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14961 });
14962 } else {
14963 self.refresh_document_highlights(cx);
14964 }
14965
14966 Some(rename)
14967 }
14968
14969 pub fn pending_rename(&self) -> Option<&RenameState> {
14970 self.pending_rename.as_ref()
14971 }
14972
14973 fn format(
14974 &mut self,
14975 _: &Format,
14976 window: &mut Window,
14977 cx: &mut Context<Self>,
14978 ) -> Option<Task<Result<()>>> {
14979 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14980
14981 let project = match &self.project {
14982 Some(project) => project.clone(),
14983 None => return None,
14984 };
14985
14986 Some(self.perform_format(
14987 project,
14988 FormatTrigger::Manual,
14989 FormatTarget::Buffers,
14990 window,
14991 cx,
14992 ))
14993 }
14994
14995 fn format_selections(
14996 &mut self,
14997 _: &FormatSelections,
14998 window: &mut Window,
14999 cx: &mut Context<Self>,
15000 ) -> Option<Task<Result<()>>> {
15001 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15002
15003 let project = match &self.project {
15004 Some(project) => project.clone(),
15005 None => return None,
15006 };
15007
15008 let ranges = self
15009 .selections
15010 .all_adjusted(cx)
15011 .into_iter()
15012 .map(|selection| selection.range())
15013 .collect_vec();
15014
15015 Some(self.perform_format(
15016 project,
15017 FormatTrigger::Manual,
15018 FormatTarget::Ranges(ranges),
15019 window,
15020 cx,
15021 ))
15022 }
15023
15024 fn perform_format(
15025 &mut self,
15026 project: Entity<Project>,
15027 trigger: FormatTrigger,
15028 target: FormatTarget,
15029 window: &mut Window,
15030 cx: &mut Context<Self>,
15031 ) -> Task<Result<()>> {
15032 let buffer = self.buffer.clone();
15033 let (buffers, target) = match target {
15034 FormatTarget::Buffers => {
15035 let mut buffers = buffer.read(cx).all_buffers();
15036 if trigger == FormatTrigger::Save {
15037 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15038 }
15039 (buffers, LspFormatTarget::Buffers)
15040 }
15041 FormatTarget::Ranges(selection_ranges) => {
15042 let multi_buffer = buffer.read(cx);
15043 let snapshot = multi_buffer.read(cx);
15044 let mut buffers = HashSet::default();
15045 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15046 BTreeMap::new();
15047 for selection_range in selection_ranges {
15048 for (buffer, buffer_range, _) in
15049 snapshot.range_to_buffer_ranges(selection_range)
15050 {
15051 let buffer_id = buffer.remote_id();
15052 let start = buffer.anchor_before(buffer_range.start);
15053 let end = buffer.anchor_after(buffer_range.end);
15054 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15055 buffer_id_to_ranges
15056 .entry(buffer_id)
15057 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15058 .or_insert_with(|| vec![start..end]);
15059 }
15060 }
15061 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15062 }
15063 };
15064
15065 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15066 let selections_prev = transaction_id_prev
15067 .and_then(|transaction_id_prev| {
15068 // default to selections as they were after the last edit, if we have them,
15069 // instead of how they are now.
15070 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15071 // will take you back to where you made the last edit, instead of staying where you scrolled
15072 self.selection_history
15073 .transaction(transaction_id_prev)
15074 .map(|t| t.0.clone())
15075 })
15076 .unwrap_or_else(|| {
15077 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15078 self.selections.disjoint_anchors()
15079 });
15080
15081 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15082 let format = project.update(cx, |project, cx| {
15083 project.format(buffers, target, true, trigger, cx)
15084 });
15085
15086 cx.spawn_in(window, async move |editor, cx| {
15087 let transaction = futures::select_biased! {
15088 transaction = format.log_err().fuse() => transaction,
15089 () = timeout => {
15090 log::warn!("timed out waiting for formatting");
15091 None
15092 }
15093 };
15094
15095 buffer
15096 .update(cx, |buffer, cx| {
15097 if let Some(transaction) = transaction {
15098 if !buffer.is_singleton() {
15099 buffer.push_transaction(&transaction.0, cx);
15100 }
15101 }
15102 cx.notify();
15103 })
15104 .ok();
15105
15106 if let Some(transaction_id_now) =
15107 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15108 {
15109 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15110 if has_new_transaction {
15111 _ = editor.update(cx, |editor, _| {
15112 editor
15113 .selection_history
15114 .insert_transaction(transaction_id_now, selections_prev);
15115 });
15116 }
15117 }
15118
15119 Ok(())
15120 })
15121 }
15122
15123 fn organize_imports(
15124 &mut self,
15125 _: &OrganizeImports,
15126 window: &mut Window,
15127 cx: &mut Context<Self>,
15128 ) -> Option<Task<Result<()>>> {
15129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15130 let project = match &self.project {
15131 Some(project) => project.clone(),
15132 None => return None,
15133 };
15134 Some(self.perform_code_action_kind(
15135 project,
15136 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15137 window,
15138 cx,
15139 ))
15140 }
15141
15142 fn perform_code_action_kind(
15143 &mut self,
15144 project: Entity<Project>,
15145 kind: CodeActionKind,
15146 window: &mut Window,
15147 cx: &mut Context<Self>,
15148 ) -> Task<Result<()>> {
15149 let buffer = self.buffer.clone();
15150 let buffers = buffer.read(cx).all_buffers();
15151 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15152 let apply_action = project.update(cx, |project, cx| {
15153 project.apply_code_action_kind(buffers, kind, true, cx)
15154 });
15155 cx.spawn_in(window, async move |_, cx| {
15156 let transaction = futures::select_biased! {
15157 () = timeout => {
15158 log::warn!("timed out waiting for executing code action");
15159 None
15160 }
15161 transaction = apply_action.log_err().fuse() => transaction,
15162 };
15163 buffer
15164 .update(cx, |buffer, cx| {
15165 // check if we need this
15166 if let Some(transaction) = transaction {
15167 if !buffer.is_singleton() {
15168 buffer.push_transaction(&transaction.0, cx);
15169 }
15170 }
15171 cx.notify();
15172 })
15173 .ok();
15174 Ok(())
15175 })
15176 }
15177
15178 fn restart_language_server(
15179 &mut self,
15180 _: &RestartLanguageServer,
15181 _: &mut Window,
15182 cx: &mut Context<Self>,
15183 ) {
15184 if let Some(project) = self.project.clone() {
15185 self.buffer.update(cx, |multi_buffer, cx| {
15186 project.update(cx, |project, cx| {
15187 project.restart_language_servers_for_buffers(
15188 multi_buffer.all_buffers().into_iter().collect(),
15189 cx,
15190 );
15191 });
15192 })
15193 }
15194 }
15195
15196 fn stop_language_server(
15197 &mut self,
15198 _: &StopLanguageServer,
15199 _: &mut Window,
15200 cx: &mut Context<Self>,
15201 ) {
15202 if let Some(project) = self.project.clone() {
15203 self.buffer.update(cx, |multi_buffer, cx| {
15204 project.update(cx, |project, cx| {
15205 project.stop_language_servers_for_buffers(
15206 multi_buffer.all_buffers().into_iter().collect(),
15207 cx,
15208 );
15209 cx.emit(project::Event::RefreshInlayHints);
15210 });
15211 });
15212 }
15213 }
15214
15215 fn cancel_language_server_work(
15216 workspace: &mut Workspace,
15217 _: &actions::CancelLanguageServerWork,
15218 _: &mut Window,
15219 cx: &mut Context<Workspace>,
15220 ) {
15221 let project = workspace.project();
15222 let buffers = workspace
15223 .active_item(cx)
15224 .and_then(|item| item.act_as::<Editor>(cx))
15225 .map_or(HashSet::default(), |editor| {
15226 editor.read(cx).buffer.read(cx).all_buffers()
15227 });
15228 project.update(cx, |project, cx| {
15229 project.cancel_language_server_work_for_buffers(buffers, cx);
15230 });
15231 }
15232
15233 fn show_character_palette(
15234 &mut self,
15235 _: &ShowCharacterPalette,
15236 window: &mut Window,
15237 _: &mut Context<Self>,
15238 ) {
15239 window.show_character_palette();
15240 }
15241
15242 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15243 if self.mode.is_minimap() {
15244 return;
15245 }
15246
15247 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15248 let buffer = self.buffer.read(cx).snapshot(cx);
15249 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15250 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15251 let is_valid = buffer
15252 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15253 .any(|entry| {
15254 entry.diagnostic.is_primary
15255 && !entry.range.is_empty()
15256 && entry.range.start == primary_range_start
15257 && entry.diagnostic.message == active_diagnostics.active_message
15258 });
15259
15260 if !is_valid {
15261 self.dismiss_diagnostics(cx);
15262 }
15263 }
15264 }
15265
15266 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15267 match &self.active_diagnostics {
15268 ActiveDiagnostic::Group(group) => Some(group),
15269 _ => None,
15270 }
15271 }
15272
15273 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15274 self.dismiss_diagnostics(cx);
15275 self.active_diagnostics = ActiveDiagnostic::All;
15276 }
15277
15278 fn activate_diagnostics(
15279 &mut self,
15280 buffer_id: BufferId,
15281 diagnostic: DiagnosticEntry<usize>,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) {
15285 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15286 return;
15287 }
15288 self.dismiss_diagnostics(cx);
15289 let snapshot = self.snapshot(window, cx);
15290 let buffer = self.buffer.read(cx).snapshot(cx);
15291 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15292 return;
15293 };
15294
15295 let diagnostic_group = buffer
15296 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15297 .collect::<Vec<_>>();
15298
15299 let blocks =
15300 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15301
15302 let blocks = self.display_map.update(cx, |display_map, cx| {
15303 display_map.insert_blocks(blocks, cx).into_iter().collect()
15304 });
15305 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15306 active_range: buffer.anchor_before(diagnostic.range.start)
15307 ..buffer.anchor_after(diagnostic.range.end),
15308 active_message: diagnostic.diagnostic.message.clone(),
15309 group_id: diagnostic.diagnostic.group_id,
15310 blocks,
15311 });
15312 cx.notify();
15313 }
15314
15315 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15316 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15317 return;
15318 };
15319
15320 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15321 if let ActiveDiagnostic::Group(group) = prev {
15322 self.display_map.update(cx, |display_map, cx| {
15323 display_map.remove_blocks(group.blocks, cx);
15324 });
15325 cx.notify();
15326 }
15327 }
15328
15329 /// Disable inline diagnostics rendering for this editor.
15330 pub fn disable_inline_diagnostics(&mut self) {
15331 self.inline_diagnostics_enabled = false;
15332 self.inline_diagnostics_update = Task::ready(());
15333 self.inline_diagnostics.clear();
15334 }
15335
15336 pub fn diagnostics_enabled(&self) -> bool {
15337 self.mode.is_full()
15338 }
15339
15340 pub fn inline_diagnostics_enabled(&self) -> bool {
15341 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15342 }
15343
15344 pub fn show_inline_diagnostics(&self) -> bool {
15345 self.show_inline_diagnostics
15346 }
15347
15348 pub fn toggle_inline_diagnostics(
15349 &mut self,
15350 _: &ToggleInlineDiagnostics,
15351 window: &mut Window,
15352 cx: &mut Context<Editor>,
15353 ) {
15354 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15355 self.refresh_inline_diagnostics(false, window, cx);
15356 }
15357
15358 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15359 self.diagnostics_max_severity = severity;
15360 self.display_map.update(cx, |display_map, _| {
15361 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15362 });
15363 }
15364
15365 pub fn toggle_diagnostics(
15366 &mut self,
15367 _: &ToggleDiagnostics,
15368 window: &mut Window,
15369 cx: &mut Context<Editor>,
15370 ) {
15371 if !self.diagnostics_enabled() {
15372 return;
15373 }
15374
15375 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15376 EditorSettings::get_global(cx)
15377 .diagnostics_max_severity
15378 .filter(|severity| severity != &DiagnosticSeverity::Off)
15379 .unwrap_or(DiagnosticSeverity::Hint)
15380 } else {
15381 DiagnosticSeverity::Off
15382 };
15383 self.set_max_diagnostics_severity(new_severity, cx);
15384 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15385 self.active_diagnostics = ActiveDiagnostic::None;
15386 self.inline_diagnostics_update = Task::ready(());
15387 self.inline_diagnostics.clear();
15388 } else {
15389 self.refresh_inline_diagnostics(false, window, cx);
15390 }
15391
15392 cx.notify();
15393 }
15394
15395 pub fn toggle_minimap(
15396 &mut self,
15397 _: &ToggleMinimap,
15398 window: &mut Window,
15399 cx: &mut Context<Editor>,
15400 ) {
15401 if self.supports_minimap(cx) {
15402 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15403 }
15404 }
15405
15406 fn refresh_inline_diagnostics(
15407 &mut self,
15408 debounce: bool,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 let max_severity = ProjectSettings::get_global(cx)
15413 .diagnostics
15414 .inline
15415 .max_severity
15416 .unwrap_or(self.diagnostics_max_severity);
15417
15418 if self.mode.is_minimap()
15419 || !self.inline_diagnostics_enabled()
15420 || !self.show_inline_diagnostics
15421 || max_severity == DiagnosticSeverity::Off
15422 {
15423 self.inline_diagnostics_update = Task::ready(());
15424 self.inline_diagnostics.clear();
15425 return;
15426 }
15427
15428 let debounce_ms = ProjectSettings::get_global(cx)
15429 .diagnostics
15430 .inline
15431 .update_debounce_ms;
15432 let debounce = if debounce && debounce_ms > 0 {
15433 Some(Duration::from_millis(debounce_ms))
15434 } else {
15435 None
15436 };
15437 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15438 let editor = editor.upgrade().unwrap();
15439
15440 if let Some(debounce) = debounce {
15441 cx.background_executor().timer(debounce).await;
15442 }
15443 let Some(snapshot) = editor
15444 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15445 .ok()
15446 else {
15447 return;
15448 };
15449
15450 let new_inline_diagnostics = cx
15451 .background_spawn(async move {
15452 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15453 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15454 let message = diagnostic_entry
15455 .diagnostic
15456 .message
15457 .split_once('\n')
15458 .map(|(line, _)| line)
15459 .map(SharedString::new)
15460 .unwrap_or_else(|| {
15461 SharedString::from(diagnostic_entry.diagnostic.message)
15462 });
15463 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15464 let (Ok(i) | Err(i)) = inline_diagnostics
15465 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15466 inline_diagnostics.insert(
15467 i,
15468 (
15469 start_anchor,
15470 InlineDiagnostic {
15471 message,
15472 group_id: diagnostic_entry.diagnostic.group_id,
15473 start: diagnostic_entry.range.start.to_point(&snapshot),
15474 is_primary: diagnostic_entry.diagnostic.is_primary,
15475 severity: diagnostic_entry.diagnostic.severity,
15476 },
15477 ),
15478 );
15479 }
15480 inline_diagnostics
15481 })
15482 .await;
15483
15484 editor
15485 .update(cx, |editor, cx| {
15486 editor.inline_diagnostics = new_inline_diagnostics;
15487 cx.notify();
15488 })
15489 .ok();
15490 });
15491 }
15492
15493 pub fn set_selections_from_remote(
15494 &mut self,
15495 selections: Vec<Selection<Anchor>>,
15496 pending_selection: Option<Selection<Anchor>>,
15497 window: &mut Window,
15498 cx: &mut Context<Self>,
15499 ) {
15500 let old_cursor_position = self.selections.newest_anchor().head();
15501 self.selections.change_with(cx, |s| {
15502 s.select_anchors(selections);
15503 if let Some(pending_selection) = pending_selection {
15504 s.set_pending(pending_selection, SelectMode::Character);
15505 } else {
15506 s.clear_pending();
15507 }
15508 });
15509 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15510 }
15511
15512 fn push_to_selection_history(&mut self) {
15513 self.selection_history.push(SelectionHistoryEntry {
15514 selections: self.selections.disjoint_anchors(),
15515 select_next_state: self.select_next_state.clone(),
15516 select_prev_state: self.select_prev_state.clone(),
15517 add_selections_state: self.add_selections_state.clone(),
15518 });
15519 }
15520
15521 pub fn transact(
15522 &mut self,
15523 window: &mut Window,
15524 cx: &mut Context<Self>,
15525 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15526 ) -> Option<TransactionId> {
15527 self.start_transaction_at(Instant::now(), window, cx);
15528 update(self, window, cx);
15529 self.end_transaction_at(Instant::now(), cx)
15530 }
15531
15532 pub fn start_transaction_at(
15533 &mut self,
15534 now: Instant,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) {
15538 self.end_selection(window, cx);
15539 if let Some(tx_id) = self
15540 .buffer
15541 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15542 {
15543 self.selection_history
15544 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15545 cx.emit(EditorEvent::TransactionBegun {
15546 transaction_id: tx_id,
15547 })
15548 }
15549 }
15550
15551 pub fn end_transaction_at(
15552 &mut self,
15553 now: Instant,
15554 cx: &mut Context<Self>,
15555 ) -> Option<TransactionId> {
15556 if let Some(transaction_id) = self
15557 .buffer
15558 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15559 {
15560 if let Some((_, end_selections)) =
15561 self.selection_history.transaction_mut(transaction_id)
15562 {
15563 *end_selections = Some(self.selections.disjoint_anchors());
15564 } else {
15565 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15566 }
15567
15568 cx.emit(EditorEvent::Edited { transaction_id });
15569 Some(transaction_id)
15570 } else {
15571 None
15572 }
15573 }
15574
15575 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15576 if self.selection_mark_mode {
15577 self.change_selections(None, window, cx, |s| {
15578 s.move_with(|_, sel| {
15579 sel.collapse_to(sel.head(), SelectionGoal::None);
15580 });
15581 })
15582 }
15583 self.selection_mark_mode = true;
15584 cx.notify();
15585 }
15586
15587 pub fn swap_selection_ends(
15588 &mut self,
15589 _: &actions::SwapSelectionEnds,
15590 window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) {
15593 self.change_selections(None, window, cx, |s| {
15594 s.move_with(|_, sel| {
15595 if sel.start != sel.end {
15596 sel.reversed = !sel.reversed
15597 }
15598 });
15599 });
15600 self.request_autoscroll(Autoscroll::newest(), cx);
15601 cx.notify();
15602 }
15603
15604 pub fn toggle_fold(
15605 &mut self,
15606 _: &actions::ToggleFold,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 if self.is_singleton(cx) {
15611 let selection = self.selections.newest::<Point>(cx);
15612
15613 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15614 let range = if selection.is_empty() {
15615 let point = selection.head().to_display_point(&display_map);
15616 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15617 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15618 .to_point(&display_map);
15619 start..end
15620 } else {
15621 selection.range()
15622 };
15623 if display_map.folds_in_range(range).next().is_some() {
15624 self.unfold_lines(&Default::default(), window, cx)
15625 } else {
15626 self.fold(&Default::default(), window, cx)
15627 }
15628 } else {
15629 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15630 let buffer_ids: HashSet<_> = self
15631 .selections
15632 .disjoint_anchor_ranges()
15633 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15634 .collect();
15635
15636 let should_unfold = buffer_ids
15637 .iter()
15638 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15639
15640 for buffer_id in buffer_ids {
15641 if should_unfold {
15642 self.unfold_buffer(buffer_id, cx);
15643 } else {
15644 self.fold_buffer(buffer_id, cx);
15645 }
15646 }
15647 }
15648 }
15649
15650 pub fn toggle_fold_recursive(
15651 &mut self,
15652 _: &actions::ToggleFoldRecursive,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 let selection = self.selections.newest::<Point>(cx);
15657
15658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15659 let range = if selection.is_empty() {
15660 let point = selection.head().to_display_point(&display_map);
15661 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15662 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15663 .to_point(&display_map);
15664 start..end
15665 } else {
15666 selection.range()
15667 };
15668 if display_map.folds_in_range(range).next().is_some() {
15669 self.unfold_recursive(&Default::default(), window, cx)
15670 } else {
15671 self.fold_recursive(&Default::default(), window, cx)
15672 }
15673 }
15674
15675 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15676 if self.is_singleton(cx) {
15677 let mut to_fold = Vec::new();
15678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15679 let selections = self.selections.all_adjusted(cx);
15680
15681 for selection in selections {
15682 let range = selection.range().sorted();
15683 let buffer_start_row = range.start.row;
15684
15685 if range.start.row != range.end.row {
15686 let mut found = false;
15687 let mut row = range.start.row;
15688 while row <= range.end.row {
15689 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15690 {
15691 found = true;
15692 row = crease.range().end.row + 1;
15693 to_fold.push(crease);
15694 } else {
15695 row += 1
15696 }
15697 }
15698 if found {
15699 continue;
15700 }
15701 }
15702
15703 for row in (0..=range.start.row).rev() {
15704 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15705 if crease.range().end.row >= buffer_start_row {
15706 to_fold.push(crease);
15707 if row <= range.start.row {
15708 break;
15709 }
15710 }
15711 }
15712 }
15713 }
15714
15715 self.fold_creases(to_fold, true, window, cx);
15716 } else {
15717 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15718 let buffer_ids = self
15719 .selections
15720 .disjoint_anchor_ranges()
15721 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15722 .collect::<HashSet<_>>();
15723 for buffer_id in buffer_ids {
15724 self.fold_buffer(buffer_id, cx);
15725 }
15726 }
15727 }
15728
15729 fn fold_at_level(
15730 &mut self,
15731 fold_at: &FoldAtLevel,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) {
15735 if !self.buffer.read(cx).is_singleton() {
15736 return;
15737 }
15738
15739 let fold_at_level = fold_at.0;
15740 let snapshot = self.buffer.read(cx).snapshot(cx);
15741 let mut to_fold = Vec::new();
15742 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15743
15744 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15745 while start_row < end_row {
15746 match self
15747 .snapshot(window, cx)
15748 .crease_for_buffer_row(MultiBufferRow(start_row))
15749 {
15750 Some(crease) => {
15751 let nested_start_row = crease.range().start.row + 1;
15752 let nested_end_row = crease.range().end.row;
15753
15754 if current_level < fold_at_level {
15755 stack.push((nested_start_row, nested_end_row, current_level + 1));
15756 } else if current_level == fold_at_level {
15757 to_fold.push(crease);
15758 }
15759
15760 start_row = nested_end_row + 1;
15761 }
15762 None => start_row += 1,
15763 }
15764 }
15765 }
15766
15767 self.fold_creases(to_fold, true, window, cx);
15768 }
15769
15770 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15771 if self.buffer.read(cx).is_singleton() {
15772 let mut fold_ranges = Vec::new();
15773 let snapshot = self.buffer.read(cx).snapshot(cx);
15774
15775 for row in 0..snapshot.max_row().0 {
15776 if let Some(foldable_range) = self
15777 .snapshot(window, cx)
15778 .crease_for_buffer_row(MultiBufferRow(row))
15779 {
15780 fold_ranges.push(foldable_range);
15781 }
15782 }
15783
15784 self.fold_creases(fold_ranges, true, window, cx);
15785 } else {
15786 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15787 editor
15788 .update_in(cx, |editor, _, cx| {
15789 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15790 editor.fold_buffer(buffer_id, cx);
15791 }
15792 })
15793 .ok();
15794 });
15795 }
15796 }
15797
15798 pub fn fold_function_bodies(
15799 &mut self,
15800 _: &actions::FoldFunctionBodies,
15801 window: &mut Window,
15802 cx: &mut Context<Self>,
15803 ) {
15804 let snapshot = self.buffer.read(cx).snapshot(cx);
15805
15806 let ranges = snapshot
15807 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15808 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15809 .collect::<Vec<_>>();
15810
15811 let creases = ranges
15812 .into_iter()
15813 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15814 .collect();
15815
15816 self.fold_creases(creases, true, window, cx);
15817 }
15818
15819 pub fn fold_recursive(
15820 &mut self,
15821 _: &actions::FoldRecursive,
15822 window: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 let mut to_fold = Vec::new();
15826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15827 let selections = self.selections.all_adjusted(cx);
15828
15829 for selection in selections {
15830 let range = selection.range().sorted();
15831 let buffer_start_row = range.start.row;
15832
15833 if range.start.row != range.end.row {
15834 let mut found = false;
15835 for row in range.start.row..=range.end.row {
15836 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15837 found = true;
15838 to_fold.push(crease);
15839 }
15840 }
15841 if found {
15842 continue;
15843 }
15844 }
15845
15846 for row in (0..=range.start.row).rev() {
15847 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15848 if crease.range().end.row >= buffer_start_row {
15849 to_fold.push(crease);
15850 } else {
15851 break;
15852 }
15853 }
15854 }
15855 }
15856
15857 self.fold_creases(to_fold, true, window, cx);
15858 }
15859
15860 pub fn fold_at(
15861 &mut self,
15862 buffer_row: MultiBufferRow,
15863 window: &mut Window,
15864 cx: &mut Context<Self>,
15865 ) {
15866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15867
15868 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15869 let autoscroll = self
15870 .selections
15871 .all::<Point>(cx)
15872 .iter()
15873 .any(|selection| crease.range().overlaps(&selection.range()));
15874
15875 self.fold_creases(vec![crease], autoscroll, window, cx);
15876 }
15877 }
15878
15879 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15880 if self.is_singleton(cx) {
15881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15882 let buffer = &display_map.buffer_snapshot;
15883 let selections = self.selections.all::<Point>(cx);
15884 let ranges = selections
15885 .iter()
15886 .map(|s| {
15887 let range = s.display_range(&display_map).sorted();
15888 let mut start = range.start.to_point(&display_map);
15889 let mut end = range.end.to_point(&display_map);
15890 start.column = 0;
15891 end.column = buffer.line_len(MultiBufferRow(end.row));
15892 start..end
15893 })
15894 .collect::<Vec<_>>();
15895
15896 self.unfold_ranges(&ranges, true, true, cx);
15897 } else {
15898 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15899 let buffer_ids = self
15900 .selections
15901 .disjoint_anchor_ranges()
15902 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15903 .collect::<HashSet<_>>();
15904 for buffer_id in buffer_ids {
15905 self.unfold_buffer(buffer_id, cx);
15906 }
15907 }
15908 }
15909
15910 pub fn unfold_recursive(
15911 &mut self,
15912 _: &UnfoldRecursive,
15913 _window: &mut Window,
15914 cx: &mut Context<Self>,
15915 ) {
15916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15917 let selections = self.selections.all::<Point>(cx);
15918 let ranges = selections
15919 .iter()
15920 .map(|s| {
15921 let mut range = s.display_range(&display_map).sorted();
15922 *range.start.column_mut() = 0;
15923 *range.end.column_mut() = display_map.line_len(range.end.row());
15924 let start = range.start.to_point(&display_map);
15925 let end = range.end.to_point(&display_map);
15926 start..end
15927 })
15928 .collect::<Vec<_>>();
15929
15930 self.unfold_ranges(&ranges, true, true, cx);
15931 }
15932
15933 pub fn unfold_at(
15934 &mut self,
15935 buffer_row: MultiBufferRow,
15936 _window: &mut Window,
15937 cx: &mut Context<Self>,
15938 ) {
15939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15940
15941 let intersection_range = Point::new(buffer_row.0, 0)
15942 ..Point::new(
15943 buffer_row.0,
15944 display_map.buffer_snapshot.line_len(buffer_row),
15945 );
15946
15947 let autoscroll = self
15948 .selections
15949 .all::<Point>(cx)
15950 .iter()
15951 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15952
15953 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15954 }
15955
15956 pub fn unfold_all(
15957 &mut self,
15958 _: &actions::UnfoldAll,
15959 _window: &mut Window,
15960 cx: &mut Context<Self>,
15961 ) {
15962 if self.buffer.read(cx).is_singleton() {
15963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15964 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15965 } else {
15966 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15967 editor
15968 .update(cx, |editor, cx| {
15969 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15970 editor.unfold_buffer(buffer_id, cx);
15971 }
15972 })
15973 .ok();
15974 });
15975 }
15976 }
15977
15978 pub fn fold_selected_ranges(
15979 &mut self,
15980 _: &FoldSelectedRanges,
15981 window: &mut Window,
15982 cx: &mut Context<Self>,
15983 ) {
15984 let selections = self.selections.all_adjusted(cx);
15985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15986 let ranges = selections
15987 .into_iter()
15988 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15989 .collect::<Vec<_>>();
15990 self.fold_creases(ranges, true, window, cx);
15991 }
15992
15993 pub fn fold_ranges<T: ToOffset + Clone>(
15994 &mut self,
15995 ranges: Vec<Range<T>>,
15996 auto_scroll: bool,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16001 let ranges = ranges
16002 .into_iter()
16003 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16004 .collect::<Vec<_>>();
16005 self.fold_creases(ranges, auto_scroll, window, cx);
16006 }
16007
16008 pub fn fold_creases<T: ToOffset + Clone>(
16009 &mut self,
16010 creases: Vec<Crease<T>>,
16011 auto_scroll: bool,
16012 _window: &mut Window,
16013 cx: &mut Context<Self>,
16014 ) {
16015 if creases.is_empty() {
16016 return;
16017 }
16018
16019 let mut buffers_affected = HashSet::default();
16020 let multi_buffer = self.buffer().read(cx);
16021 for crease in &creases {
16022 if let Some((_, buffer, _)) =
16023 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16024 {
16025 buffers_affected.insert(buffer.read(cx).remote_id());
16026 };
16027 }
16028
16029 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16030
16031 if auto_scroll {
16032 self.request_autoscroll(Autoscroll::fit(), cx);
16033 }
16034
16035 cx.notify();
16036
16037 self.scrollbar_marker_state.dirty = true;
16038 self.folds_did_change(cx);
16039 }
16040
16041 /// Removes any folds whose ranges intersect any of the given ranges.
16042 pub fn unfold_ranges<T: ToOffset + Clone>(
16043 &mut self,
16044 ranges: &[Range<T>],
16045 inclusive: bool,
16046 auto_scroll: bool,
16047 cx: &mut Context<Self>,
16048 ) {
16049 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16050 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16051 });
16052 self.folds_did_change(cx);
16053 }
16054
16055 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16056 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16057 return;
16058 }
16059 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16060 self.display_map.update(cx, |display_map, cx| {
16061 display_map.fold_buffers([buffer_id], cx)
16062 });
16063 cx.emit(EditorEvent::BufferFoldToggled {
16064 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16065 folded: true,
16066 });
16067 cx.notify();
16068 }
16069
16070 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16071 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16072 return;
16073 }
16074 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16075 self.display_map.update(cx, |display_map, cx| {
16076 display_map.unfold_buffers([buffer_id], cx);
16077 });
16078 cx.emit(EditorEvent::BufferFoldToggled {
16079 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16080 folded: false,
16081 });
16082 cx.notify();
16083 }
16084
16085 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16086 self.display_map.read(cx).is_buffer_folded(buffer)
16087 }
16088
16089 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16090 self.display_map.read(cx).folded_buffers()
16091 }
16092
16093 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16094 self.display_map.update(cx, |display_map, cx| {
16095 display_map.disable_header_for_buffer(buffer_id, cx);
16096 });
16097 cx.notify();
16098 }
16099
16100 /// Removes any folds with the given ranges.
16101 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16102 &mut self,
16103 ranges: &[Range<T>],
16104 type_id: TypeId,
16105 auto_scroll: bool,
16106 cx: &mut Context<Self>,
16107 ) {
16108 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16109 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16110 });
16111 self.folds_did_change(cx);
16112 }
16113
16114 fn remove_folds_with<T: ToOffset + Clone>(
16115 &mut self,
16116 ranges: &[Range<T>],
16117 auto_scroll: bool,
16118 cx: &mut Context<Self>,
16119 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16120 ) {
16121 if ranges.is_empty() {
16122 return;
16123 }
16124
16125 let mut buffers_affected = HashSet::default();
16126 let multi_buffer = self.buffer().read(cx);
16127 for range in ranges {
16128 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16129 buffers_affected.insert(buffer.read(cx).remote_id());
16130 };
16131 }
16132
16133 self.display_map.update(cx, update);
16134
16135 if auto_scroll {
16136 self.request_autoscroll(Autoscroll::fit(), cx);
16137 }
16138
16139 cx.notify();
16140 self.scrollbar_marker_state.dirty = true;
16141 self.active_indent_guides_state.dirty = true;
16142 }
16143
16144 pub fn update_fold_widths(
16145 &mut self,
16146 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16147 cx: &mut Context<Self>,
16148 ) -> bool {
16149 self.display_map
16150 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16151 }
16152
16153 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16154 self.display_map.read(cx).fold_placeholder.clone()
16155 }
16156
16157 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16158 self.buffer.update(cx, |buffer, cx| {
16159 buffer.set_all_diff_hunks_expanded(cx);
16160 });
16161 }
16162
16163 pub fn expand_all_diff_hunks(
16164 &mut self,
16165 _: &ExpandAllDiffHunks,
16166 _window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) {
16169 self.buffer.update(cx, |buffer, cx| {
16170 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16171 });
16172 }
16173
16174 pub fn toggle_selected_diff_hunks(
16175 &mut self,
16176 _: &ToggleSelectedDiffHunks,
16177 _window: &mut Window,
16178 cx: &mut Context<Self>,
16179 ) {
16180 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16181 self.toggle_diff_hunks_in_ranges(ranges, cx);
16182 }
16183
16184 pub fn diff_hunks_in_ranges<'a>(
16185 &'a self,
16186 ranges: &'a [Range<Anchor>],
16187 buffer: &'a MultiBufferSnapshot,
16188 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16189 ranges.iter().flat_map(move |range| {
16190 let end_excerpt_id = range.end.excerpt_id;
16191 let range = range.to_point(buffer);
16192 let mut peek_end = range.end;
16193 if range.end.row < buffer.max_row().0 {
16194 peek_end = Point::new(range.end.row + 1, 0);
16195 }
16196 buffer
16197 .diff_hunks_in_range(range.start..peek_end)
16198 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16199 })
16200 }
16201
16202 pub fn has_stageable_diff_hunks_in_ranges(
16203 &self,
16204 ranges: &[Range<Anchor>],
16205 snapshot: &MultiBufferSnapshot,
16206 ) -> bool {
16207 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16208 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16209 }
16210
16211 pub fn toggle_staged_selected_diff_hunks(
16212 &mut self,
16213 _: &::git::ToggleStaged,
16214 _: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) {
16217 let snapshot = self.buffer.read(cx).snapshot(cx);
16218 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16219 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16220 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16221 }
16222
16223 pub fn set_render_diff_hunk_controls(
16224 &mut self,
16225 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16226 cx: &mut Context<Self>,
16227 ) {
16228 self.render_diff_hunk_controls = render_diff_hunk_controls;
16229 cx.notify();
16230 }
16231
16232 pub fn stage_and_next(
16233 &mut self,
16234 _: &::git::StageAndNext,
16235 window: &mut Window,
16236 cx: &mut Context<Self>,
16237 ) {
16238 self.do_stage_or_unstage_and_next(true, window, cx);
16239 }
16240
16241 pub fn unstage_and_next(
16242 &mut self,
16243 _: &::git::UnstageAndNext,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 self.do_stage_or_unstage_and_next(false, window, cx);
16248 }
16249
16250 pub fn stage_or_unstage_diff_hunks(
16251 &mut self,
16252 stage: bool,
16253 ranges: Vec<Range<Anchor>>,
16254 cx: &mut Context<Self>,
16255 ) {
16256 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16257 cx.spawn(async move |this, cx| {
16258 task.await?;
16259 this.update(cx, |this, cx| {
16260 let snapshot = this.buffer.read(cx).snapshot(cx);
16261 let chunk_by = this
16262 .diff_hunks_in_ranges(&ranges, &snapshot)
16263 .chunk_by(|hunk| hunk.buffer_id);
16264 for (buffer_id, hunks) in &chunk_by {
16265 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16266 }
16267 })
16268 })
16269 .detach_and_log_err(cx);
16270 }
16271
16272 fn save_buffers_for_ranges_if_needed(
16273 &mut self,
16274 ranges: &[Range<Anchor>],
16275 cx: &mut Context<Editor>,
16276 ) -> Task<Result<()>> {
16277 let multibuffer = self.buffer.read(cx);
16278 let snapshot = multibuffer.read(cx);
16279 let buffer_ids: HashSet<_> = ranges
16280 .iter()
16281 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16282 .collect();
16283 drop(snapshot);
16284
16285 let mut buffers = HashSet::default();
16286 for buffer_id in buffer_ids {
16287 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16288 let buffer = buffer_entity.read(cx);
16289 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16290 {
16291 buffers.insert(buffer_entity);
16292 }
16293 }
16294 }
16295
16296 if let Some(project) = &self.project {
16297 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16298 } else {
16299 Task::ready(Ok(()))
16300 }
16301 }
16302
16303 fn do_stage_or_unstage_and_next(
16304 &mut self,
16305 stage: bool,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) {
16309 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16310
16311 if ranges.iter().any(|range| range.start != range.end) {
16312 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16313 return;
16314 }
16315
16316 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16317 let snapshot = self.snapshot(window, cx);
16318 let position = self.selections.newest::<Point>(cx).head();
16319 let mut row = snapshot
16320 .buffer_snapshot
16321 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16322 .find(|hunk| hunk.row_range.start.0 > position.row)
16323 .map(|hunk| hunk.row_range.start);
16324
16325 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16326 // Outside of the project diff editor, wrap around to the beginning.
16327 if !all_diff_hunks_expanded {
16328 row = row.or_else(|| {
16329 snapshot
16330 .buffer_snapshot
16331 .diff_hunks_in_range(Point::zero()..position)
16332 .find(|hunk| hunk.row_range.end.0 < position.row)
16333 .map(|hunk| hunk.row_range.start)
16334 });
16335 }
16336
16337 if let Some(row) = row {
16338 let destination = Point::new(row.0, 0);
16339 let autoscroll = Autoscroll::center();
16340
16341 self.unfold_ranges(&[destination..destination], false, false, cx);
16342 self.change_selections(Some(autoscroll), window, cx, |s| {
16343 s.select_ranges([destination..destination]);
16344 });
16345 }
16346 }
16347
16348 fn do_stage_or_unstage(
16349 &self,
16350 stage: bool,
16351 buffer_id: BufferId,
16352 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16353 cx: &mut App,
16354 ) -> Option<()> {
16355 let project = self.project.as_ref()?;
16356 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16357 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16358 let buffer_snapshot = buffer.read(cx).snapshot();
16359 let file_exists = buffer_snapshot
16360 .file()
16361 .is_some_and(|file| file.disk_state().exists());
16362 diff.update(cx, |diff, cx| {
16363 diff.stage_or_unstage_hunks(
16364 stage,
16365 &hunks
16366 .map(|hunk| buffer_diff::DiffHunk {
16367 buffer_range: hunk.buffer_range,
16368 diff_base_byte_range: hunk.diff_base_byte_range,
16369 secondary_status: hunk.secondary_status,
16370 range: Point::zero()..Point::zero(), // unused
16371 })
16372 .collect::<Vec<_>>(),
16373 &buffer_snapshot,
16374 file_exists,
16375 cx,
16376 )
16377 });
16378 None
16379 }
16380
16381 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16382 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16383 self.buffer
16384 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16385 }
16386
16387 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16388 self.buffer.update(cx, |buffer, cx| {
16389 let ranges = vec![Anchor::min()..Anchor::max()];
16390 if !buffer.all_diff_hunks_expanded()
16391 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16392 {
16393 buffer.collapse_diff_hunks(ranges, cx);
16394 true
16395 } else {
16396 false
16397 }
16398 })
16399 }
16400
16401 fn toggle_diff_hunks_in_ranges(
16402 &mut self,
16403 ranges: Vec<Range<Anchor>>,
16404 cx: &mut Context<Editor>,
16405 ) {
16406 self.buffer.update(cx, |buffer, cx| {
16407 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16408 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16409 })
16410 }
16411
16412 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16413 self.buffer.update(cx, |buffer, cx| {
16414 let snapshot = buffer.snapshot(cx);
16415 let excerpt_id = range.end.excerpt_id;
16416 let point_range = range.to_point(&snapshot);
16417 let expand = !buffer.single_hunk_is_expanded(range, cx);
16418 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16419 })
16420 }
16421
16422 pub(crate) fn apply_all_diff_hunks(
16423 &mut self,
16424 _: &ApplyAllDiffHunks,
16425 window: &mut Window,
16426 cx: &mut Context<Self>,
16427 ) {
16428 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16429
16430 let buffers = self.buffer.read(cx).all_buffers();
16431 for branch_buffer in buffers {
16432 branch_buffer.update(cx, |branch_buffer, cx| {
16433 branch_buffer.merge_into_base(Vec::new(), cx);
16434 });
16435 }
16436
16437 if let Some(project) = self.project.clone() {
16438 self.save(true, project, window, cx).detach_and_log_err(cx);
16439 }
16440 }
16441
16442 pub(crate) fn apply_selected_diff_hunks(
16443 &mut self,
16444 _: &ApplyDiffHunk,
16445 window: &mut Window,
16446 cx: &mut Context<Self>,
16447 ) {
16448 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16449 let snapshot = self.snapshot(window, cx);
16450 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16451 let mut ranges_by_buffer = HashMap::default();
16452 self.transact(window, cx, |editor, _window, cx| {
16453 for hunk in hunks {
16454 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16455 ranges_by_buffer
16456 .entry(buffer.clone())
16457 .or_insert_with(Vec::new)
16458 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16459 }
16460 }
16461
16462 for (buffer, ranges) in ranges_by_buffer {
16463 buffer.update(cx, |buffer, cx| {
16464 buffer.merge_into_base(ranges, cx);
16465 });
16466 }
16467 });
16468
16469 if let Some(project) = self.project.clone() {
16470 self.save(true, project, window, cx).detach_and_log_err(cx);
16471 }
16472 }
16473
16474 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16475 if hovered != self.gutter_hovered {
16476 self.gutter_hovered = hovered;
16477 cx.notify();
16478 }
16479 }
16480
16481 pub fn insert_blocks(
16482 &mut self,
16483 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16484 autoscroll: Option<Autoscroll>,
16485 cx: &mut Context<Self>,
16486 ) -> Vec<CustomBlockId> {
16487 let blocks = self
16488 .display_map
16489 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16490 if let Some(autoscroll) = autoscroll {
16491 self.request_autoscroll(autoscroll, cx);
16492 }
16493 cx.notify();
16494 blocks
16495 }
16496
16497 pub fn resize_blocks(
16498 &mut self,
16499 heights: HashMap<CustomBlockId, u32>,
16500 autoscroll: Option<Autoscroll>,
16501 cx: &mut Context<Self>,
16502 ) {
16503 self.display_map
16504 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16505 if let Some(autoscroll) = autoscroll {
16506 self.request_autoscroll(autoscroll, cx);
16507 }
16508 cx.notify();
16509 }
16510
16511 pub fn replace_blocks(
16512 &mut self,
16513 renderers: HashMap<CustomBlockId, RenderBlock>,
16514 autoscroll: Option<Autoscroll>,
16515 cx: &mut Context<Self>,
16516 ) {
16517 self.display_map
16518 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16519 if let Some(autoscroll) = autoscroll {
16520 self.request_autoscroll(autoscroll, cx);
16521 }
16522 cx.notify();
16523 }
16524
16525 pub fn remove_blocks(
16526 &mut self,
16527 block_ids: HashSet<CustomBlockId>,
16528 autoscroll: Option<Autoscroll>,
16529 cx: &mut Context<Self>,
16530 ) {
16531 self.display_map.update(cx, |display_map, cx| {
16532 display_map.remove_blocks(block_ids, cx)
16533 });
16534 if let Some(autoscroll) = autoscroll {
16535 self.request_autoscroll(autoscroll, cx);
16536 }
16537 cx.notify();
16538 }
16539
16540 pub fn row_for_block(
16541 &self,
16542 block_id: CustomBlockId,
16543 cx: &mut Context<Self>,
16544 ) -> Option<DisplayRow> {
16545 self.display_map
16546 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16547 }
16548
16549 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16550 self.focused_block = Some(focused_block);
16551 }
16552
16553 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16554 self.focused_block.take()
16555 }
16556
16557 pub fn insert_creases(
16558 &mut self,
16559 creases: impl IntoIterator<Item = Crease<Anchor>>,
16560 cx: &mut Context<Self>,
16561 ) -> Vec<CreaseId> {
16562 self.display_map
16563 .update(cx, |map, cx| map.insert_creases(creases, cx))
16564 }
16565
16566 pub fn remove_creases(
16567 &mut self,
16568 ids: impl IntoIterator<Item = CreaseId>,
16569 cx: &mut Context<Self>,
16570 ) -> Vec<(CreaseId, Range<Anchor>)> {
16571 self.display_map
16572 .update(cx, |map, cx| map.remove_creases(ids, cx))
16573 }
16574
16575 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16576 self.display_map
16577 .update(cx, |map, cx| map.snapshot(cx))
16578 .longest_row()
16579 }
16580
16581 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16582 self.display_map
16583 .update(cx, |map, cx| map.snapshot(cx))
16584 .max_point()
16585 }
16586
16587 pub fn text(&self, cx: &App) -> String {
16588 self.buffer.read(cx).read(cx).text()
16589 }
16590
16591 pub fn is_empty(&self, cx: &App) -> bool {
16592 self.buffer.read(cx).read(cx).is_empty()
16593 }
16594
16595 pub fn text_option(&self, cx: &App) -> Option<String> {
16596 let text = self.text(cx);
16597 let text = text.trim();
16598
16599 if text.is_empty() {
16600 return None;
16601 }
16602
16603 Some(text.to_string())
16604 }
16605
16606 pub fn set_text(
16607 &mut self,
16608 text: impl Into<Arc<str>>,
16609 window: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) {
16612 self.transact(window, cx, |this, _, cx| {
16613 this.buffer
16614 .read(cx)
16615 .as_singleton()
16616 .expect("you can only call set_text on editors for singleton buffers")
16617 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16618 });
16619 }
16620
16621 pub fn display_text(&self, cx: &mut App) -> String {
16622 self.display_map
16623 .update(cx, |map, cx| map.snapshot(cx))
16624 .text()
16625 }
16626
16627 fn create_minimap(
16628 &self,
16629 minimap_settings: MinimapSettings,
16630 window: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) -> Option<Entity<Self>> {
16633 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16634 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16635 }
16636
16637 fn initialize_new_minimap(
16638 &self,
16639 minimap_settings: MinimapSettings,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) -> Entity<Self> {
16643 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16644
16645 let mut minimap = Editor::new_internal(
16646 EditorMode::Minimap {
16647 parent: cx.weak_entity(),
16648 },
16649 self.buffer.clone(),
16650 self.project.clone(),
16651 Some(self.display_map.clone()),
16652 window,
16653 cx,
16654 );
16655 minimap.scroll_manager.clone_state(&self.scroll_manager);
16656 minimap.set_text_style_refinement(TextStyleRefinement {
16657 font_size: Some(MINIMAP_FONT_SIZE),
16658 font_weight: Some(MINIMAP_FONT_WEIGHT),
16659 ..Default::default()
16660 });
16661 minimap.update_minimap_configuration(minimap_settings, cx);
16662 cx.new(|_| minimap)
16663 }
16664
16665 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16666 let current_line_highlight = minimap_settings
16667 .current_line_highlight
16668 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16669 self.set_current_line_highlight(Some(current_line_highlight));
16670 }
16671
16672 pub fn minimap(&self) -> Option<&Entity<Self>> {
16673 self.minimap
16674 .as_ref()
16675 .filter(|_| self.minimap_visibility.visible())
16676 }
16677
16678 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16679 let mut wrap_guides = smallvec::smallvec![];
16680
16681 if self.show_wrap_guides == Some(false) {
16682 return wrap_guides;
16683 }
16684
16685 let settings = self.buffer.read(cx).language_settings(cx);
16686 if settings.show_wrap_guides {
16687 match self.soft_wrap_mode(cx) {
16688 SoftWrap::Column(soft_wrap) => {
16689 wrap_guides.push((soft_wrap as usize, true));
16690 }
16691 SoftWrap::Bounded(soft_wrap) => {
16692 wrap_guides.push((soft_wrap as usize, true));
16693 }
16694 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16695 }
16696 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16697 }
16698
16699 wrap_guides
16700 }
16701
16702 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16703 let settings = self.buffer.read(cx).language_settings(cx);
16704 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16705 match mode {
16706 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16707 SoftWrap::None
16708 }
16709 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16710 language_settings::SoftWrap::PreferredLineLength => {
16711 SoftWrap::Column(settings.preferred_line_length)
16712 }
16713 language_settings::SoftWrap::Bounded => {
16714 SoftWrap::Bounded(settings.preferred_line_length)
16715 }
16716 }
16717 }
16718
16719 pub fn set_soft_wrap_mode(
16720 &mut self,
16721 mode: language_settings::SoftWrap,
16722
16723 cx: &mut Context<Self>,
16724 ) {
16725 self.soft_wrap_mode_override = Some(mode);
16726 cx.notify();
16727 }
16728
16729 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16730 self.hard_wrap = hard_wrap;
16731 cx.notify();
16732 }
16733
16734 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16735 self.text_style_refinement = Some(style);
16736 }
16737
16738 /// called by the Element so we know what style we were most recently rendered with.
16739 pub(crate) fn set_style(
16740 &mut self,
16741 style: EditorStyle,
16742 window: &mut Window,
16743 cx: &mut Context<Self>,
16744 ) {
16745 // We intentionally do not inform the display map about the minimap style
16746 // so that wrapping is not recalculated and stays consistent for the editor
16747 // and its linked minimap.
16748 if !self.mode.is_minimap() {
16749 let rem_size = window.rem_size();
16750 self.display_map.update(cx, |map, cx| {
16751 map.set_font(
16752 style.text.font(),
16753 style.text.font_size.to_pixels(rem_size),
16754 cx,
16755 )
16756 });
16757 }
16758 self.style = Some(style);
16759 }
16760
16761 pub fn style(&self) -> Option<&EditorStyle> {
16762 self.style.as_ref()
16763 }
16764
16765 // Called by the element. This method is not designed to be called outside of the editor
16766 // element's layout code because it does not notify when rewrapping is computed synchronously.
16767 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16768 self.display_map
16769 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16770 }
16771
16772 pub fn set_soft_wrap(&mut self) {
16773 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16774 }
16775
16776 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16777 if self.soft_wrap_mode_override.is_some() {
16778 self.soft_wrap_mode_override.take();
16779 } else {
16780 let soft_wrap = match self.soft_wrap_mode(cx) {
16781 SoftWrap::GitDiff => return,
16782 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16783 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16784 language_settings::SoftWrap::None
16785 }
16786 };
16787 self.soft_wrap_mode_override = Some(soft_wrap);
16788 }
16789 cx.notify();
16790 }
16791
16792 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16793 let Some(workspace) = self.workspace() else {
16794 return;
16795 };
16796 let fs = workspace.read(cx).app_state().fs.clone();
16797 let current_show = TabBarSettings::get_global(cx).show;
16798 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16799 setting.show = Some(!current_show);
16800 });
16801 }
16802
16803 pub fn toggle_indent_guides(
16804 &mut self,
16805 _: &ToggleIndentGuides,
16806 _: &mut Window,
16807 cx: &mut Context<Self>,
16808 ) {
16809 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16810 self.buffer
16811 .read(cx)
16812 .language_settings(cx)
16813 .indent_guides
16814 .enabled
16815 });
16816 self.show_indent_guides = Some(!currently_enabled);
16817 cx.notify();
16818 }
16819
16820 fn should_show_indent_guides(&self) -> Option<bool> {
16821 self.show_indent_guides
16822 }
16823
16824 pub fn toggle_line_numbers(
16825 &mut self,
16826 _: &ToggleLineNumbers,
16827 _: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 let mut editor_settings = EditorSettings::get_global(cx).clone();
16831 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16832 EditorSettings::override_global(editor_settings, cx);
16833 }
16834
16835 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16836 if let Some(show_line_numbers) = self.show_line_numbers {
16837 return show_line_numbers;
16838 }
16839 EditorSettings::get_global(cx).gutter.line_numbers
16840 }
16841
16842 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16843 self.use_relative_line_numbers
16844 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16845 }
16846
16847 pub fn toggle_relative_line_numbers(
16848 &mut self,
16849 _: &ToggleRelativeLineNumbers,
16850 _: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) {
16853 let is_relative = self.should_use_relative_line_numbers(cx);
16854 self.set_relative_line_number(Some(!is_relative), cx)
16855 }
16856
16857 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16858 self.use_relative_line_numbers = is_relative;
16859 cx.notify();
16860 }
16861
16862 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16863 self.show_gutter = show_gutter;
16864 cx.notify();
16865 }
16866
16867 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16868 self.show_scrollbars = show_scrollbars;
16869 cx.notify();
16870 }
16871
16872 pub fn set_minimap_visibility(
16873 &mut self,
16874 minimap_visibility: MinimapVisibility,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 if self.minimap_visibility != minimap_visibility {
16879 if minimap_visibility.visible() && self.minimap.is_none() {
16880 let minimap_settings = EditorSettings::get_global(cx).minimap;
16881 self.minimap =
16882 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16883 }
16884 self.minimap_visibility = minimap_visibility;
16885 cx.notify();
16886 }
16887 }
16888
16889 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16890 self.set_show_scrollbars(false, cx);
16891 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16892 }
16893
16894 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16895 self.show_line_numbers = Some(show_line_numbers);
16896 cx.notify();
16897 }
16898
16899 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16900 self.disable_expand_excerpt_buttons = true;
16901 cx.notify();
16902 }
16903
16904 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16905 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16906 cx.notify();
16907 }
16908
16909 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16910 self.show_code_actions = Some(show_code_actions);
16911 cx.notify();
16912 }
16913
16914 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16915 self.show_runnables = Some(show_runnables);
16916 cx.notify();
16917 }
16918
16919 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16920 self.show_breakpoints = Some(show_breakpoints);
16921 cx.notify();
16922 }
16923
16924 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16925 if self.display_map.read(cx).masked != masked {
16926 self.display_map.update(cx, |map, _| map.masked = masked);
16927 }
16928 cx.notify()
16929 }
16930
16931 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16932 self.show_wrap_guides = Some(show_wrap_guides);
16933 cx.notify();
16934 }
16935
16936 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16937 self.show_indent_guides = Some(show_indent_guides);
16938 cx.notify();
16939 }
16940
16941 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16942 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16943 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16944 if let Some(dir) = file.abs_path(cx).parent() {
16945 return Some(dir.to_owned());
16946 }
16947 }
16948
16949 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16950 return Some(project_path.path.to_path_buf());
16951 }
16952 }
16953
16954 None
16955 }
16956
16957 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16958 self.active_excerpt(cx)?
16959 .1
16960 .read(cx)
16961 .file()
16962 .and_then(|f| f.as_local())
16963 }
16964
16965 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16966 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16967 let buffer = buffer.read(cx);
16968 if let Some(project_path) = buffer.project_path(cx) {
16969 let project = self.project.as_ref()?.read(cx);
16970 project.absolute_path(&project_path, cx)
16971 } else {
16972 buffer
16973 .file()
16974 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16975 }
16976 })
16977 }
16978
16979 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16980 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16981 let project_path = buffer.read(cx).project_path(cx)?;
16982 let project = self.project.as_ref()?.read(cx);
16983 let entry = project.entry_for_path(&project_path, cx)?;
16984 let path = entry.path.to_path_buf();
16985 Some(path)
16986 })
16987 }
16988
16989 pub fn reveal_in_finder(
16990 &mut self,
16991 _: &RevealInFileManager,
16992 _window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 if let Some(target) = self.target_file(cx) {
16996 cx.reveal_path(&target.abs_path(cx));
16997 }
16998 }
16999
17000 pub fn copy_path(
17001 &mut self,
17002 _: &zed_actions::workspace::CopyPath,
17003 _window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 if let Some(path) = self.target_file_abs_path(cx) {
17007 if let Some(path) = path.to_str() {
17008 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17009 }
17010 }
17011 }
17012
17013 pub fn copy_relative_path(
17014 &mut self,
17015 _: &zed_actions::workspace::CopyRelativePath,
17016 _window: &mut Window,
17017 cx: &mut Context<Self>,
17018 ) {
17019 if let Some(path) = self.target_file_path(cx) {
17020 if let Some(path) = path.to_str() {
17021 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17022 }
17023 }
17024 }
17025
17026 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17027 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17028 buffer.read(cx).project_path(cx)
17029 } else {
17030 None
17031 }
17032 }
17033
17034 // Returns true if the editor handled a go-to-line request
17035 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17036 maybe!({
17037 let breakpoint_store = self.breakpoint_store.as_ref()?;
17038
17039 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17040 else {
17041 self.clear_row_highlights::<ActiveDebugLine>();
17042 return None;
17043 };
17044
17045 let position = active_stack_frame.position;
17046 let buffer_id = position.buffer_id?;
17047 let snapshot = self
17048 .project
17049 .as_ref()?
17050 .read(cx)
17051 .buffer_for_id(buffer_id, cx)?
17052 .read(cx)
17053 .snapshot();
17054
17055 let mut handled = false;
17056 for (id, ExcerptRange { context, .. }) in
17057 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17058 {
17059 if context.start.cmp(&position, &snapshot).is_ge()
17060 || context.end.cmp(&position, &snapshot).is_lt()
17061 {
17062 continue;
17063 }
17064 let snapshot = self.buffer.read(cx).snapshot(cx);
17065 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17066
17067 handled = true;
17068 self.clear_row_highlights::<ActiveDebugLine>();
17069
17070 self.go_to_line::<ActiveDebugLine>(
17071 multibuffer_anchor,
17072 Some(cx.theme().colors().editor_debugger_active_line_background),
17073 window,
17074 cx,
17075 );
17076
17077 cx.notify();
17078 }
17079
17080 handled.then_some(())
17081 })
17082 .is_some()
17083 }
17084
17085 pub fn copy_file_name_without_extension(
17086 &mut self,
17087 _: &CopyFileNameWithoutExtension,
17088 _: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 if let Some(file) = self.target_file(cx) {
17092 if let Some(file_stem) = file.path().file_stem() {
17093 if let Some(name) = file_stem.to_str() {
17094 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17095 }
17096 }
17097 }
17098 }
17099
17100 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17101 if let Some(file) = self.target_file(cx) {
17102 if let Some(file_name) = file.path().file_name() {
17103 if let Some(name) = file_name.to_str() {
17104 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17105 }
17106 }
17107 }
17108 }
17109
17110 pub fn toggle_git_blame(
17111 &mut self,
17112 _: &::git::Blame,
17113 window: &mut Window,
17114 cx: &mut Context<Self>,
17115 ) {
17116 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17117
17118 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17119 self.start_git_blame(true, window, cx);
17120 }
17121
17122 cx.notify();
17123 }
17124
17125 pub fn toggle_git_blame_inline(
17126 &mut self,
17127 _: &ToggleGitBlameInline,
17128 window: &mut Window,
17129 cx: &mut Context<Self>,
17130 ) {
17131 self.toggle_git_blame_inline_internal(true, window, cx);
17132 cx.notify();
17133 }
17134
17135 pub fn open_git_blame_commit(
17136 &mut self,
17137 _: &OpenGitBlameCommit,
17138 window: &mut Window,
17139 cx: &mut Context<Self>,
17140 ) {
17141 self.open_git_blame_commit_internal(window, cx);
17142 }
17143
17144 fn open_git_blame_commit_internal(
17145 &mut self,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) -> Option<()> {
17149 let blame = self.blame.as_ref()?;
17150 let snapshot = self.snapshot(window, cx);
17151 let cursor = self.selections.newest::<Point>(cx).head();
17152 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17153 let blame_entry = blame
17154 .update(cx, |blame, cx| {
17155 blame
17156 .blame_for_rows(
17157 &[RowInfo {
17158 buffer_id: Some(buffer.remote_id()),
17159 buffer_row: Some(point.row),
17160 ..Default::default()
17161 }],
17162 cx,
17163 )
17164 .next()
17165 })
17166 .flatten()?;
17167 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17168 let repo = blame.read(cx).repository(cx)?;
17169 let workspace = self.workspace()?.downgrade();
17170 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17171 None
17172 }
17173
17174 pub fn git_blame_inline_enabled(&self) -> bool {
17175 self.git_blame_inline_enabled
17176 }
17177
17178 pub fn toggle_selection_menu(
17179 &mut self,
17180 _: &ToggleSelectionMenu,
17181 _: &mut Window,
17182 cx: &mut Context<Self>,
17183 ) {
17184 self.show_selection_menu = self
17185 .show_selection_menu
17186 .map(|show_selections_menu| !show_selections_menu)
17187 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17188
17189 cx.notify();
17190 }
17191
17192 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17193 self.show_selection_menu
17194 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17195 }
17196
17197 fn start_git_blame(
17198 &mut self,
17199 user_triggered: bool,
17200 window: &mut Window,
17201 cx: &mut Context<Self>,
17202 ) {
17203 if let Some(project) = self.project.as_ref() {
17204 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17205 return;
17206 };
17207
17208 if buffer.read(cx).file().is_none() {
17209 return;
17210 }
17211
17212 let focused = self.focus_handle(cx).contains_focused(window, cx);
17213
17214 let project = project.clone();
17215 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17216 self.blame_subscription =
17217 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17218 self.blame = Some(blame);
17219 }
17220 }
17221
17222 fn toggle_git_blame_inline_internal(
17223 &mut self,
17224 user_triggered: bool,
17225 window: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) {
17228 if self.git_blame_inline_enabled {
17229 self.git_blame_inline_enabled = false;
17230 self.show_git_blame_inline = false;
17231 self.show_git_blame_inline_delay_task.take();
17232 } else {
17233 self.git_blame_inline_enabled = true;
17234 self.start_git_blame_inline(user_triggered, window, cx);
17235 }
17236
17237 cx.notify();
17238 }
17239
17240 fn start_git_blame_inline(
17241 &mut self,
17242 user_triggered: bool,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) {
17246 self.start_git_blame(user_triggered, window, cx);
17247
17248 if ProjectSettings::get_global(cx)
17249 .git
17250 .inline_blame_delay()
17251 .is_some()
17252 {
17253 self.start_inline_blame_timer(window, cx);
17254 } else {
17255 self.show_git_blame_inline = true
17256 }
17257 }
17258
17259 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17260 self.blame.as_ref()
17261 }
17262
17263 pub fn show_git_blame_gutter(&self) -> bool {
17264 self.show_git_blame_gutter
17265 }
17266
17267 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17268 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17269 }
17270
17271 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17272 self.show_git_blame_inline
17273 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17274 && !self.newest_selection_head_on_empty_line(cx)
17275 && self.has_blame_entries(cx)
17276 }
17277
17278 fn has_blame_entries(&self, cx: &App) -> bool {
17279 self.blame()
17280 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17281 }
17282
17283 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17284 let cursor_anchor = self.selections.newest_anchor().head();
17285
17286 let snapshot = self.buffer.read(cx).snapshot(cx);
17287 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17288
17289 snapshot.line_len(buffer_row) == 0
17290 }
17291
17292 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17293 let buffer_and_selection = maybe!({
17294 let selection = self.selections.newest::<Point>(cx);
17295 let selection_range = selection.range();
17296
17297 let multi_buffer = self.buffer().read(cx);
17298 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17299 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17300
17301 let (buffer, range, _) = if selection.reversed {
17302 buffer_ranges.first()
17303 } else {
17304 buffer_ranges.last()
17305 }?;
17306
17307 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17308 ..text::ToPoint::to_point(&range.end, &buffer).row;
17309 Some((
17310 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17311 selection,
17312 ))
17313 });
17314
17315 let Some((buffer, selection)) = buffer_and_selection else {
17316 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17317 };
17318
17319 let Some(project) = self.project.as_ref() else {
17320 return Task::ready(Err(anyhow!("editor does not have project")));
17321 };
17322
17323 project.update(cx, |project, cx| {
17324 project.get_permalink_to_line(&buffer, selection, cx)
17325 })
17326 }
17327
17328 pub fn copy_permalink_to_line(
17329 &mut self,
17330 _: &CopyPermalinkToLine,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 let permalink_task = self.get_permalink_to_line(cx);
17335 let workspace = self.workspace();
17336
17337 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17338 Ok(permalink) => {
17339 cx.update(|_, cx| {
17340 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17341 })
17342 .ok();
17343 }
17344 Err(err) => {
17345 let message = format!("Failed to copy permalink: {err}");
17346
17347 anyhow::Result::<()>::Err(err).log_err();
17348
17349 if let Some(workspace) = workspace {
17350 workspace
17351 .update_in(cx, |workspace, _, cx| {
17352 struct CopyPermalinkToLine;
17353
17354 workspace.show_toast(
17355 Toast::new(
17356 NotificationId::unique::<CopyPermalinkToLine>(),
17357 message,
17358 ),
17359 cx,
17360 )
17361 })
17362 .ok();
17363 }
17364 }
17365 })
17366 .detach();
17367 }
17368
17369 pub fn copy_file_location(
17370 &mut self,
17371 _: &CopyFileLocation,
17372 _: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17376 if let Some(file) = self.target_file(cx) {
17377 if let Some(path) = file.path().to_str() {
17378 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17379 }
17380 }
17381 }
17382
17383 pub fn open_permalink_to_line(
17384 &mut self,
17385 _: &OpenPermalinkToLine,
17386 window: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) {
17389 let permalink_task = self.get_permalink_to_line(cx);
17390 let workspace = self.workspace();
17391
17392 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17393 Ok(permalink) => {
17394 cx.update(|_, cx| {
17395 cx.open_url(permalink.as_ref());
17396 })
17397 .ok();
17398 }
17399 Err(err) => {
17400 let message = format!("Failed to open permalink: {err}");
17401
17402 anyhow::Result::<()>::Err(err).log_err();
17403
17404 if let Some(workspace) = workspace {
17405 workspace
17406 .update(cx, |workspace, cx| {
17407 struct OpenPermalinkToLine;
17408
17409 workspace.show_toast(
17410 Toast::new(
17411 NotificationId::unique::<OpenPermalinkToLine>(),
17412 message,
17413 ),
17414 cx,
17415 )
17416 })
17417 .ok();
17418 }
17419 }
17420 })
17421 .detach();
17422 }
17423
17424 pub fn insert_uuid_v4(
17425 &mut self,
17426 _: &InsertUuidV4,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.insert_uuid(UuidVersion::V4, window, cx);
17431 }
17432
17433 pub fn insert_uuid_v7(
17434 &mut self,
17435 _: &InsertUuidV7,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 self.insert_uuid(UuidVersion::V7, window, cx);
17440 }
17441
17442 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17443 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17444 self.transact(window, cx, |this, window, cx| {
17445 let edits = this
17446 .selections
17447 .all::<Point>(cx)
17448 .into_iter()
17449 .map(|selection| {
17450 let uuid = match version {
17451 UuidVersion::V4 => uuid::Uuid::new_v4(),
17452 UuidVersion::V7 => uuid::Uuid::now_v7(),
17453 };
17454
17455 (selection.range(), uuid.to_string())
17456 });
17457 this.edit(edits, cx);
17458 this.refresh_inline_completion(true, false, window, cx);
17459 });
17460 }
17461
17462 pub fn open_selections_in_multibuffer(
17463 &mut self,
17464 _: &OpenSelectionsInMultibuffer,
17465 window: &mut Window,
17466 cx: &mut Context<Self>,
17467 ) {
17468 let multibuffer = self.buffer.read(cx);
17469
17470 let Some(buffer) = multibuffer.as_singleton() else {
17471 return;
17472 };
17473
17474 let Some(workspace) = self.workspace() else {
17475 return;
17476 };
17477
17478 let locations = self
17479 .selections
17480 .disjoint_anchors()
17481 .iter()
17482 .map(|range| Location {
17483 buffer: buffer.clone(),
17484 range: range.start.text_anchor..range.end.text_anchor,
17485 })
17486 .collect::<Vec<_>>();
17487
17488 let title = multibuffer.title(cx).to_string();
17489
17490 cx.spawn_in(window, async move |_, cx| {
17491 workspace.update_in(cx, |workspace, window, cx| {
17492 Self::open_locations_in_multibuffer(
17493 workspace,
17494 locations,
17495 format!("Selections for '{title}'"),
17496 false,
17497 MultibufferSelectionMode::All,
17498 window,
17499 cx,
17500 );
17501 })
17502 })
17503 .detach();
17504 }
17505
17506 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17507 /// last highlight added will be used.
17508 ///
17509 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17510 pub fn highlight_rows<T: 'static>(
17511 &mut self,
17512 range: Range<Anchor>,
17513 color: Hsla,
17514 options: RowHighlightOptions,
17515 cx: &mut Context<Self>,
17516 ) {
17517 let snapshot = self.buffer().read(cx).snapshot(cx);
17518 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17519 let ix = row_highlights.binary_search_by(|highlight| {
17520 Ordering::Equal
17521 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17522 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17523 });
17524
17525 if let Err(mut ix) = ix {
17526 let index = post_inc(&mut self.highlight_order);
17527
17528 // If this range intersects with the preceding highlight, then merge it with
17529 // the preceding highlight. Otherwise insert a new highlight.
17530 let mut merged = false;
17531 if ix > 0 {
17532 let prev_highlight = &mut row_highlights[ix - 1];
17533 if prev_highlight
17534 .range
17535 .end
17536 .cmp(&range.start, &snapshot)
17537 .is_ge()
17538 {
17539 ix -= 1;
17540 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17541 prev_highlight.range.end = range.end;
17542 }
17543 merged = true;
17544 prev_highlight.index = index;
17545 prev_highlight.color = color;
17546 prev_highlight.options = options;
17547 }
17548 }
17549
17550 if !merged {
17551 row_highlights.insert(
17552 ix,
17553 RowHighlight {
17554 range: range.clone(),
17555 index,
17556 color,
17557 options,
17558 type_id: TypeId::of::<T>(),
17559 },
17560 );
17561 }
17562
17563 // If any of the following highlights intersect with this one, merge them.
17564 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17565 let highlight = &row_highlights[ix];
17566 if next_highlight
17567 .range
17568 .start
17569 .cmp(&highlight.range.end, &snapshot)
17570 .is_le()
17571 {
17572 if next_highlight
17573 .range
17574 .end
17575 .cmp(&highlight.range.end, &snapshot)
17576 .is_gt()
17577 {
17578 row_highlights[ix].range.end = next_highlight.range.end;
17579 }
17580 row_highlights.remove(ix + 1);
17581 } else {
17582 break;
17583 }
17584 }
17585 }
17586 }
17587
17588 /// Remove any highlighted row ranges of the given type that intersect the
17589 /// given ranges.
17590 pub fn remove_highlighted_rows<T: 'static>(
17591 &mut self,
17592 ranges_to_remove: Vec<Range<Anchor>>,
17593 cx: &mut Context<Self>,
17594 ) {
17595 let snapshot = self.buffer().read(cx).snapshot(cx);
17596 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17597 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17598 row_highlights.retain(|highlight| {
17599 while let Some(range_to_remove) = ranges_to_remove.peek() {
17600 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17601 Ordering::Less | Ordering::Equal => {
17602 ranges_to_remove.next();
17603 }
17604 Ordering::Greater => {
17605 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17606 Ordering::Less | Ordering::Equal => {
17607 return false;
17608 }
17609 Ordering::Greater => break,
17610 }
17611 }
17612 }
17613 }
17614
17615 true
17616 })
17617 }
17618
17619 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17620 pub fn clear_row_highlights<T: 'static>(&mut self) {
17621 self.highlighted_rows.remove(&TypeId::of::<T>());
17622 }
17623
17624 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17625 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17626 self.highlighted_rows
17627 .get(&TypeId::of::<T>())
17628 .map_or(&[] as &[_], |vec| vec.as_slice())
17629 .iter()
17630 .map(|highlight| (highlight.range.clone(), highlight.color))
17631 }
17632
17633 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17634 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17635 /// Allows to ignore certain kinds of highlights.
17636 pub fn highlighted_display_rows(
17637 &self,
17638 window: &mut Window,
17639 cx: &mut App,
17640 ) -> BTreeMap<DisplayRow, LineHighlight> {
17641 let snapshot = self.snapshot(window, cx);
17642 let mut used_highlight_orders = HashMap::default();
17643 self.highlighted_rows
17644 .iter()
17645 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17646 .fold(
17647 BTreeMap::<DisplayRow, LineHighlight>::new(),
17648 |mut unique_rows, highlight| {
17649 let start = highlight.range.start.to_display_point(&snapshot);
17650 let end = highlight.range.end.to_display_point(&snapshot);
17651 let start_row = start.row().0;
17652 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17653 && end.column() == 0
17654 {
17655 end.row().0.saturating_sub(1)
17656 } else {
17657 end.row().0
17658 };
17659 for row in start_row..=end_row {
17660 let used_index =
17661 used_highlight_orders.entry(row).or_insert(highlight.index);
17662 if highlight.index >= *used_index {
17663 *used_index = highlight.index;
17664 unique_rows.insert(
17665 DisplayRow(row),
17666 LineHighlight {
17667 include_gutter: highlight.options.include_gutter,
17668 border: None,
17669 background: highlight.color.into(),
17670 type_id: Some(highlight.type_id),
17671 },
17672 );
17673 }
17674 }
17675 unique_rows
17676 },
17677 )
17678 }
17679
17680 pub fn highlighted_display_row_for_autoscroll(
17681 &self,
17682 snapshot: &DisplaySnapshot,
17683 ) -> Option<DisplayRow> {
17684 self.highlighted_rows
17685 .values()
17686 .flat_map(|highlighted_rows| highlighted_rows.iter())
17687 .filter_map(|highlight| {
17688 if highlight.options.autoscroll {
17689 Some(highlight.range.start.to_display_point(snapshot).row())
17690 } else {
17691 None
17692 }
17693 })
17694 .min()
17695 }
17696
17697 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17698 self.highlight_background::<SearchWithinRange>(
17699 ranges,
17700 |colors| colors.editor_document_highlight_read_background,
17701 cx,
17702 )
17703 }
17704
17705 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17706 self.breadcrumb_header = Some(new_header);
17707 }
17708
17709 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17710 self.clear_background_highlights::<SearchWithinRange>(cx);
17711 }
17712
17713 pub fn highlight_background<T: 'static>(
17714 &mut self,
17715 ranges: &[Range<Anchor>],
17716 color_fetcher: fn(&ThemeColors) -> Hsla,
17717 cx: &mut Context<Self>,
17718 ) {
17719 self.background_highlights
17720 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17721 self.scrollbar_marker_state.dirty = true;
17722 cx.notify();
17723 }
17724
17725 pub fn clear_background_highlights<T: 'static>(
17726 &mut self,
17727 cx: &mut Context<Self>,
17728 ) -> Option<BackgroundHighlight> {
17729 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17730 if !text_highlights.1.is_empty() {
17731 self.scrollbar_marker_state.dirty = true;
17732 cx.notify();
17733 }
17734 Some(text_highlights)
17735 }
17736
17737 pub fn highlight_gutter<T: 'static>(
17738 &mut self,
17739 ranges: &[Range<Anchor>],
17740 color_fetcher: fn(&App) -> Hsla,
17741 cx: &mut Context<Self>,
17742 ) {
17743 self.gutter_highlights
17744 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17745 cx.notify();
17746 }
17747
17748 pub fn clear_gutter_highlights<T: 'static>(
17749 &mut self,
17750 cx: &mut Context<Self>,
17751 ) -> Option<GutterHighlight> {
17752 cx.notify();
17753 self.gutter_highlights.remove(&TypeId::of::<T>())
17754 }
17755
17756 #[cfg(feature = "test-support")]
17757 pub fn all_text_background_highlights(
17758 &self,
17759 window: &mut Window,
17760 cx: &mut Context<Self>,
17761 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17762 let snapshot = self.snapshot(window, cx);
17763 let buffer = &snapshot.buffer_snapshot;
17764 let start = buffer.anchor_before(0);
17765 let end = buffer.anchor_after(buffer.len());
17766 let theme = cx.theme().colors();
17767 self.background_highlights_in_range(start..end, &snapshot, theme)
17768 }
17769
17770 #[cfg(feature = "test-support")]
17771 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17772 let snapshot = self.buffer().read(cx).snapshot(cx);
17773
17774 let highlights = self
17775 .background_highlights
17776 .get(&TypeId::of::<items::BufferSearchHighlights>());
17777
17778 if let Some((_color, ranges)) = highlights {
17779 ranges
17780 .iter()
17781 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17782 .collect_vec()
17783 } else {
17784 vec![]
17785 }
17786 }
17787
17788 fn document_highlights_for_position<'a>(
17789 &'a self,
17790 position: Anchor,
17791 buffer: &'a MultiBufferSnapshot,
17792 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17793 let read_highlights = self
17794 .background_highlights
17795 .get(&TypeId::of::<DocumentHighlightRead>())
17796 .map(|h| &h.1);
17797 let write_highlights = self
17798 .background_highlights
17799 .get(&TypeId::of::<DocumentHighlightWrite>())
17800 .map(|h| &h.1);
17801 let left_position = position.bias_left(buffer);
17802 let right_position = position.bias_right(buffer);
17803 read_highlights
17804 .into_iter()
17805 .chain(write_highlights)
17806 .flat_map(move |ranges| {
17807 let start_ix = match ranges.binary_search_by(|probe| {
17808 let cmp = probe.end.cmp(&left_position, buffer);
17809 if cmp.is_ge() {
17810 Ordering::Greater
17811 } else {
17812 Ordering::Less
17813 }
17814 }) {
17815 Ok(i) | Err(i) => i,
17816 };
17817
17818 ranges[start_ix..]
17819 .iter()
17820 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17821 })
17822 }
17823
17824 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17825 self.background_highlights
17826 .get(&TypeId::of::<T>())
17827 .map_or(false, |(_, highlights)| !highlights.is_empty())
17828 }
17829
17830 pub fn background_highlights_in_range(
17831 &self,
17832 search_range: Range<Anchor>,
17833 display_snapshot: &DisplaySnapshot,
17834 theme: &ThemeColors,
17835 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17836 let mut results = Vec::new();
17837 for (color_fetcher, ranges) in self.background_highlights.values() {
17838 let color = color_fetcher(theme);
17839 let start_ix = match ranges.binary_search_by(|probe| {
17840 let cmp = probe
17841 .end
17842 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17843 if cmp.is_gt() {
17844 Ordering::Greater
17845 } else {
17846 Ordering::Less
17847 }
17848 }) {
17849 Ok(i) | Err(i) => i,
17850 };
17851 for range in &ranges[start_ix..] {
17852 if range
17853 .start
17854 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17855 .is_ge()
17856 {
17857 break;
17858 }
17859
17860 let start = range.start.to_display_point(display_snapshot);
17861 let end = range.end.to_display_point(display_snapshot);
17862 results.push((start..end, color))
17863 }
17864 }
17865 results
17866 }
17867
17868 pub fn background_highlight_row_ranges<T: 'static>(
17869 &self,
17870 search_range: Range<Anchor>,
17871 display_snapshot: &DisplaySnapshot,
17872 count: usize,
17873 ) -> Vec<RangeInclusive<DisplayPoint>> {
17874 let mut results = Vec::new();
17875 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17876 return vec![];
17877 };
17878
17879 let start_ix = match ranges.binary_search_by(|probe| {
17880 let cmp = probe
17881 .end
17882 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17883 if cmp.is_gt() {
17884 Ordering::Greater
17885 } else {
17886 Ordering::Less
17887 }
17888 }) {
17889 Ok(i) | Err(i) => i,
17890 };
17891 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17892 if let (Some(start_display), Some(end_display)) = (start, end) {
17893 results.push(
17894 start_display.to_display_point(display_snapshot)
17895 ..=end_display.to_display_point(display_snapshot),
17896 );
17897 }
17898 };
17899 let mut start_row: Option<Point> = None;
17900 let mut end_row: Option<Point> = None;
17901 if ranges.len() > count {
17902 return Vec::new();
17903 }
17904 for range in &ranges[start_ix..] {
17905 if range
17906 .start
17907 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17908 .is_ge()
17909 {
17910 break;
17911 }
17912 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17913 if let Some(current_row) = &end_row {
17914 if end.row == current_row.row {
17915 continue;
17916 }
17917 }
17918 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17919 if start_row.is_none() {
17920 assert_eq!(end_row, None);
17921 start_row = Some(start);
17922 end_row = Some(end);
17923 continue;
17924 }
17925 if let Some(current_end) = end_row.as_mut() {
17926 if start.row > current_end.row + 1 {
17927 push_region(start_row, end_row);
17928 start_row = Some(start);
17929 end_row = Some(end);
17930 } else {
17931 // Merge two hunks.
17932 *current_end = end;
17933 }
17934 } else {
17935 unreachable!();
17936 }
17937 }
17938 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17939 push_region(start_row, end_row);
17940 results
17941 }
17942
17943 pub fn gutter_highlights_in_range(
17944 &self,
17945 search_range: Range<Anchor>,
17946 display_snapshot: &DisplaySnapshot,
17947 cx: &App,
17948 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17949 let mut results = Vec::new();
17950 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17951 let color = color_fetcher(cx);
17952 let start_ix = match ranges.binary_search_by(|probe| {
17953 let cmp = probe
17954 .end
17955 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17956 if cmp.is_gt() {
17957 Ordering::Greater
17958 } else {
17959 Ordering::Less
17960 }
17961 }) {
17962 Ok(i) | Err(i) => i,
17963 };
17964 for range in &ranges[start_ix..] {
17965 if range
17966 .start
17967 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17968 .is_ge()
17969 {
17970 break;
17971 }
17972
17973 let start = range.start.to_display_point(display_snapshot);
17974 let end = range.end.to_display_point(display_snapshot);
17975 results.push((start..end, color))
17976 }
17977 }
17978 results
17979 }
17980
17981 /// Get the text ranges corresponding to the redaction query
17982 pub fn redacted_ranges(
17983 &self,
17984 search_range: Range<Anchor>,
17985 display_snapshot: &DisplaySnapshot,
17986 cx: &App,
17987 ) -> Vec<Range<DisplayPoint>> {
17988 display_snapshot
17989 .buffer_snapshot
17990 .redacted_ranges(search_range, |file| {
17991 if let Some(file) = file {
17992 file.is_private()
17993 && EditorSettings::get(
17994 Some(SettingsLocation {
17995 worktree_id: file.worktree_id(cx),
17996 path: file.path().as_ref(),
17997 }),
17998 cx,
17999 )
18000 .redact_private_values
18001 } else {
18002 false
18003 }
18004 })
18005 .map(|range| {
18006 range.start.to_display_point(display_snapshot)
18007 ..range.end.to_display_point(display_snapshot)
18008 })
18009 .collect()
18010 }
18011
18012 pub fn highlight_text<T: 'static>(
18013 &mut self,
18014 ranges: Vec<Range<Anchor>>,
18015 style: HighlightStyle,
18016 cx: &mut Context<Self>,
18017 ) {
18018 self.display_map.update(cx, |map, _| {
18019 map.highlight_text(TypeId::of::<T>(), ranges, style)
18020 });
18021 cx.notify();
18022 }
18023
18024 pub(crate) fn highlight_inlays<T: 'static>(
18025 &mut self,
18026 highlights: Vec<InlayHighlight>,
18027 style: HighlightStyle,
18028 cx: &mut Context<Self>,
18029 ) {
18030 self.display_map.update(cx, |map, _| {
18031 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18032 });
18033 cx.notify();
18034 }
18035
18036 pub fn text_highlights<'a, T: 'static>(
18037 &'a self,
18038 cx: &'a App,
18039 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18040 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18041 }
18042
18043 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18044 let cleared = self
18045 .display_map
18046 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18047 if cleared {
18048 cx.notify();
18049 }
18050 }
18051
18052 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18053 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18054 && self.focus_handle.is_focused(window)
18055 }
18056
18057 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18058 self.show_cursor_when_unfocused = is_enabled;
18059 cx.notify();
18060 }
18061
18062 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18063 cx.notify();
18064 }
18065
18066 fn on_debug_session_event(
18067 &mut self,
18068 _session: Entity<Session>,
18069 event: &SessionEvent,
18070 cx: &mut Context<Self>,
18071 ) {
18072 match event {
18073 SessionEvent::InvalidateInlineValue => {
18074 self.refresh_inline_values(cx);
18075 }
18076 _ => {}
18077 }
18078 }
18079
18080 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18081 let Some(project) = self.project.clone() else {
18082 return;
18083 };
18084
18085 if !self.inline_value_cache.enabled {
18086 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18087 self.splice_inlays(&inlays, Vec::new(), cx);
18088 return;
18089 }
18090
18091 let current_execution_position = self
18092 .highlighted_rows
18093 .get(&TypeId::of::<ActiveDebugLine>())
18094 .and_then(|lines| lines.last().map(|line| line.range.start));
18095
18096 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18097 let inline_values = editor
18098 .update(cx, |editor, cx| {
18099 let Some(current_execution_position) = current_execution_position else {
18100 return Some(Task::ready(Ok(Vec::new())));
18101 };
18102
18103 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18104 let snapshot = buffer.snapshot(cx);
18105
18106 let excerpt = snapshot.excerpt_containing(
18107 current_execution_position..current_execution_position,
18108 )?;
18109
18110 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18111 })?;
18112
18113 let range =
18114 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18115
18116 project.inline_values(buffer, range, cx)
18117 })
18118 .ok()
18119 .flatten()?
18120 .await
18121 .context("refreshing debugger inlays")
18122 .log_err()?;
18123
18124 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18125
18126 for (buffer_id, inline_value) in inline_values
18127 .into_iter()
18128 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18129 {
18130 buffer_inline_values
18131 .entry(buffer_id)
18132 .or_default()
18133 .push(inline_value);
18134 }
18135
18136 editor
18137 .update(cx, |editor, cx| {
18138 let snapshot = editor.buffer.read(cx).snapshot(cx);
18139 let mut new_inlays = Vec::default();
18140
18141 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18142 let buffer_id = buffer_snapshot.remote_id();
18143 buffer_inline_values
18144 .get(&buffer_id)
18145 .into_iter()
18146 .flatten()
18147 .for_each(|hint| {
18148 let inlay = Inlay::debugger_hint(
18149 post_inc(&mut editor.next_inlay_id),
18150 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18151 hint.text(),
18152 );
18153
18154 new_inlays.push(inlay);
18155 });
18156 }
18157
18158 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18159 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18160
18161 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18162 })
18163 .ok()?;
18164 Some(())
18165 });
18166 }
18167
18168 fn on_buffer_event(
18169 &mut self,
18170 multibuffer: &Entity<MultiBuffer>,
18171 event: &multi_buffer::Event,
18172 window: &mut Window,
18173 cx: &mut Context<Self>,
18174 ) {
18175 match event {
18176 multi_buffer::Event::Edited {
18177 singleton_buffer_edited,
18178 edited_buffer: buffer_edited,
18179 } => {
18180 self.scrollbar_marker_state.dirty = true;
18181 self.active_indent_guides_state.dirty = true;
18182 self.refresh_active_diagnostics(cx);
18183 self.refresh_code_actions(window, cx);
18184 self.refresh_selected_text_highlights(true, window, cx);
18185 refresh_matching_bracket_highlights(self, window, cx);
18186 if self.has_active_inline_completion() {
18187 self.update_visible_inline_completion(window, cx);
18188 }
18189 if let Some(buffer) = buffer_edited {
18190 let buffer_id = buffer.read(cx).remote_id();
18191 if !self.registered_buffers.contains_key(&buffer_id) {
18192 if let Some(project) = self.project.as_ref() {
18193 project.update(cx, |project, cx| {
18194 self.registered_buffers.insert(
18195 buffer_id,
18196 project.register_buffer_with_language_servers(&buffer, cx),
18197 );
18198 })
18199 }
18200 }
18201 }
18202 cx.emit(EditorEvent::BufferEdited);
18203 cx.emit(SearchEvent::MatchesInvalidated);
18204 if *singleton_buffer_edited {
18205 if let Some(project) = &self.project {
18206 #[allow(clippy::mutable_key_type)]
18207 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18208 multibuffer
18209 .all_buffers()
18210 .into_iter()
18211 .filter_map(|buffer| {
18212 buffer.update(cx, |buffer, cx| {
18213 let language = buffer.language()?;
18214 let should_discard = project.update(cx, |project, cx| {
18215 project.is_local()
18216 && !project.has_language_servers_for(buffer, cx)
18217 });
18218 should_discard.not().then_some(language.clone())
18219 })
18220 })
18221 .collect::<HashSet<_>>()
18222 });
18223 if !languages_affected.is_empty() {
18224 self.refresh_inlay_hints(
18225 InlayHintRefreshReason::BufferEdited(languages_affected),
18226 cx,
18227 );
18228 }
18229 }
18230 }
18231
18232 let Some(project) = &self.project else { return };
18233 let (telemetry, is_via_ssh) = {
18234 let project = project.read(cx);
18235 let telemetry = project.client().telemetry().clone();
18236 let is_via_ssh = project.is_via_ssh();
18237 (telemetry, is_via_ssh)
18238 };
18239 refresh_linked_ranges(self, window, cx);
18240 telemetry.log_edit_event("editor", is_via_ssh);
18241 }
18242 multi_buffer::Event::ExcerptsAdded {
18243 buffer,
18244 predecessor,
18245 excerpts,
18246 } => {
18247 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18248 let buffer_id = buffer.read(cx).remote_id();
18249 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18250 if let Some(project) = &self.project {
18251 update_uncommitted_diff_for_buffer(
18252 cx.entity(),
18253 project,
18254 [buffer.clone()],
18255 self.buffer.clone(),
18256 cx,
18257 )
18258 .detach();
18259 }
18260 }
18261 cx.emit(EditorEvent::ExcerptsAdded {
18262 buffer: buffer.clone(),
18263 predecessor: *predecessor,
18264 excerpts: excerpts.clone(),
18265 });
18266 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18267 }
18268 multi_buffer::Event::ExcerptsRemoved {
18269 ids,
18270 removed_buffer_ids,
18271 } => {
18272 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18273 let buffer = self.buffer.read(cx);
18274 self.registered_buffers
18275 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18276 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18277 cx.emit(EditorEvent::ExcerptsRemoved {
18278 ids: ids.clone(),
18279 removed_buffer_ids: removed_buffer_ids.clone(),
18280 })
18281 }
18282 multi_buffer::Event::ExcerptsEdited {
18283 excerpt_ids,
18284 buffer_ids,
18285 } => {
18286 self.display_map.update(cx, |map, cx| {
18287 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18288 });
18289 cx.emit(EditorEvent::ExcerptsEdited {
18290 ids: excerpt_ids.clone(),
18291 })
18292 }
18293 multi_buffer::Event::ExcerptsExpanded { ids } => {
18294 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18295 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18296 }
18297 multi_buffer::Event::Reparsed(buffer_id) => {
18298 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18299 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18300
18301 cx.emit(EditorEvent::Reparsed(*buffer_id));
18302 }
18303 multi_buffer::Event::DiffHunksToggled => {
18304 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18305 }
18306 multi_buffer::Event::LanguageChanged(buffer_id) => {
18307 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18308 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18309 cx.emit(EditorEvent::Reparsed(*buffer_id));
18310 cx.notify();
18311 }
18312 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18313 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18314 multi_buffer::Event::FileHandleChanged
18315 | multi_buffer::Event::Reloaded
18316 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18317 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18318 multi_buffer::Event::DiagnosticsUpdated => {
18319 self.refresh_active_diagnostics(cx);
18320 self.refresh_inline_diagnostics(true, window, cx);
18321 self.scrollbar_marker_state.dirty = true;
18322 cx.notify();
18323 }
18324 _ => {}
18325 };
18326 }
18327
18328 pub fn start_temporary_diff_override(&mut self) {
18329 self.load_diff_task.take();
18330 self.temporary_diff_override = true;
18331 }
18332
18333 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18334 self.temporary_diff_override = false;
18335 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18336 self.buffer.update(cx, |buffer, cx| {
18337 buffer.set_all_diff_hunks_collapsed(cx);
18338 });
18339
18340 if let Some(project) = self.project.clone() {
18341 self.load_diff_task = Some(
18342 update_uncommitted_diff_for_buffer(
18343 cx.entity(),
18344 &project,
18345 self.buffer.read(cx).all_buffers(),
18346 self.buffer.clone(),
18347 cx,
18348 )
18349 .shared(),
18350 );
18351 }
18352 }
18353
18354 fn on_display_map_changed(
18355 &mut self,
18356 _: Entity<DisplayMap>,
18357 _: &mut Window,
18358 cx: &mut Context<Self>,
18359 ) {
18360 cx.notify();
18361 }
18362
18363 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18364 let new_severity = if self.diagnostics_enabled() {
18365 EditorSettings::get_global(cx)
18366 .diagnostics_max_severity
18367 .unwrap_or(DiagnosticSeverity::Hint)
18368 } else {
18369 DiagnosticSeverity::Off
18370 };
18371 self.set_max_diagnostics_severity(new_severity, cx);
18372 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18373 self.update_edit_prediction_settings(cx);
18374 self.refresh_inline_completion(true, false, window, cx);
18375 self.refresh_inlay_hints(
18376 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18377 self.selections.newest_anchor().head(),
18378 &self.buffer.read(cx).snapshot(cx),
18379 cx,
18380 )),
18381 cx,
18382 );
18383
18384 let old_cursor_shape = self.cursor_shape;
18385
18386 {
18387 let editor_settings = EditorSettings::get_global(cx);
18388 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18389 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18390 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18391 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18392 }
18393
18394 if old_cursor_shape != self.cursor_shape {
18395 cx.emit(EditorEvent::CursorShapeChanged);
18396 }
18397
18398 let project_settings = ProjectSettings::get_global(cx);
18399 self.serialize_dirty_buffers =
18400 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18401
18402 if self.mode.is_full() {
18403 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18404 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18405 if self.show_inline_diagnostics != show_inline_diagnostics {
18406 self.show_inline_diagnostics = show_inline_diagnostics;
18407 self.refresh_inline_diagnostics(false, window, cx);
18408 }
18409
18410 if self.git_blame_inline_enabled != inline_blame_enabled {
18411 self.toggle_git_blame_inline_internal(false, window, cx);
18412 }
18413
18414 let minimap_settings = EditorSettings::get_global(cx).minimap;
18415 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18416 self.set_minimap_visibility(
18417 self.minimap_visibility.toggle_visibility(),
18418 window,
18419 cx,
18420 );
18421 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18422 minimap_entity.update(cx, |minimap_editor, cx| {
18423 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18424 })
18425 }
18426 }
18427
18428 cx.notify();
18429 }
18430
18431 pub fn set_searchable(&mut self, searchable: bool) {
18432 self.searchable = searchable;
18433 }
18434
18435 pub fn searchable(&self) -> bool {
18436 self.searchable
18437 }
18438
18439 fn open_proposed_changes_editor(
18440 &mut self,
18441 _: &OpenProposedChangesEditor,
18442 window: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) {
18445 let Some(workspace) = self.workspace() else {
18446 cx.propagate();
18447 return;
18448 };
18449
18450 let selections = self.selections.all::<usize>(cx);
18451 let multi_buffer = self.buffer.read(cx);
18452 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18453 let mut new_selections_by_buffer = HashMap::default();
18454 for selection in selections {
18455 for (buffer, range, _) in
18456 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18457 {
18458 let mut range = range.to_point(buffer);
18459 range.start.column = 0;
18460 range.end.column = buffer.line_len(range.end.row);
18461 new_selections_by_buffer
18462 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18463 .or_insert(Vec::new())
18464 .push(range)
18465 }
18466 }
18467
18468 let proposed_changes_buffers = new_selections_by_buffer
18469 .into_iter()
18470 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18471 .collect::<Vec<_>>();
18472 let proposed_changes_editor = cx.new(|cx| {
18473 ProposedChangesEditor::new(
18474 "Proposed changes",
18475 proposed_changes_buffers,
18476 self.project.clone(),
18477 window,
18478 cx,
18479 )
18480 });
18481
18482 window.defer(cx, move |window, cx| {
18483 workspace.update(cx, |workspace, cx| {
18484 workspace.active_pane().update(cx, |pane, cx| {
18485 pane.add_item(
18486 Box::new(proposed_changes_editor),
18487 true,
18488 true,
18489 None,
18490 window,
18491 cx,
18492 );
18493 });
18494 });
18495 });
18496 }
18497
18498 pub fn open_excerpts_in_split(
18499 &mut self,
18500 _: &OpenExcerptsSplit,
18501 window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 self.open_excerpts_common(None, true, window, cx)
18505 }
18506
18507 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18508 self.open_excerpts_common(None, false, window, cx)
18509 }
18510
18511 fn open_excerpts_common(
18512 &mut self,
18513 jump_data: Option<JumpData>,
18514 split: bool,
18515 window: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) {
18518 let Some(workspace) = self.workspace() else {
18519 cx.propagate();
18520 return;
18521 };
18522
18523 if self.buffer.read(cx).is_singleton() {
18524 cx.propagate();
18525 return;
18526 }
18527
18528 let mut new_selections_by_buffer = HashMap::default();
18529 match &jump_data {
18530 Some(JumpData::MultiBufferPoint {
18531 excerpt_id,
18532 position,
18533 anchor,
18534 line_offset_from_top,
18535 }) => {
18536 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18537 if let Some(buffer) = multi_buffer_snapshot
18538 .buffer_id_for_excerpt(*excerpt_id)
18539 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18540 {
18541 let buffer_snapshot = buffer.read(cx).snapshot();
18542 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18543 language::ToPoint::to_point(anchor, &buffer_snapshot)
18544 } else {
18545 buffer_snapshot.clip_point(*position, Bias::Left)
18546 };
18547 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18548 new_selections_by_buffer.insert(
18549 buffer,
18550 (
18551 vec![jump_to_offset..jump_to_offset],
18552 Some(*line_offset_from_top),
18553 ),
18554 );
18555 }
18556 }
18557 Some(JumpData::MultiBufferRow {
18558 row,
18559 line_offset_from_top,
18560 }) => {
18561 let point = MultiBufferPoint::new(row.0, 0);
18562 if let Some((buffer, buffer_point, _)) =
18563 self.buffer.read(cx).point_to_buffer_point(point, cx)
18564 {
18565 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18566 new_selections_by_buffer
18567 .entry(buffer)
18568 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18569 .0
18570 .push(buffer_offset..buffer_offset)
18571 }
18572 }
18573 None => {
18574 let selections = self.selections.all::<usize>(cx);
18575 let multi_buffer = self.buffer.read(cx);
18576 for selection in selections {
18577 for (snapshot, range, _, anchor) in multi_buffer
18578 .snapshot(cx)
18579 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18580 {
18581 if let Some(anchor) = anchor {
18582 // selection is in a deleted hunk
18583 let Some(buffer_id) = anchor.buffer_id else {
18584 continue;
18585 };
18586 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18587 continue;
18588 };
18589 let offset = text::ToOffset::to_offset(
18590 &anchor.text_anchor,
18591 &buffer_handle.read(cx).snapshot(),
18592 );
18593 let range = offset..offset;
18594 new_selections_by_buffer
18595 .entry(buffer_handle)
18596 .or_insert((Vec::new(), None))
18597 .0
18598 .push(range)
18599 } else {
18600 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18601 else {
18602 continue;
18603 };
18604 new_selections_by_buffer
18605 .entry(buffer_handle)
18606 .or_insert((Vec::new(), None))
18607 .0
18608 .push(range)
18609 }
18610 }
18611 }
18612 }
18613 }
18614
18615 new_selections_by_buffer
18616 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18617
18618 if new_selections_by_buffer.is_empty() {
18619 return;
18620 }
18621
18622 // We defer the pane interaction because we ourselves are a workspace item
18623 // and activating a new item causes the pane to call a method on us reentrantly,
18624 // which panics if we're on the stack.
18625 window.defer(cx, move |window, cx| {
18626 workspace.update(cx, |workspace, cx| {
18627 let pane = if split {
18628 workspace.adjacent_pane(window, cx)
18629 } else {
18630 workspace.active_pane().clone()
18631 };
18632
18633 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18634 let editor = buffer
18635 .read(cx)
18636 .file()
18637 .is_none()
18638 .then(|| {
18639 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18640 // so `workspace.open_project_item` will never find them, always opening a new editor.
18641 // Instead, we try to activate the existing editor in the pane first.
18642 let (editor, pane_item_index) =
18643 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18644 let editor = item.downcast::<Editor>()?;
18645 let singleton_buffer =
18646 editor.read(cx).buffer().read(cx).as_singleton()?;
18647 if singleton_buffer == buffer {
18648 Some((editor, i))
18649 } else {
18650 None
18651 }
18652 })?;
18653 pane.update(cx, |pane, cx| {
18654 pane.activate_item(pane_item_index, true, true, window, cx)
18655 });
18656 Some(editor)
18657 })
18658 .flatten()
18659 .unwrap_or_else(|| {
18660 workspace.open_project_item::<Self>(
18661 pane.clone(),
18662 buffer,
18663 true,
18664 true,
18665 window,
18666 cx,
18667 )
18668 });
18669
18670 editor.update(cx, |editor, cx| {
18671 let autoscroll = match scroll_offset {
18672 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18673 None => Autoscroll::newest(),
18674 };
18675 let nav_history = editor.nav_history.take();
18676 editor.change_selections(Some(autoscroll), window, cx, |s| {
18677 s.select_ranges(ranges);
18678 });
18679 editor.nav_history = nav_history;
18680 });
18681 }
18682 })
18683 });
18684 }
18685
18686 // For now, don't allow opening excerpts in buffers that aren't backed by
18687 // regular project files.
18688 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18689 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18690 }
18691
18692 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18693 let snapshot = self.buffer.read(cx).read(cx);
18694 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18695 Some(
18696 ranges
18697 .iter()
18698 .map(move |range| {
18699 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18700 })
18701 .collect(),
18702 )
18703 }
18704
18705 fn selection_replacement_ranges(
18706 &self,
18707 range: Range<OffsetUtf16>,
18708 cx: &mut App,
18709 ) -> Vec<Range<OffsetUtf16>> {
18710 let selections = self.selections.all::<OffsetUtf16>(cx);
18711 let newest_selection = selections
18712 .iter()
18713 .max_by_key(|selection| selection.id)
18714 .unwrap();
18715 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18716 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18717 let snapshot = self.buffer.read(cx).read(cx);
18718 selections
18719 .into_iter()
18720 .map(|mut selection| {
18721 selection.start.0 =
18722 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18723 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18724 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18725 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18726 })
18727 .collect()
18728 }
18729
18730 fn report_editor_event(
18731 &self,
18732 event_type: &'static str,
18733 file_extension: Option<String>,
18734 cx: &App,
18735 ) {
18736 if cfg!(any(test, feature = "test-support")) {
18737 return;
18738 }
18739
18740 let Some(project) = &self.project else { return };
18741
18742 // If None, we are in a file without an extension
18743 let file = self
18744 .buffer
18745 .read(cx)
18746 .as_singleton()
18747 .and_then(|b| b.read(cx).file());
18748 let file_extension = file_extension.or(file
18749 .as_ref()
18750 .and_then(|file| Path::new(file.file_name(cx)).extension())
18751 .and_then(|e| e.to_str())
18752 .map(|a| a.to_string()));
18753
18754 let vim_mode = vim_enabled(cx);
18755
18756 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18757 let copilot_enabled = edit_predictions_provider
18758 == language::language_settings::EditPredictionProvider::Copilot;
18759 let copilot_enabled_for_language = self
18760 .buffer
18761 .read(cx)
18762 .language_settings(cx)
18763 .show_edit_predictions;
18764
18765 let project = project.read(cx);
18766 telemetry::event!(
18767 event_type,
18768 file_extension,
18769 vim_mode,
18770 copilot_enabled,
18771 copilot_enabled_for_language,
18772 edit_predictions_provider,
18773 is_via_ssh = project.is_via_ssh(),
18774 );
18775 }
18776
18777 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18778 /// with each line being an array of {text, highlight} objects.
18779 fn copy_highlight_json(
18780 &mut self,
18781 _: &CopyHighlightJson,
18782 window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 #[derive(Serialize)]
18786 struct Chunk<'a> {
18787 text: String,
18788 highlight: Option<&'a str>,
18789 }
18790
18791 let snapshot = self.buffer.read(cx).snapshot(cx);
18792 let range = self
18793 .selected_text_range(false, window, cx)
18794 .and_then(|selection| {
18795 if selection.range.is_empty() {
18796 None
18797 } else {
18798 Some(selection.range)
18799 }
18800 })
18801 .unwrap_or_else(|| 0..snapshot.len());
18802
18803 let chunks = snapshot.chunks(range, true);
18804 let mut lines = Vec::new();
18805 let mut line: VecDeque<Chunk> = VecDeque::new();
18806
18807 let Some(style) = self.style.as_ref() else {
18808 return;
18809 };
18810
18811 for chunk in chunks {
18812 let highlight = chunk
18813 .syntax_highlight_id
18814 .and_then(|id| id.name(&style.syntax));
18815 let mut chunk_lines = chunk.text.split('\n').peekable();
18816 while let Some(text) = chunk_lines.next() {
18817 let mut merged_with_last_token = false;
18818 if let Some(last_token) = line.back_mut() {
18819 if last_token.highlight == highlight {
18820 last_token.text.push_str(text);
18821 merged_with_last_token = true;
18822 }
18823 }
18824
18825 if !merged_with_last_token {
18826 line.push_back(Chunk {
18827 text: text.into(),
18828 highlight,
18829 });
18830 }
18831
18832 if chunk_lines.peek().is_some() {
18833 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18834 line.pop_front();
18835 }
18836 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18837 line.pop_back();
18838 }
18839
18840 lines.push(mem::take(&mut line));
18841 }
18842 }
18843 }
18844
18845 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18846 return;
18847 };
18848 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18849 }
18850
18851 pub fn open_context_menu(
18852 &mut self,
18853 _: &OpenContextMenu,
18854 window: &mut Window,
18855 cx: &mut Context<Self>,
18856 ) {
18857 self.request_autoscroll(Autoscroll::newest(), cx);
18858 let position = self.selections.newest_display(cx).start;
18859 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18860 }
18861
18862 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18863 &self.inlay_hint_cache
18864 }
18865
18866 pub fn replay_insert_event(
18867 &mut self,
18868 text: &str,
18869 relative_utf16_range: Option<Range<isize>>,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 if !self.input_enabled {
18874 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18875 return;
18876 }
18877 if let Some(relative_utf16_range) = relative_utf16_range {
18878 let selections = self.selections.all::<OffsetUtf16>(cx);
18879 self.change_selections(None, window, cx, |s| {
18880 let new_ranges = selections.into_iter().map(|range| {
18881 let start = OffsetUtf16(
18882 range
18883 .head()
18884 .0
18885 .saturating_add_signed(relative_utf16_range.start),
18886 );
18887 let end = OffsetUtf16(
18888 range
18889 .head()
18890 .0
18891 .saturating_add_signed(relative_utf16_range.end),
18892 );
18893 start..end
18894 });
18895 s.select_ranges(new_ranges);
18896 });
18897 }
18898
18899 self.handle_input(text, window, cx);
18900 }
18901
18902 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18903 let Some(provider) = self.semantics_provider.as_ref() else {
18904 return false;
18905 };
18906
18907 let mut supports = false;
18908 self.buffer().update(cx, |this, cx| {
18909 this.for_each_buffer(|buffer| {
18910 supports |= provider.supports_inlay_hints(buffer, cx);
18911 });
18912 });
18913
18914 supports
18915 }
18916
18917 pub fn is_focused(&self, window: &Window) -> bool {
18918 self.focus_handle.is_focused(window)
18919 }
18920
18921 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18922 cx.emit(EditorEvent::Focused);
18923
18924 if let Some(descendant) = self
18925 .last_focused_descendant
18926 .take()
18927 .and_then(|descendant| descendant.upgrade())
18928 {
18929 window.focus(&descendant);
18930 } else {
18931 if let Some(blame) = self.blame.as_ref() {
18932 blame.update(cx, GitBlame::focus)
18933 }
18934
18935 self.blink_manager.update(cx, BlinkManager::enable);
18936 self.show_cursor_names(window, cx);
18937 self.buffer.update(cx, |buffer, cx| {
18938 buffer.finalize_last_transaction(cx);
18939 if self.leader_id.is_none() {
18940 buffer.set_active_selections(
18941 &self.selections.disjoint_anchors(),
18942 self.selections.line_mode,
18943 self.cursor_shape,
18944 cx,
18945 );
18946 }
18947 });
18948 }
18949 }
18950
18951 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18952 cx.emit(EditorEvent::FocusedIn)
18953 }
18954
18955 fn handle_focus_out(
18956 &mut self,
18957 event: FocusOutEvent,
18958 _window: &mut Window,
18959 cx: &mut Context<Self>,
18960 ) {
18961 if event.blurred != self.focus_handle {
18962 self.last_focused_descendant = Some(event.blurred);
18963 }
18964 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18965 }
18966
18967 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18968 self.blink_manager.update(cx, BlinkManager::disable);
18969 self.buffer
18970 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18971
18972 if let Some(blame) = self.blame.as_ref() {
18973 blame.update(cx, GitBlame::blur)
18974 }
18975 if !self.hover_state.focused(window, cx) {
18976 hide_hover(self, cx);
18977 }
18978 if !self
18979 .context_menu
18980 .borrow()
18981 .as_ref()
18982 .is_some_and(|context_menu| context_menu.focused(window, cx))
18983 {
18984 self.hide_context_menu(window, cx);
18985 }
18986 self.discard_inline_completion(false, cx);
18987 cx.emit(EditorEvent::Blurred);
18988 cx.notify();
18989 }
18990
18991 pub fn register_action<A: Action>(
18992 &mut self,
18993 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18994 ) -> Subscription {
18995 let id = self.next_editor_action_id.post_inc();
18996 let listener = Arc::new(listener);
18997 self.editor_actions.borrow_mut().insert(
18998 id,
18999 Box::new(move |window, _| {
19000 let listener = listener.clone();
19001 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19002 let action = action.downcast_ref().unwrap();
19003 if phase == DispatchPhase::Bubble {
19004 listener(action, window, cx)
19005 }
19006 })
19007 }),
19008 );
19009
19010 let editor_actions = self.editor_actions.clone();
19011 Subscription::new(move || {
19012 editor_actions.borrow_mut().remove(&id);
19013 })
19014 }
19015
19016 pub fn file_header_size(&self) -> u32 {
19017 FILE_HEADER_HEIGHT
19018 }
19019
19020 pub fn restore(
19021 &mut self,
19022 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19023 window: &mut Window,
19024 cx: &mut Context<Self>,
19025 ) {
19026 let workspace = self.workspace();
19027 let project = self.project.as_ref();
19028 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19029 let mut tasks = Vec::new();
19030 for (buffer_id, changes) in revert_changes {
19031 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19032 buffer.update(cx, |buffer, cx| {
19033 buffer.edit(
19034 changes
19035 .into_iter()
19036 .map(|(range, text)| (range, text.to_string())),
19037 None,
19038 cx,
19039 );
19040 });
19041
19042 if let Some(project) =
19043 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19044 {
19045 project.update(cx, |project, cx| {
19046 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19047 })
19048 }
19049 }
19050 }
19051 tasks
19052 });
19053 cx.spawn_in(window, async move |_, cx| {
19054 for (buffer, task) in save_tasks {
19055 let result = task.await;
19056 if result.is_err() {
19057 let Some(path) = buffer
19058 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19059 .ok()
19060 else {
19061 continue;
19062 };
19063 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19064 let Some(task) = cx
19065 .update_window_entity(&workspace, |workspace, window, cx| {
19066 workspace
19067 .open_path_preview(path, None, false, false, false, window, cx)
19068 })
19069 .ok()
19070 else {
19071 continue;
19072 };
19073 task.await.log_err();
19074 }
19075 }
19076 }
19077 })
19078 .detach();
19079 self.change_selections(None, window, cx, |selections| selections.refresh());
19080 }
19081
19082 pub fn to_pixel_point(
19083 &self,
19084 source: multi_buffer::Anchor,
19085 editor_snapshot: &EditorSnapshot,
19086 window: &mut Window,
19087 ) -> Option<gpui::Point<Pixels>> {
19088 let source_point = source.to_display_point(editor_snapshot);
19089 self.display_to_pixel_point(source_point, editor_snapshot, window)
19090 }
19091
19092 pub fn display_to_pixel_point(
19093 &self,
19094 source: DisplayPoint,
19095 editor_snapshot: &EditorSnapshot,
19096 window: &mut Window,
19097 ) -> Option<gpui::Point<Pixels>> {
19098 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19099 let text_layout_details = self.text_layout_details(window);
19100 let scroll_top = text_layout_details
19101 .scroll_anchor
19102 .scroll_position(editor_snapshot)
19103 .y;
19104
19105 if source.row().as_f32() < scroll_top.floor() {
19106 return None;
19107 }
19108 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19109 let source_y = line_height * (source.row().as_f32() - scroll_top);
19110 Some(gpui::Point::new(source_x, source_y))
19111 }
19112
19113 pub fn has_visible_completions_menu(&self) -> bool {
19114 !self.edit_prediction_preview_is_active()
19115 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19116 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19117 })
19118 }
19119
19120 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19121 if self.mode.is_minimap() {
19122 return;
19123 }
19124 self.addons
19125 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19126 }
19127
19128 pub fn unregister_addon<T: Addon>(&mut self) {
19129 self.addons.remove(&std::any::TypeId::of::<T>());
19130 }
19131
19132 pub fn addon<T: Addon>(&self) -> Option<&T> {
19133 let type_id = std::any::TypeId::of::<T>();
19134 self.addons
19135 .get(&type_id)
19136 .and_then(|item| item.to_any().downcast_ref::<T>())
19137 }
19138
19139 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19140 let type_id = std::any::TypeId::of::<T>();
19141 self.addons
19142 .get_mut(&type_id)
19143 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19144 }
19145
19146 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19147 let text_layout_details = self.text_layout_details(window);
19148 let style = &text_layout_details.editor_style;
19149 let font_id = window.text_system().resolve_font(&style.text.font());
19150 let font_size = style.text.font_size.to_pixels(window.rem_size());
19151 let line_height = style.text.line_height_in_pixels(window.rem_size());
19152 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19153
19154 gpui::Size::new(em_width, line_height)
19155 }
19156
19157 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19158 self.load_diff_task.clone()
19159 }
19160
19161 fn read_metadata_from_db(
19162 &mut self,
19163 item_id: u64,
19164 workspace_id: WorkspaceId,
19165 window: &mut Window,
19166 cx: &mut Context<Editor>,
19167 ) {
19168 if self.is_singleton(cx)
19169 && !self.mode.is_minimap()
19170 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19171 {
19172 let buffer_snapshot = OnceCell::new();
19173
19174 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19175 if !folds.is_empty() {
19176 let snapshot =
19177 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19178 self.fold_ranges(
19179 folds
19180 .into_iter()
19181 .map(|(start, end)| {
19182 snapshot.clip_offset(start, Bias::Left)
19183 ..snapshot.clip_offset(end, Bias::Right)
19184 })
19185 .collect(),
19186 false,
19187 window,
19188 cx,
19189 );
19190 }
19191 }
19192
19193 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19194 if !selections.is_empty() {
19195 let snapshot =
19196 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19197 self.change_selections(None, window, cx, |s| {
19198 s.select_ranges(selections.into_iter().map(|(start, end)| {
19199 snapshot.clip_offset(start, Bias::Left)
19200 ..snapshot.clip_offset(end, Bias::Right)
19201 }));
19202 });
19203 }
19204 };
19205 }
19206
19207 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19208 }
19209}
19210
19211fn vim_enabled(cx: &App) -> bool {
19212 cx.global::<SettingsStore>()
19213 .raw_user_settings()
19214 .get("vim_mode")
19215 == Some(&serde_json::Value::Bool(true))
19216}
19217
19218// Consider user intent and default settings
19219fn choose_completion_range(
19220 completion: &Completion,
19221 intent: CompletionIntent,
19222 buffer: &Entity<Buffer>,
19223 cx: &mut Context<Editor>,
19224) -> Range<usize> {
19225 fn should_replace(
19226 completion: &Completion,
19227 insert_range: &Range<text::Anchor>,
19228 intent: CompletionIntent,
19229 completion_mode_setting: LspInsertMode,
19230 buffer: &Buffer,
19231 ) -> bool {
19232 // specific actions take precedence over settings
19233 match intent {
19234 CompletionIntent::CompleteWithInsert => return false,
19235 CompletionIntent::CompleteWithReplace => return true,
19236 CompletionIntent::Complete | CompletionIntent::Compose => {}
19237 }
19238
19239 match completion_mode_setting {
19240 LspInsertMode::Insert => false,
19241 LspInsertMode::Replace => true,
19242 LspInsertMode::ReplaceSubsequence => {
19243 let mut text_to_replace = buffer.chars_for_range(
19244 buffer.anchor_before(completion.replace_range.start)
19245 ..buffer.anchor_after(completion.replace_range.end),
19246 );
19247 let mut completion_text = completion.new_text.chars();
19248
19249 // is `text_to_replace` a subsequence of `completion_text`
19250 text_to_replace
19251 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19252 }
19253 LspInsertMode::ReplaceSuffix => {
19254 let range_after_cursor = insert_range.end..completion.replace_range.end;
19255
19256 let text_after_cursor = buffer
19257 .text_for_range(
19258 buffer.anchor_before(range_after_cursor.start)
19259 ..buffer.anchor_after(range_after_cursor.end),
19260 )
19261 .collect::<String>();
19262 completion.new_text.ends_with(&text_after_cursor)
19263 }
19264 }
19265 }
19266
19267 let buffer = buffer.read(cx);
19268
19269 if let CompletionSource::Lsp {
19270 insert_range: Some(insert_range),
19271 ..
19272 } = &completion.source
19273 {
19274 let completion_mode_setting =
19275 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19276 .completions
19277 .lsp_insert_mode;
19278
19279 if !should_replace(
19280 completion,
19281 &insert_range,
19282 intent,
19283 completion_mode_setting,
19284 buffer,
19285 ) {
19286 return insert_range.to_offset(buffer);
19287 }
19288 }
19289
19290 completion.replace_range.to_offset(buffer)
19291}
19292
19293fn insert_extra_newline_brackets(
19294 buffer: &MultiBufferSnapshot,
19295 range: Range<usize>,
19296 language: &language::LanguageScope,
19297) -> bool {
19298 let leading_whitespace_len = buffer
19299 .reversed_chars_at(range.start)
19300 .take_while(|c| c.is_whitespace() && *c != '\n')
19301 .map(|c| c.len_utf8())
19302 .sum::<usize>();
19303 let trailing_whitespace_len = buffer
19304 .chars_at(range.end)
19305 .take_while(|c| c.is_whitespace() && *c != '\n')
19306 .map(|c| c.len_utf8())
19307 .sum::<usize>();
19308 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19309
19310 language.brackets().any(|(pair, enabled)| {
19311 let pair_start = pair.start.trim_end();
19312 let pair_end = pair.end.trim_start();
19313
19314 enabled
19315 && pair.newline
19316 && buffer.contains_str_at(range.end, pair_end)
19317 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19318 })
19319}
19320
19321fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19322 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19323 [(buffer, range, _)] => (*buffer, range.clone()),
19324 _ => return false,
19325 };
19326 let pair = {
19327 let mut result: Option<BracketMatch> = None;
19328
19329 for pair in buffer
19330 .all_bracket_ranges(range.clone())
19331 .filter(move |pair| {
19332 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19333 })
19334 {
19335 let len = pair.close_range.end - pair.open_range.start;
19336
19337 if let Some(existing) = &result {
19338 let existing_len = existing.close_range.end - existing.open_range.start;
19339 if len > existing_len {
19340 continue;
19341 }
19342 }
19343
19344 result = Some(pair);
19345 }
19346
19347 result
19348 };
19349 let Some(pair) = pair else {
19350 return false;
19351 };
19352 pair.newline_only
19353 && buffer
19354 .chars_for_range(pair.open_range.end..range.start)
19355 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19356 .all(|c| c.is_whitespace() && c != '\n')
19357}
19358
19359fn update_uncommitted_diff_for_buffer(
19360 editor: Entity<Editor>,
19361 project: &Entity<Project>,
19362 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19363 buffer: Entity<MultiBuffer>,
19364 cx: &mut App,
19365) -> Task<()> {
19366 let mut tasks = Vec::new();
19367 project.update(cx, |project, cx| {
19368 for buffer in buffers {
19369 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19370 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19371 }
19372 }
19373 });
19374 cx.spawn(async move |cx| {
19375 let diffs = future::join_all(tasks).await;
19376 if editor
19377 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19378 .unwrap_or(false)
19379 {
19380 return;
19381 }
19382
19383 buffer
19384 .update(cx, |buffer, cx| {
19385 for diff in diffs.into_iter().flatten() {
19386 buffer.add_diff(diff, cx);
19387 }
19388 })
19389 .ok();
19390 })
19391}
19392
19393fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19394 let tab_size = tab_size.get() as usize;
19395 let mut width = offset;
19396
19397 for ch in text.chars() {
19398 width += if ch == '\t' {
19399 tab_size - (width % tab_size)
19400 } else {
19401 1
19402 };
19403 }
19404
19405 width - offset
19406}
19407
19408#[cfg(test)]
19409mod tests {
19410 use super::*;
19411
19412 #[test]
19413 fn test_string_size_with_expanded_tabs() {
19414 let nz = |val| NonZeroU32::new(val).unwrap();
19415 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19416 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19417 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19418 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19419 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19420 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19421 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19422 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19423 }
19424}
19425
19426/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19427struct WordBreakingTokenizer<'a> {
19428 input: &'a str,
19429}
19430
19431impl<'a> WordBreakingTokenizer<'a> {
19432 fn new(input: &'a str) -> Self {
19433 Self { input }
19434 }
19435}
19436
19437fn is_char_ideographic(ch: char) -> bool {
19438 use unicode_script::Script::*;
19439 use unicode_script::UnicodeScript;
19440 matches!(ch.script(), Han | Tangut | Yi)
19441}
19442
19443fn is_grapheme_ideographic(text: &str) -> bool {
19444 text.chars().any(is_char_ideographic)
19445}
19446
19447fn is_grapheme_whitespace(text: &str) -> bool {
19448 text.chars().any(|x| x.is_whitespace())
19449}
19450
19451fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19452 text.chars().next().map_or(false, |ch| {
19453 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19454 })
19455}
19456
19457#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19458enum WordBreakToken<'a> {
19459 Word { token: &'a str, grapheme_len: usize },
19460 InlineWhitespace { token: &'a str, grapheme_len: usize },
19461 Newline,
19462}
19463
19464impl<'a> Iterator for WordBreakingTokenizer<'a> {
19465 /// Yields a span, the count of graphemes in the token, and whether it was
19466 /// whitespace. Note that it also breaks at word boundaries.
19467 type Item = WordBreakToken<'a>;
19468
19469 fn next(&mut self) -> Option<Self::Item> {
19470 use unicode_segmentation::UnicodeSegmentation;
19471 if self.input.is_empty() {
19472 return None;
19473 }
19474
19475 let mut iter = self.input.graphemes(true).peekable();
19476 let mut offset = 0;
19477 let mut grapheme_len = 0;
19478 if let Some(first_grapheme) = iter.next() {
19479 let is_newline = first_grapheme == "\n";
19480 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19481 offset += first_grapheme.len();
19482 grapheme_len += 1;
19483 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19484 if let Some(grapheme) = iter.peek().copied() {
19485 if should_stay_with_preceding_ideograph(grapheme) {
19486 offset += grapheme.len();
19487 grapheme_len += 1;
19488 }
19489 }
19490 } else {
19491 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19492 let mut next_word_bound = words.peek().copied();
19493 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19494 next_word_bound = words.next();
19495 }
19496 while let Some(grapheme) = iter.peek().copied() {
19497 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19498 break;
19499 };
19500 if is_grapheme_whitespace(grapheme) != is_whitespace
19501 || (grapheme == "\n") != is_newline
19502 {
19503 break;
19504 };
19505 offset += grapheme.len();
19506 grapheme_len += 1;
19507 iter.next();
19508 }
19509 }
19510 let token = &self.input[..offset];
19511 self.input = &self.input[offset..];
19512 if token == "\n" {
19513 Some(WordBreakToken::Newline)
19514 } else if is_whitespace {
19515 Some(WordBreakToken::InlineWhitespace {
19516 token,
19517 grapheme_len,
19518 })
19519 } else {
19520 Some(WordBreakToken::Word {
19521 token,
19522 grapheme_len,
19523 })
19524 }
19525 } else {
19526 None
19527 }
19528 }
19529}
19530
19531#[test]
19532fn test_word_breaking_tokenizer() {
19533 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19534 ("", &[]),
19535 (" ", &[whitespace(" ", 2)]),
19536 ("Ʒ", &[word("Ʒ", 1)]),
19537 ("Ǽ", &[word("Ǽ", 1)]),
19538 ("⋑", &[word("⋑", 1)]),
19539 ("⋑⋑", &[word("⋑⋑", 2)]),
19540 (
19541 "原理,进而",
19542 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19543 ),
19544 (
19545 "hello world",
19546 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19547 ),
19548 (
19549 "hello, world",
19550 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19551 ),
19552 (
19553 " hello world",
19554 &[
19555 whitespace(" ", 2),
19556 word("hello", 5),
19557 whitespace(" ", 1),
19558 word("world", 5),
19559 ],
19560 ),
19561 (
19562 "这是什么 \n 钢笔",
19563 &[
19564 word("这", 1),
19565 word("是", 1),
19566 word("什", 1),
19567 word("么", 1),
19568 whitespace(" ", 1),
19569 newline(),
19570 whitespace(" ", 1),
19571 word("钢", 1),
19572 word("笔", 1),
19573 ],
19574 ),
19575 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19576 ];
19577
19578 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19579 WordBreakToken::Word {
19580 token,
19581 grapheme_len,
19582 }
19583 }
19584
19585 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19586 WordBreakToken::InlineWhitespace {
19587 token,
19588 grapheme_len,
19589 }
19590 }
19591
19592 fn newline() -> WordBreakToken<'static> {
19593 WordBreakToken::Newline
19594 }
19595
19596 for (input, result) in tests {
19597 assert_eq!(
19598 WordBreakingTokenizer::new(input)
19599 .collect::<Vec<_>>()
19600 .as_slice(),
19601 *result,
19602 );
19603 }
19604}
19605
19606fn wrap_with_prefix(
19607 line_prefix: String,
19608 unwrapped_text: String,
19609 wrap_column: usize,
19610 tab_size: NonZeroU32,
19611 preserve_existing_whitespace: bool,
19612) -> String {
19613 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19614 let mut wrapped_text = String::new();
19615 let mut current_line = line_prefix.clone();
19616
19617 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19618 let mut current_line_len = line_prefix_len;
19619 let mut in_whitespace = false;
19620 for token in tokenizer {
19621 let have_preceding_whitespace = in_whitespace;
19622 match token {
19623 WordBreakToken::Word {
19624 token,
19625 grapheme_len,
19626 } => {
19627 in_whitespace = false;
19628 if current_line_len + grapheme_len > wrap_column
19629 && current_line_len != line_prefix_len
19630 {
19631 wrapped_text.push_str(current_line.trim_end());
19632 wrapped_text.push('\n');
19633 current_line.truncate(line_prefix.len());
19634 current_line_len = line_prefix_len;
19635 }
19636 current_line.push_str(token);
19637 current_line_len += grapheme_len;
19638 }
19639 WordBreakToken::InlineWhitespace {
19640 mut token,
19641 mut grapheme_len,
19642 } => {
19643 in_whitespace = true;
19644 if have_preceding_whitespace && !preserve_existing_whitespace {
19645 continue;
19646 }
19647 if !preserve_existing_whitespace {
19648 token = " ";
19649 grapheme_len = 1;
19650 }
19651 if current_line_len + grapheme_len > wrap_column {
19652 wrapped_text.push_str(current_line.trim_end());
19653 wrapped_text.push('\n');
19654 current_line.truncate(line_prefix.len());
19655 current_line_len = line_prefix_len;
19656 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19657 current_line.push_str(token);
19658 current_line_len += grapheme_len;
19659 }
19660 }
19661 WordBreakToken::Newline => {
19662 in_whitespace = true;
19663 if preserve_existing_whitespace {
19664 wrapped_text.push_str(current_line.trim_end());
19665 wrapped_text.push('\n');
19666 current_line.truncate(line_prefix.len());
19667 current_line_len = line_prefix_len;
19668 } else if have_preceding_whitespace {
19669 continue;
19670 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19671 {
19672 wrapped_text.push_str(current_line.trim_end());
19673 wrapped_text.push('\n');
19674 current_line.truncate(line_prefix.len());
19675 current_line_len = line_prefix_len;
19676 } else if current_line_len != line_prefix_len {
19677 current_line.push(' ');
19678 current_line_len += 1;
19679 }
19680 }
19681 }
19682 }
19683
19684 if !current_line.is_empty() {
19685 wrapped_text.push_str(¤t_line);
19686 }
19687 wrapped_text
19688}
19689
19690#[test]
19691fn test_wrap_with_prefix() {
19692 assert_eq!(
19693 wrap_with_prefix(
19694 "# ".to_string(),
19695 "abcdefg".to_string(),
19696 4,
19697 NonZeroU32::new(4).unwrap(),
19698 false,
19699 ),
19700 "# abcdefg"
19701 );
19702 assert_eq!(
19703 wrap_with_prefix(
19704 "".to_string(),
19705 "\thello world".to_string(),
19706 8,
19707 NonZeroU32::new(4).unwrap(),
19708 false,
19709 ),
19710 "hello\nworld"
19711 );
19712 assert_eq!(
19713 wrap_with_prefix(
19714 "// ".to_string(),
19715 "xx \nyy zz aa bb cc".to_string(),
19716 12,
19717 NonZeroU32::new(4).unwrap(),
19718 false,
19719 ),
19720 "// xx yy zz\n// aa bb cc"
19721 );
19722 assert_eq!(
19723 wrap_with_prefix(
19724 String::new(),
19725 "这是什么 \n 钢笔".to_string(),
19726 3,
19727 NonZeroU32::new(4).unwrap(),
19728 false,
19729 ),
19730 "这是什\n么 钢\n笔"
19731 );
19732}
19733
19734pub trait CollaborationHub {
19735 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19736 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19737 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19738}
19739
19740impl CollaborationHub for Entity<Project> {
19741 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19742 self.read(cx).collaborators()
19743 }
19744
19745 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19746 self.read(cx).user_store().read(cx).participant_indices()
19747 }
19748
19749 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19750 let this = self.read(cx);
19751 let user_ids = this.collaborators().values().map(|c| c.user_id);
19752 this.user_store().read_with(cx, |user_store, cx| {
19753 user_store.participant_names(user_ids, cx)
19754 })
19755 }
19756}
19757
19758pub trait SemanticsProvider {
19759 fn hover(
19760 &self,
19761 buffer: &Entity<Buffer>,
19762 position: text::Anchor,
19763 cx: &mut App,
19764 ) -> Option<Task<Vec<project::Hover>>>;
19765
19766 fn inline_values(
19767 &self,
19768 buffer_handle: Entity<Buffer>,
19769 range: Range<text::Anchor>,
19770 cx: &mut App,
19771 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19772
19773 fn inlay_hints(
19774 &self,
19775 buffer_handle: Entity<Buffer>,
19776 range: Range<text::Anchor>,
19777 cx: &mut App,
19778 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19779
19780 fn resolve_inlay_hint(
19781 &self,
19782 hint: InlayHint,
19783 buffer_handle: Entity<Buffer>,
19784 server_id: LanguageServerId,
19785 cx: &mut App,
19786 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19787
19788 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19789
19790 fn document_highlights(
19791 &self,
19792 buffer: &Entity<Buffer>,
19793 position: text::Anchor,
19794 cx: &mut App,
19795 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19796
19797 fn definitions(
19798 &self,
19799 buffer: &Entity<Buffer>,
19800 position: text::Anchor,
19801 kind: GotoDefinitionKind,
19802 cx: &mut App,
19803 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19804
19805 fn range_for_rename(
19806 &self,
19807 buffer: &Entity<Buffer>,
19808 position: text::Anchor,
19809 cx: &mut App,
19810 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19811
19812 fn perform_rename(
19813 &self,
19814 buffer: &Entity<Buffer>,
19815 position: text::Anchor,
19816 new_name: String,
19817 cx: &mut App,
19818 ) -> Option<Task<Result<ProjectTransaction>>>;
19819}
19820
19821pub trait CompletionProvider {
19822 fn completions(
19823 &self,
19824 excerpt_id: ExcerptId,
19825 buffer: &Entity<Buffer>,
19826 buffer_position: text::Anchor,
19827 trigger: CompletionContext,
19828 window: &mut Window,
19829 cx: &mut Context<Editor>,
19830 ) -> Task<Result<Option<Vec<Completion>>>>;
19831
19832 fn resolve_completions(
19833 &self,
19834 buffer: Entity<Buffer>,
19835 completion_indices: Vec<usize>,
19836 completions: Rc<RefCell<Box<[Completion]>>>,
19837 cx: &mut Context<Editor>,
19838 ) -> Task<Result<bool>>;
19839
19840 fn apply_additional_edits_for_completion(
19841 &self,
19842 _buffer: Entity<Buffer>,
19843 _completions: Rc<RefCell<Box<[Completion]>>>,
19844 _completion_index: usize,
19845 _push_to_history: bool,
19846 _cx: &mut Context<Editor>,
19847 ) -> Task<Result<Option<language::Transaction>>> {
19848 Task::ready(Ok(None))
19849 }
19850
19851 fn is_completion_trigger(
19852 &self,
19853 buffer: &Entity<Buffer>,
19854 position: language::Anchor,
19855 text: &str,
19856 trigger_in_words: bool,
19857 cx: &mut Context<Editor>,
19858 ) -> bool;
19859
19860 fn sort_completions(&self) -> bool {
19861 true
19862 }
19863
19864 fn filter_completions(&self) -> bool {
19865 true
19866 }
19867}
19868
19869pub trait CodeActionProvider {
19870 fn id(&self) -> Arc<str>;
19871
19872 fn code_actions(
19873 &self,
19874 buffer: &Entity<Buffer>,
19875 range: Range<text::Anchor>,
19876 window: &mut Window,
19877 cx: &mut App,
19878 ) -> Task<Result<Vec<CodeAction>>>;
19879
19880 fn apply_code_action(
19881 &self,
19882 buffer_handle: Entity<Buffer>,
19883 action: CodeAction,
19884 excerpt_id: ExcerptId,
19885 push_to_history: bool,
19886 window: &mut Window,
19887 cx: &mut App,
19888 ) -> Task<Result<ProjectTransaction>>;
19889}
19890
19891impl CodeActionProvider for Entity<Project> {
19892 fn id(&self) -> Arc<str> {
19893 "project".into()
19894 }
19895
19896 fn code_actions(
19897 &self,
19898 buffer: &Entity<Buffer>,
19899 range: Range<text::Anchor>,
19900 _window: &mut Window,
19901 cx: &mut App,
19902 ) -> Task<Result<Vec<CodeAction>>> {
19903 self.update(cx, |project, cx| {
19904 let code_lens = project.code_lens(buffer, range.clone(), cx);
19905 let code_actions = project.code_actions(buffer, range, None, cx);
19906 cx.background_spawn(async move {
19907 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19908 Ok(code_lens
19909 .context("code lens fetch")?
19910 .into_iter()
19911 .chain(code_actions.context("code action fetch")?)
19912 .collect())
19913 })
19914 })
19915 }
19916
19917 fn apply_code_action(
19918 &self,
19919 buffer_handle: Entity<Buffer>,
19920 action: CodeAction,
19921 _excerpt_id: ExcerptId,
19922 push_to_history: bool,
19923 _window: &mut Window,
19924 cx: &mut App,
19925 ) -> Task<Result<ProjectTransaction>> {
19926 self.update(cx, |project, cx| {
19927 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19928 })
19929 }
19930}
19931
19932fn snippet_completions(
19933 project: &Project,
19934 buffer: &Entity<Buffer>,
19935 buffer_position: text::Anchor,
19936 cx: &mut App,
19937) -> Task<Result<Vec<Completion>>> {
19938 let languages = buffer.read(cx).languages_at(buffer_position);
19939 let snippet_store = project.snippets().read(cx);
19940
19941 let scopes: Vec<_> = languages
19942 .iter()
19943 .filter_map(|language| {
19944 let language_name = language.lsp_id();
19945 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19946
19947 if snippets.is_empty() {
19948 None
19949 } else {
19950 Some((language.default_scope(), snippets))
19951 }
19952 })
19953 .collect();
19954
19955 if scopes.is_empty() {
19956 return Task::ready(Ok(vec![]));
19957 }
19958
19959 let snapshot = buffer.read(cx).text_snapshot();
19960 let chars: String = snapshot
19961 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19962 .collect();
19963 let executor = cx.background_executor().clone();
19964
19965 cx.background_spawn(async move {
19966 let mut all_results: Vec<Completion> = Vec::new();
19967 for (scope, snippets) in scopes.into_iter() {
19968 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19969 let mut last_word = chars
19970 .chars()
19971 .take_while(|c| classifier.is_word(*c))
19972 .collect::<String>();
19973 last_word = last_word.chars().rev().collect();
19974
19975 if last_word.is_empty() {
19976 return Ok(vec![]);
19977 }
19978
19979 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19980 let to_lsp = |point: &text::Anchor| {
19981 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19982 point_to_lsp(end)
19983 };
19984 let lsp_end = to_lsp(&buffer_position);
19985
19986 let candidates = snippets
19987 .iter()
19988 .enumerate()
19989 .flat_map(|(ix, snippet)| {
19990 snippet
19991 .prefix
19992 .iter()
19993 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19994 })
19995 .collect::<Vec<StringMatchCandidate>>();
19996
19997 let mut matches = fuzzy::match_strings(
19998 &candidates,
19999 &last_word,
20000 last_word.chars().any(|c| c.is_uppercase()),
20001 100,
20002 &Default::default(),
20003 executor.clone(),
20004 )
20005 .await;
20006
20007 // Remove all candidates where the query's start does not match the start of any word in the candidate
20008 if let Some(query_start) = last_word.chars().next() {
20009 matches.retain(|string_match| {
20010 split_words(&string_match.string).any(|word| {
20011 // Check that the first codepoint of the word as lowercase matches the first
20012 // codepoint of the query as lowercase
20013 word.chars()
20014 .flat_map(|codepoint| codepoint.to_lowercase())
20015 .zip(query_start.to_lowercase())
20016 .all(|(word_cp, query_cp)| word_cp == query_cp)
20017 })
20018 });
20019 }
20020
20021 let matched_strings = matches
20022 .into_iter()
20023 .map(|m| m.string)
20024 .collect::<HashSet<_>>();
20025
20026 let mut result: Vec<Completion> = snippets
20027 .iter()
20028 .filter_map(|snippet| {
20029 let matching_prefix = snippet
20030 .prefix
20031 .iter()
20032 .find(|prefix| matched_strings.contains(*prefix))?;
20033 let start = as_offset - last_word.len();
20034 let start = snapshot.anchor_before(start);
20035 let range = start..buffer_position;
20036 let lsp_start = to_lsp(&start);
20037 let lsp_range = lsp::Range {
20038 start: lsp_start,
20039 end: lsp_end,
20040 };
20041 Some(Completion {
20042 replace_range: range,
20043 new_text: snippet.body.clone(),
20044 source: CompletionSource::Lsp {
20045 insert_range: None,
20046 server_id: LanguageServerId(usize::MAX),
20047 resolved: true,
20048 lsp_completion: Box::new(lsp::CompletionItem {
20049 label: snippet.prefix.first().unwrap().clone(),
20050 kind: Some(CompletionItemKind::SNIPPET),
20051 label_details: snippet.description.as_ref().map(|description| {
20052 lsp::CompletionItemLabelDetails {
20053 detail: Some(description.clone()),
20054 description: None,
20055 }
20056 }),
20057 insert_text_format: Some(InsertTextFormat::SNIPPET),
20058 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20059 lsp::InsertReplaceEdit {
20060 new_text: snippet.body.clone(),
20061 insert: lsp_range,
20062 replace: lsp_range,
20063 },
20064 )),
20065 filter_text: Some(snippet.body.clone()),
20066 sort_text: Some(char::MAX.to_string()),
20067 ..lsp::CompletionItem::default()
20068 }),
20069 lsp_defaults: None,
20070 },
20071 label: CodeLabel {
20072 text: matching_prefix.clone(),
20073 runs: Vec::new(),
20074 filter_range: 0..matching_prefix.len(),
20075 },
20076 icon_path: None,
20077 documentation: Some(
20078 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20079 single_line: snippet.name.clone().into(),
20080 plain_text: snippet
20081 .description
20082 .clone()
20083 .map(|description| description.into()),
20084 },
20085 ),
20086 insert_text_mode: None,
20087 confirm: None,
20088 })
20089 })
20090 .collect();
20091
20092 all_results.append(&mut result);
20093 }
20094
20095 Ok(all_results)
20096 })
20097}
20098
20099impl CompletionProvider for Entity<Project> {
20100 fn completions(
20101 &self,
20102 _excerpt_id: ExcerptId,
20103 buffer: &Entity<Buffer>,
20104 buffer_position: text::Anchor,
20105 options: CompletionContext,
20106 _window: &mut Window,
20107 cx: &mut Context<Editor>,
20108 ) -> Task<Result<Option<Vec<Completion>>>> {
20109 self.update(cx, |project, cx| {
20110 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20111 let project_completions = project.completions(buffer, buffer_position, options, cx);
20112 cx.background_spawn(async move {
20113 let snippets_completions = snippets.await?;
20114 match project_completions.await? {
20115 Some(mut completions) => {
20116 completions.extend(snippets_completions);
20117 Ok(Some(completions))
20118 }
20119 None => {
20120 if snippets_completions.is_empty() {
20121 Ok(None)
20122 } else {
20123 Ok(Some(snippets_completions))
20124 }
20125 }
20126 }
20127 })
20128 })
20129 }
20130
20131 fn resolve_completions(
20132 &self,
20133 buffer: Entity<Buffer>,
20134 completion_indices: Vec<usize>,
20135 completions: Rc<RefCell<Box<[Completion]>>>,
20136 cx: &mut Context<Editor>,
20137 ) -> Task<Result<bool>> {
20138 self.update(cx, |project, cx| {
20139 project.lsp_store().update(cx, |lsp_store, cx| {
20140 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20141 })
20142 })
20143 }
20144
20145 fn apply_additional_edits_for_completion(
20146 &self,
20147 buffer: Entity<Buffer>,
20148 completions: Rc<RefCell<Box<[Completion]>>>,
20149 completion_index: usize,
20150 push_to_history: bool,
20151 cx: &mut Context<Editor>,
20152 ) -> Task<Result<Option<language::Transaction>>> {
20153 self.update(cx, |project, cx| {
20154 project.lsp_store().update(cx, |lsp_store, cx| {
20155 lsp_store.apply_additional_edits_for_completion(
20156 buffer,
20157 completions,
20158 completion_index,
20159 push_to_history,
20160 cx,
20161 )
20162 })
20163 })
20164 }
20165
20166 fn is_completion_trigger(
20167 &self,
20168 buffer: &Entity<Buffer>,
20169 position: language::Anchor,
20170 text: &str,
20171 trigger_in_words: bool,
20172 cx: &mut Context<Editor>,
20173 ) -> bool {
20174 let mut chars = text.chars();
20175 let char = if let Some(char) = chars.next() {
20176 char
20177 } else {
20178 return false;
20179 };
20180 if chars.next().is_some() {
20181 return false;
20182 }
20183
20184 let buffer = buffer.read(cx);
20185 let snapshot = buffer.snapshot();
20186 if !snapshot.settings_at(position, cx).show_completions_on_input {
20187 return false;
20188 }
20189 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20190 if trigger_in_words && classifier.is_word(char) {
20191 return true;
20192 }
20193
20194 buffer.completion_triggers().contains(text)
20195 }
20196}
20197
20198impl SemanticsProvider for Entity<Project> {
20199 fn hover(
20200 &self,
20201 buffer: &Entity<Buffer>,
20202 position: text::Anchor,
20203 cx: &mut App,
20204 ) -> Option<Task<Vec<project::Hover>>> {
20205 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20206 }
20207
20208 fn document_highlights(
20209 &self,
20210 buffer: &Entity<Buffer>,
20211 position: text::Anchor,
20212 cx: &mut App,
20213 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20214 Some(self.update(cx, |project, cx| {
20215 project.document_highlights(buffer, position, cx)
20216 }))
20217 }
20218
20219 fn definitions(
20220 &self,
20221 buffer: &Entity<Buffer>,
20222 position: text::Anchor,
20223 kind: GotoDefinitionKind,
20224 cx: &mut App,
20225 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20226 Some(self.update(cx, |project, cx| match kind {
20227 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20228 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20229 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20230 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20231 }))
20232 }
20233
20234 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20235 // TODO: make this work for remote projects
20236 self.update(cx, |project, cx| {
20237 if project
20238 .active_debug_session(cx)
20239 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20240 {
20241 return true;
20242 }
20243
20244 buffer.update(cx, |buffer, cx| {
20245 project.any_language_server_supports_inlay_hints(buffer, cx)
20246 })
20247 })
20248 }
20249
20250 fn inline_values(
20251 &self,
20252 buffer_handle: Entity<Buffer>,
20253
20254 range: Range<text::Anchor>,
20255 cx: &mut App,
20256 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20257 self.update(cx, |project, cx| {
20258 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20259
20260 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20261 })
20262 }
20263
20264 fn inlay_hints(
20265 &self,
20266 buffer_handle: Entity<Buffer>,
20267 range: Range<text::Anchor>,
20268 cx: &mut App,
20269 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20270 Some(self.update(cx, |project, cx| {
20271 project.inlay_hints(buffer_handle, range, cx)
20272 }))
20273 }
20274
20275 fn resolve_inlay_hint(
20276 &self,
20277 hint: InlayHint,
20278 buffer_handle: Entity<Buffer>,
20279 server_id: LanguageServerId,
20280 cx: &mut App,
20281 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20282 Some(self.update(cx, |project, cx| {
20283 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20284 }))
20285 }
20286
20287 fn range_for_rename(
20288 &self,
20289 buffer: &Entity<Buffer>,
20290 position: text::Anchor,
20291 cx: &mut App,
20292 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20293 Some(self.update(cx, |project, cx| {
20294 let buffer = buffer.clone();
20295 let task = project.prepare_rename(buffer.clone(), position, cx);
20296 cx.spawn(async move |_, cx| {
20297 Ok(match task.await? {
20298 PrepareRenameResponse::Success(range) => Some(range),
20299 PrepareRenameResponse::InvalidPosition => None,
20300 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20301 // Fallback on using TreeSitter info to determine identifier range
20302 buffer.update(cx, |buffer, _| {
20303 let snapshot = buffer.snapshot();
20304 let (range, kind) = snapshot.surrounding_word(position);
20305 if kind != Some(CharKind::Word) {
20306 return None;
20307 }
20308 Some(
20309 snapshot.anchor_before(range.start)
20310 ..snapshot.anchor_after(range.end),
20311 )
20312 })?
20313 }
20314 })
20315 })
20316 }))
20317 }
20318
20319 fn perform_rename(
20320 &self,
20321 buffer: &Entity<Buffer>,
20322 position: text::Anchor,
20323 new_name: String,
20324 cx: &mut App,
20325 ) -> Option<Task<Result<ProjectTransaction>>> {
20326 Some(self.update(cx, |project, cx| {
20327 project.perform_rename(buffer.clone(), position, new_name, cx)
20328 }))
20329 }
20330}
20331
20332fn inlay_hint_settings(
20333 location: Anchor,
20334 snapshot: &MultiBufferSnapshot,
20335 cx: &mut Context<Editor>,
20336) -> InlayHintSettings {
20337 let file = snapshot.file_at(location);
20338 let language = snapshot.language_at(location).map(|l| l.name());
20339 language_settings(language, file, cx).inlay_hints
20340}
20341
20342fn consume_contiguous_rows(
20343 contiguous_row_selections: &mut Vec<Selection<Point>>,
20344 selection: &Selection<Point>,
20345 display_map: &DisplaySnapshot,
20346 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20347) -> (MultiBufferRow, MultiBufferRow) {
20348 contiguous_row_selections.push(selection.clone());
20349 let start_row = MultiBufferRow(selection.start.row);
20350 let mut end_row = ending_row(selection, display_map);
20351
20352 while let Some(next_selection) = selections.peek() {
20353 if next_selection.start.row <= end_row.0 {
20354 end_row = ending_row(next_selection, display_map);
20355 contiguous_row_selections.push(selections.next().unwrap().clone());
20356 } else {
20357 break;
20358 }
20359 }
20360 (start_row, end_row)
20361}
20362
20363fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20364 if next_selection.end.column > 0 || next_selection.is_empty() {
20365 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20366 } else {
20367 MultiBufferRow(next_selection.end.row)
20368 }
20369}
20370
20371impl EditorSnapshot {
20372 pub fn remote_selections_in_range<'a>(
20373 &'a self,
20374 range: &'a Range<Anchor>,
20375 collaboration_hub: &dyn CollaborationHub,
20376 cx: &'a App,
20377 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20378 let participant_names = collaboration_hub.user_names(cx);
20379 let participant_indices = collaboration_hub.user_participant_indices(cx);
20380 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20381 let collaborators_by_replica_id = collaborators_by_peer_id
20382 .values()
20383 .map(|collaborator| (collaborator.replica_id, collaborator))
20384 .collect::<HashMap<_, _>>();
20385 self.buffer_snapshot
20386 .selections_in_range(range, false)
20387 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20388 if replica_id == AGENT_REPLICA_ID {
20389 Some(RemoteSelection {
20390 replica_id,
20391 selection,
20392 cursor_shape,
20393 line_mode,
20394 collaborator_id: CollaboratorId::Agent,
20395 user_name: Some("Agent".into()),
20396 color: cx.theme().players().agent(),
20397 })
20398 } else {
20399 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20400 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20401 let user_name = participant_names.get(&collaborator.user_id).cloned();
20402 Some(RemoteSelection {
20403 replica_id,
20404 selection,
20405 cursor_shape,
20406 line_mode,
20407 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20408 user_name,
20409 color: if let Some(index) = participant_index {
20410 cx.theme().players().color_for_participant(index.0)
20411 } else {
20412 cx.theme().players().absent()
20413 },
20414 })
20415 }
20416 })
20417 }
20418
20419 pub fn hunks_for_ranges(
20420 &self,
20421 ranges: impl IntoIterator<Item = Range<Point>>,
20422 ) -> Vec<MultiBufferDiffHunk> {
20423 let mut hunks = Vec::new();
20424 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20425 HashMap::default();
20426 for query_range in ranges {
20427 let query_rows =
20428 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20429 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20430 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20431 ) {
20432 // Include deleted hunks that are adjacent to the query range, because
20433 // otherwise they would be missed.
20434 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20435 if hunk.status().is_deleted() {
20436 intersects_range |= hunk.row_range.start == query_rows.end;
20437 intersects_range |= hunk.row_range.end == query_rows.start;
20438 }
20439 if intersects_range {
20440 if !processed_buffer_rows
20441 .entry(hunk.buffer_id)
20442 .or_default()
20443 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20444 {
20445 continue;
20446 }
20447 hunks.push(hunk);
20448 }
20449 }
20450 }
20451
20452 hunks
20453 }
20454
20455 fn display_diff_hunks_for_rows<'a>(
20456 &'a self,
20457 display_rows: Range<DisplayRow>,
20458 folded_buffers: &'a HashSet<BufferId>,
20459 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20460 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20461 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20462
20463 self.buffer_snapshot
20464 .diff_hunks_in_range(buffer_start..buffer_end)
20465 .filter_map(|hunk| {
20466 if folded_buffers.contains(&hunk.buffer_id) {
20467 return None;
20468 }
20469
20470 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20471 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20472
20473 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20474 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20475
20476 let display_hunk = if hunk_display_start.column() != 0 {
20477 DisplayDiffHunk::Folded {
20478 display_row: hunk_display_start.row(),
20479 }
20480 } else {
20481 let mut end_row = hunk_display_end.row();
20482 if hunk_display_end.column() > 0 {
20483 end_row.0 += 1;
20484 }
20485 let is_created_file = hunk.is_created_file();
20486 DisplayDiffHunk::Unfolded {
20487 status: hunk.status(),
20488 diff_base_byte_range: hunk.diff_base_byte_range,
20489 display_row_range: hunk_display_start.row()..end_row,
20490 multi_buffer_range: Anchor::range_in_buffer(
20491 hunk.excerpt_id,
20492 hunk.buffer_id,
20493 hunk.buffer_range,
20494 ),
20495 is_created_file,
20496 }
20497 };
20498
20499 Some(display_hunk)
20500 })
20501 }
20502
20503 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20504 self.display_snapshot.buffer_snapshot.language_at(position)
20505 }
20506
20507 pub fn is_focused(&self) -> bool {
20508 self.is_focused
20509 }
20510
20511 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20512 self.placeholder_text.as_ref()
20513 }
20514
20515 pub fn scroll_position(&self) -> gpui::Point<f32> {
20516 self.scroll_anchor.scroll_position(&self.display_snapshot)
20517 }
20518
20519 fn gutter_dimensions(
20520 &self,
20521 font_id: FontId,
20522 font_size: Pixels,
20523 max_line_number_width: Pixels,
20524 cx: &App,
20525 ) -> Option<GutterDimensions> {
20526 if !self.show_gutter {
20527 return None;
20528 }
20529
20530 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20531 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20532
20533 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20534 matches!(
20535 ProjectSettings::get_global(cx).git.git_gutter,
20536 Some(GitGutterSetting::TrackedFiles)
20537 )
20538 });
20539 let gutter_settings = EditorSettings::get_global(cx).gutter;
20540 let show_line_numbers = self
20541 .show_line_numbers
20542 .unwrap_or(gutter_settings.line_numbers);
20543 let line_gutter_width = if show_line_numbers {
20544 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20545 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20546 max_line_number_width.max(min_width_for_number_on_gutter)
20547 } else {
20548 0.0.into()
20549 };
20550
20551 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20552 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20553
20554 let git_blame_entries_width =
20555 self.git_blame_gutter_max_author_length
20556 .map(|max_author_length| {
20557 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20558 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20559
20560 /// The number of characters to dedicate to gaps and margins.
20561 const SPACING_WIDTH: usize = 4;
20562
20563 let max_char_count = max_author_length.min(renderer.max_author_length())
20564 + ::git::SHORT_SHA_LENGTH
20565 + MAX_RELATIVE_TIMESTAMP.len()
20566 + SPACING_WIDTH;
20567
20568 em_advance * max_char_count
20569 });
20570
20571 let is_singleton = self.buffer_snapshot.is_singleton();
20572
20573 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20574 left_padding += if !is_singleton {
20575 em_width * 4.0
20576 } else if show_runnables || show_breakpoints {
20577 em_width * 3.0
20578 } else if show_git_gutter && show_line_numbers {
20579 em_width * 2.0
20580 } else if show_git_gutter || show_line_numbers {
20581 em_width
20582 } else {
20583 px(0.)
20584 };
20585
20586 let shows_folds = is_singleton && gutter_settings.folds;
20587
20588 let right_padding = if shows_folds && show_line_numbers {
20589 em_width * 4.0
20590 } else if shows_folds || (!is_singleton && show_line_numbers) {
20591 em_width * 3.0
20592 } else if show_line_numbers {
20593 em_width
20594 } else {
20595 px(0.)
20596 };
20597
20598 Some(GutterDimensions {
20599 left_padding,
20600 right_padding,
20601 width: line_gutter_width + left_padding + right_padding,
20602 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20603 git_blame_entries_width,
20604 })
20605 }
20606
20607 pub fn render_crease_toggle(
20608 &self,
20609 buffer_row: MultiBufferRow,
20610 row_contains_cursor: bool,
20611 editor: Entity<Editor>,
20612 window: &mut Window,
20613 cx: &mut App,
20614 ) -> Option<AnyElement> {
20615 let folded = self.is_line_folded(buffer_row);
20616 let mut is_foldable = false;
20617
20618 if let Some(crease) = self
20619 .crease_snapshot
20620 .query_row(buffer_row, &self.buffer_snapshot)
20621 {
20622 is_foldable = true;
20623 match crease {
20624 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20625 if let Some(render_toggle) = render_toggle {
20626 let toggle_callback =
20627 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20628 if folded {
20629 editor.update(cx, |editor, cx| {
20630 editor.fold_at(buffer_row, window, cx)
20631 });
20632 } else {
20633 editor.update(cx, |editor, cx| {
20634 editor.unfold_at(buffer_row, window, cx)
20635 });
20636 }
20637 });
20638 return Some((render_toggle)(
20639 buffer_row,
20640 folded,
20641 toggle_callback,
20642 window,
20643 cx,
20644 ));
20645 }
20646 }
20647 }
20648 }
20649
20650 is_foldable |= self.starts_indent(buffer_row);
20651
20652 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20653 Some(
20654 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20655 .toggle_state(folded)
20656 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20657 if folded {
20658 this.unfold_at(buffer_row, window, cx);
20659 } else {
20660 this.fold_at(buffer_row, window, cx);
20661 }
20662 }))
20663 .into_any_element(),
20664 )
20665 } else {
20666 None
20667 }
20668 }
20669
20670 pub fn render_crease_trailer(
20671 &self,
20672 buffer_row: MultiBufferRow,
20673 window: &mut Window,
20674 cx: &mut App,
20675 ) -> Option<AnyElement> {
20676 let folded = self.is_line_folded(buffer_row);
20677 if let Crease::Inline { render_trailer, .. } = self
20678 .crease_snapshot
20679 .query_row(buffer_row, &self.buffer_snapshot)?
20680 {
20681 let render_trailer = render_trailer.as_ref()?;
20682 Some(render_trailer(buffer_row, folded, window, cx))
20683 } else {
20684 None
20685 }
20686 }
20687}
20688
20689impl Deref for EditorSnapshot {
20690 type Target = DisplaySnapshot;
20691
20692 fn deref(&self) -> &Self::Target {
20693 &self.display_snapshot
20694 }
20695}
20696
20697#[derive(Clone, Debug, PartialEq, Eq)]
20698pub enum EditorEvent {
20699 InputIgnored {
20700 text: Arc<str>,
20701 },
20702 InputHandled {
20703 utf16_range_to_replace: Option<Range<isize>>,
20704 text: Arc<str>,
20705 },
20706 ExcerptsAdded {
20707 buffer: Entity<Buffer>,
20708 predecessor: ExcerptId,
20709 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20710 },
20711 ExcerptsRemoved {
20712 ids: Vec<ExcerptId>,
20713 removed_buffer_ids: Vec<BufferId>,
20714 },
20715 BufferFoldToggled {
20716 ids: Vec<ExcerptId>,
20717 folded: bool,
20718 },
20719 ExcerptsEdited {
20720 ids: Vec<ExcerptId>,
20721 },
20722 ExcerptsExpanded {
20723 ids: Vec<ExcerptId>,
20724 },
20725 BufferEdited,
20726 Edited {
20727 transaction_id: clock::Lamport,
20728 },
20729 Reparsed(BufferId),
20730 Focused,
20731 FocusedIn,
20732 Blurred,
20733 DirtyChanged,
20734 Saved,
20735 TitleChanged,
20736 DiffBaseChanged,
20737 SelectionsChanged {
20738 local: bool,
20739 },
20740 ScrollPositionChanged {
20741 local: bool,
20742 autoscroll: bool,
20743 },
20744 Closed,
20745 TransactionUndone {
20746 transaction_id: clock::Lamport,
20747 },
20748 TransactionBegun {
20749 transaction_id: clock::Lamport,
20750 },
20751 Reloaded,
20752 CursorShapeChanged,
20753 PushedToNavHistory {
20754 anchor: Anchor,
20755 is_deactivate: bool,
20756 },
20757}
20758
20759impl EventEmitter<EditorEvent> for Editor {}
20760
20761impl Focusable for Editor {
20762 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20763 self.focus_handle.clone()
20764 }
20765}
20766
20767impl Render for Editor {
20768 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20769 let settings = ThemeSettings::get_global(cx);
20770
20771 let mut text_style = match self.mode {
20772 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20773 color: cx.theme().colors().editor_foreground,
20774 font_family: settings.ui_font.family.clone(),
20775 font_features: settings.ui_font.features.clone(),
20776 font_fallbacks: settings.ui_font.fallbacks.clone(),
20777 font_size: rems(0.875).into(),
20778 font_weight: settings.ui_font.weight,
20779 line_height: relative(settings.buffer_line_height.value()),
20780 ..Default::default()
20781 },
20782 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20783 color: cx.theme().colors().editor_foreground,
20784 font_family: settings.buffer_font.family.clone(),
20785 font_features: settings.buffer_font.features.clone(),
20786 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20787 font_size: settings.buffer_font_size(cx).into(),
20788 font_weight: settings.buffer_font.weight,
20789 line_height: relative(settings.buffer_line_height.value()),
20790 ..Default::default()
20791 },
20792 };
20793 if let Some(text_style_refinement) = &self.text_style_refinement {
20794 text_style.refine(text_style_refinement)
20795 }
20796
20797 let background = match self.mode {
20798 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20799 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20800 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20801 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20802 };
20803
20804 EditorElement::new(
20805 &cx.entity(),
20806 EditorStyle {
20807 background,
20808 local_player: cx.theme().players().local(),
20809 text: text_style,
20810 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20811 syntax: cx.theme().syntax().clone(),
20812 status: cx.theme().status().clone(),
20813 inlay_hints_style: make_inlay_hints_style(cx),
20814 inline_completion_styles: make_suggestion_styles(cx),
20815 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20816 show_underlines: !self.mode.is_minimap(),
20817 },
20818 )
20819 }
20820}
20821
20822impl EntityInputHandler for Editor {
20823 fn text_for_range(
20824 &mut self,
20825 range_utf16: Range<usize>,
20826 adjusted_range: &mut Option<Range<usize>>,
20827 _: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) -> Option<String> {
20830 let snapshot = self.buffer.read(cx).read(cx);
20831 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20832 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20833 if (start.0..end.0) != range_utf16 {
20834 adjusted_range.replace(start.0..end.0);
20835 }
20836 Some(snapshot.text_for_range(start..end).collect())
20837 }
20838
20839 fn selected_text_range(
20840 &mut self,
20841 ignore_disabled_input: bool,
20842 _: &mut Window,
20843 cx: &mut Context<Self>,
20844 ) -> Option<UTF16Selection> {
20845 // Prevent the IME menu from appearing when holding down an alphabetic key
20846 // while input is disabled.
20847 if !ignore_disabled_input && !self.input_enabled {
20848 return None;
20849 }
20850
20851 let selection = self.selections.newest::<OffsetUtf16>(cx);
20852 let range = selection.range();
20853
20854 Some(UTF16Selection {
20855 range: range.start.0..range.end.0,
20856 reversed: selection.reversed,
20857 })
20858 }
20859
20860 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20861 let snapshot = self.buffer.read(cx).read(cx);
20862 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20863 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20864 }
20865
20866 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20867 self.clear_highlights::<InputComposition>(cx);
20868 self.ime_transaction.take();
20869 }
20870
20871 fn replace_text_in_range(
20872 &mut self,
20873 range_utf16: Option<Range<usize>>,
20874 text: &str,
20875 window: &mut Window,
20876 cx: &mut Context<Self>,
20877 ) {
20878 if !self.input_enabled {
20879 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20880 return;
20881 }
20882
20883 self.transact(window, cx, |this, window, cx| {
20884 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20885 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20886 Some(this.selection_replacement_ranges(range_utf16, cx))
20887 } else {
20888 this.marked_text_ranges(cx)
20889 };
20890
20891 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20892 let newest_selection_id = this.selections.newest_anchor().id;
20893 this.selections
20894 .all::<OffsetUtf16>(cx)
20895 .iter()
20896 .zip(ranges_to_replace.iter())
20897 .find_map(|(selection, range)| {
20898 if selection.id == newest_selection_id {
20899 Some(
20900 (range.start.0 as isize - selection.head().0 as isize)
20901 ..(range.end.0 as isize - selection.head().0 as isize),
20902 )
20903 } else {
20904 None
20905 }
20906 })
20907 });
20908
20909 cx.emit(EditorEvent::InputHandled {
20910 utf16_range_to_replace: range_to_replace,
20911 text: text.into(),
20912 });
20913
20914 if let Some(new_selected_ranges) = new_selected_ranges {
20915 this.change_selections(None, window, cx, |selections| {
20916 selections.select_ranges(new_selected_ranges)
20917 });
20918 this.backspace(&Default::default(), window, cx);
20919 }
20920
20921 this.handle_input(text, window, cx);
20922 });
20923
20924 if let Some(transaction) = self.ime_transaction {
20925 self.buffer.update(cx, |buffer, cx| {
20926 buffer.group_until_transaction(transaction, cx);
20927 });
20928 }
20929
20930 self.unmark_text(window, cx);
20931 }
20932
20933 fn replace_and_mark_text_in_range(
20934 &mut self,
20935 range_utf16: Option<Range<usize>>,
20936 text: &str,
20937 new_selected_range_utf16: Option<Range<usize>>,
20938 window: &mut Window,
20939 cx: &mut Context<Self>,
20940 ) {
20941 if !self.input_enabled {
20942 return;
20943 }
20944
20945 let transaction = self.transact(window, cx, |this, window, cx| {
20946 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20947 let snapshot = this.buffer.read(cx).read(cx);
20948 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20949 for marked_range in &mut marked_ranges {
20950 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20951 marked_range.start.0 += relative_range_utf16.start;
20952 marked_range.start =
20953 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20954 marked_range.end =
20955 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20956 }
20957 }
20958 Some(marked_ranges)
20959 } else if let Some(range_utf16) = range_utf16 {
20960 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20961 Some(this.selection_replacement_ranges(range_utf16, cx))
20962 } else {
20963 None
20964 };
20965
20966 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20967 let newest_selection_id = this.selections.newest_anchor().id;
20968 this.selections
20969 .all::<OffsetUtf16>(cx)
20970 .iter()
20971 .zip(ranges_to_replace.iter())
20972 .find_map(|(selection, range)| {
20973 if selection.id == newest_selection_id {
20974 Some(
20975 (range.start.0 as isize - selection.head().0 as isize)
20976 ..(range.end.0 as isize - selection.head().0 as isize),
20977 )
20978 } else {
20979 None
20980 }
20981 })
20982 });
20983
20984 cx.emit(EditorEvent::InputHandled {
20985 utf16_range_to_replace: range_to_replace,
20986 text: text.into(),
20987 });
20988
20989 if let Some(ranges) = ranges_to_replace {
20990 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20991 }
20992
20993 let marked_ranges = {
20994 let snapshot = this.buffer.read(cx).read(cx);
20995 this.selections
20996 .disjoint_anchors()
20997 .iter()
20998 .map(|selection| {
20999 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21000 })
21001 .collect::<Vec<_>>()
21002 };
21003
21004 if text.is_empty() {
21005 this.unmark_text(window, cx);
21006 } else {
21007 this.highlight_text::<InputComposition>(
21008 marked_ranges.clone(),
21009 HighlightStyle {
21010 underline: Some(UnderlineStyle {
21011 thickness: px(1.),
21012 color: None,
21013 wavy: false,
21014 }),
21015 ..Default::default()
21016 },
21017 cx,
21018 );
21019 }
21020
21021 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21022 let use_autoclose = this.use_autoclose;
21023 let use_auto_surround = this.use_auto_surround;
21024 this.set_use_autoclose(false);
21025 this.set_use_auto_surround(false);
21026 this.handle_input(text, window, cx);
21027 this.set_use_autoclose(use_autoclose);
21028 this.set_use_auto_surround(use_auto_surround);
21029
21030 if let Some(new_selected_range) = new_selected_range_utf16 {
21031 let snapshot = this.buffer.read(cx).read(cx);
21032 let new_selected_ranges = marked_ranges
21033 .into_iter()
21034 .map(|marked_range| {
21035 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21036 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21037 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21038 snapshot.clip_offset_utf16(new_start, Bias::Left)
21039 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21040 })
21041 .collect::<Vec<_>>();
21042
21043 drop(snapshot);
21044 this.change_selections(None, window, cx, |selections| {
21045 selections.select_ranges(new_selected_ranges)
21046 });
21047 }
21048 });
21049
21050 self.ime_transaction = self.ime_transaction.or(transaction);
21051 if let Some(transaction) = self.ime_transaction {
21052 self.buffer.update(cx, |buffer, cx| {
21053 buffer.group_until_transaction(transaction, cx);
21054 });
21055 }
21056
21057 if self.text_highlights::<InputComposition>(cx).is_none() {
21058 self.ime_transaction.take();
21059 }
21060 }
21061
21062 fn bounds_for_range(
21063 &mut self,
21064 range_utf16: Range<usize>,
21065 element_bounds: gpui::Bounds<Pixels>,
21066 window: &mut Window,
21067 cx: &mut Context<Self>,
21068 ) -> Option<gpui::Bounds<Pixels>> {
21069 let text_layout_details = self.text_layout_details(window);
21070 let gpui::Size {
21071 width: em_width,
21072 height: line_height,
21073 } = self.character_size(window);
21074
21075 let snapshot = self.snapshot(window, cx);
21076 let scroll_position = snapshot.scroll_position();
21077 let scroll_left = scroll_position.x * em_width;
21078
21079 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21080 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21081 + self.gutter_dimensions.width
21082 + self.gutter_dimensions.margin;
21083 let y = line_height * (start.row().as_f32() - scroll_position.y);
21084
21085 Some(Bounds {
21086 origin: element_bounds.origin + point(x, y),
21087 size: size(em_width, line_height),
21088 })
21089 }
21090
21091 fn character_index_for_point(
21092 &mut self,
21093 point: gpui::Point<Pixels>,
21094 _window: &mut Window,
21095 _cx: &mut Context<Self>,
21096 ) -> Option<usize> {
21097 let position_map = self.last_position_map.as_ref()?;
21098 if !position_map.text_hitbox.contains(&point) {
21099 return None;
21100 }
21101 let display_point = position_map.point_for_position(point).previous_valid;
21102 let anchor = position_map
21103 .snapshot
21104 .display_point_to_anchor(display_point, Bias::Left);
21105 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21106 Some(utf16_offset.0)
21107 }
21108}
21109
21110trait SelectionExt {
21111 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21112 fn spanned_rows(
21113 &self,
21114 include_end_if_at_line_start: bool,
21115 map: &DisplaySnapshot,
21116 ) -> Range<MultiBufferRow>;
21117}
21118
21119impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21120 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21121 let start = self
21122 .start
21123 .to_point(&map.buffer_snapshot)
21124 .to_display_point(map);
21125 let end = self
21126 .end
21127 .to_point(&map.buffer_snapshot)
21128 .to_display_point(map);
21129 if self.reversed {
21130 end..start
21131 } else {
21132 start..end
21133 }
21134 }
21135
21136 fn spanned_rows(
21137 &self,
21138 include_end_if_at_line_start: bool,
21139 map: &DisplaySnapshot,
21140 ) -> Range<MultiBufferRow> {
21141 let start = self.start.to_point(&map.buffer_snapshot);
21142 let mut end = self.end.to_point(&map.buffer_snapshot);
21143 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21144 end.row -= 1;
21145 }
21146
21147 let buffer_start = map.prev_line_boundary(start).0;
21148 let buffer_end = map.next_line_boundary(end).0;
21149 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21150 }
21151}
21152
21153impl<T: InvalidationRegion> InvalidationStack<T> {
21154 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21155 where
21156 S: Clone + ToOffset,
21157 {
21158 while let Some(region) = self.last() {
21159 let all_selections_inside_invalidation_ranges =
21160 if selections.len() == region.ranges().len() {
21161 selections
21162 .iter()
21163 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21164 .all(|(selection, invalidation_range)| {
21165 let head = selection.head().to_offset(buffer);
21166 invalidation_range.start <= head && invalidation_range.end >= head
21167 })
21168 } else {
21169 false
21170 };
21171
21172 if all_selections_inside_invalidation_ranges {
21173 break;
21174 } else {
21175 self.pop();
21176 }
21177 }
21178 }
21179}
21180
21181impl<T> Default for InvalidationStack<T> {
21182 fn default() -> Self {
21183 Self(Default::default())
21184 }
21185}
21186
21187impl<T> Deref for InvalidationStack<T> {
21188 type Target = Vec<T>;
21189
21190 fn deref(&self) -> &Self::Target {
21191 &self.0
21192 }
21193}
21194
21195impl<T> DerefMut for InvalidationStack<T> {
21196 fn deref_mut(&mut self) -> &mut Self::Target {
21197 &mut self.0
21198 }
21199}
21200
21201impl InvalidationRegion for SnippetState {
21202 fn ranges(&self) -> &[Range<Anchor>] {
21203 &self.ranges[self.active_index]
21204 }
21205}
21206
21207fn inline_completion_edit_text(
21208 current_snapshot: &BufferSnapshot,
21209 edits: &[(Range<Anchor>, String)],
21210 edit_preview: &EditPreview,
21211 include_deletions: bool,
21212 cx: &App,
21213) -> HighlightedText {
21214 let edits = edits
21215 .iter()
21216 .map(|(anchor, text)| {
21217 (
21218 anchor.start.text_anchor..anchor.end.text_anchor,
21219 text.clone(),
21220 )
21221 })
21222 .collect::<Vec<_>>();
21223
21224 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21225}
21226
21227pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21228 match severity {
21229 lsp::DiagnosticSeverity::ERROR => colors.error,
21230 lsp::DiagnosticSeverity::WARNING => colors.warning,
21231 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21232 lsp::DiagnosticSeverity::HINT => colors.info,
21233 _ => colors.ignored,
21234 }
21235}
21236
21237pub fn styled_runs_for_code_label<'a>(
21238 label: &'a CodeLabel,
21239 syntax_theme: &'a theme::SyntaxTheme,
21240) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21241 let fade_out = HighlightStyle {
21242 fade_out: Some(0.35),
21243 ..Default::default()
21244 };
21245
21246 let mut prev_end = label.filter_range.end;
21247 label
21248 .runs
21249 .iter()
21250 .enumerate()
21251 .flat_map(move |(ix, (range, highlight_id))| {
21252 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21253 style
21254 } else {
21255 return Default::default();
21256 };
21257 let mut muted_style = style;
21258 muted_style.highlight(fade_out);
21259
21260 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21261 if range.start >= label.filter_range.end {
21262 if range.start > prev_end {
21263 runs.push((prev_end..range.start, fade_out));
21264 }
21265 runs.push((range.clone(), muted_style));
21266 } else if range.end <= label.filter_range.end {
21267 runs.push((range.clone(), style));
21268 } else {
21269 runs.push((range.start..label.filter_range.end, style));
21270 runs.push((label.filter_range.end..range.end, muted_style));
21271 }
21272 prev_end = cmp::max(prev_end, range.end);
21273
21274 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21275 runs.push((prev_end..label.text.len(), fade_out));
21276 }
21277
21278 runs
21279 })
21280}
21281
21282pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21283 let mut prev_index = 0;
21284 let mut prev_codepoint: Option<char> = None;
21285 text.char_indices()
21286 .chain([(text.len(), '\0')])
21287 .filter_map(move |(index, codepoint)| {
21288 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21289 let is_boundary = index == text.len()
21290 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21291 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21292 if is_boundary {
21293 let chunk = &text[prev_index..index];
21294 prev_index = index;
21295 Some(chunk)
21296 } else {
21297 None
21298 }
21299 })
21300}
21301
21302pub trait RangeToAnchorExt: Sized {
21303 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21304
21305 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21306 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21307 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21308 }
21309}
21310
21311impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21312 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21313 let start_offset = self.start.to_offset(snapshot);
21314 let end_offset = self.end.to_offset(snapshot);
21315 if start_offset == end_offset {
21316 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21317 } else {
21318 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21319 }
21320 }
21321}
21322
21323pub trait RowExt {
21324 fn as_f32(&self) -> f32;
21325
21326 fn next_row(&self) -> Self;
21327
21328 fn previous_row(&self) -> Self;
21329
21330 fn minus(&self, other: Self) -> u32;
21331}
21332
21333impl RowExt for DisplayRow {
21334 fn as_f32(&self) -> f32 {
21335 self.0 as f32
21336 }
21337
21338 fn next_row(&self) -> Self {
21339 Self(self.0 + 1)
21340 }
21341
21342 fn previous_row(&self) -> Self {
21343 Self(self.0.saturating_sub(1))
21344 }
21345
21346 fn minus(&self, other: Self) -> u32 {
21347 self.0 - other.0
21348 }
21349}
21350
21351impl RowExt for MultiBufferRow {
21352 fn as_f32(&self) -> f32 {
21353 self.0 as f32
21354 }
21355
21356 fn next_row(&self) -> Self {
21357 Self(self.0 + 1)
21358 }
21359
21360 fn previous_row(&self) -> Self {
21361 Self(self.0.saturating_sub(1))
21362 }
21363
21364 fn minus(&self, other: Self) -> u32 {
21365 self.0 - other.0
21366 }
21367}
21368
21369trait RowRangeExt {
21370 type Row;
21371
21372 fn len(&self) -> usize;
21373
21374 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21375}
21376
21377impl RowRangeExt for Range<MultiBufferRow> {
21378 type Row = MultiBufferRow;
21379
21380 fn len(&self) -> usize {
21381 (self.end.0 - self.start.0) as usize
21382 }
21383
21384 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21385 (self.start.0..self.end.0).map(MultiBufferRow)
21386 }
21387}
21388
21389impl RowRangeExt for Range<DisplayRow> {
21390 type Row = DisplayRow;
21391
21392 fn len(&self) -> usize {
21393 (self.end.0 - self.start.0) as usize
21394 }
21395
21396 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21397 (self.start.0..self.end.0).map(DisplayRow)
21398 }
21399}
21400
21401/// If select range has more than one line, we
21402/// just point the cursor to range.start.
21403fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21404 if range.start.row == range.end.row {
21405 range
21406 } else {
21407 range.start..range.start
21408 }
21409}
21410pub struct KillRing(ClipboardItem);
21411impl Global for KillRing {}
21412
21413const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21414
21415enum BreakpointPromptEditAction {
21416 Log,
21417 Condition,
21418 HitCondition,
21419}
21420
21421struct BreakpointPromptEditor {
21422 pub(crate) prompt: Entity<Editor>,
21423 editor: WeakEntity<Editor>,
21424 breakpoint_anchor: Anchor,
21425 breakpoint: Breakpoint,
21426 edit_action: BreakpointPromptEditAction,
21427 block_ids: HashSet<CustomBlockId>,
21428 editor_margins: Arc<Mutex<EditorMargins>>,
21429 _subscriptions: Vec<Subscription>,
21430}
21431
21432impl BreakpointPromptEditor {
21433 const MAX_LINES: u8 = 4;
21434
21435 fn new(
21436 editor: WeakEntity<Editor>,
21437 breakpoint_anchor: Anchor,
21438 breakpoint: Breakpoint,
21439 edit_action: BreakpointPromptEditAction,
21440 window: &mut Window,
21441 cx: &mut Context<Self>,
21442 ) -> Self {
21443 let base_text = match edit_action {
21444 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21445 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21446 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21447 }
21448 .map(|msg| msg.to_string())
21449 .unwrap_or_default();
21450
21451 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21452 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21453
21454 let prompt = cx.new(|cx| {
21455 let mut prompt = Editor::new(
21456 EditorMode::AutoHeight {
21457 max_lines: Self::MAX_LINES as usize,
21458 },
21459 buffer,
21460 None,
21461 window,
21462 cx,
21463 );
21464 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21465 prompt.set_show_cursor_when_unfocused(false, cx);
21466 prompt.set_placeholder_text(
21467 match edit_action {
21468 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21469 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21470 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21471 },
21472 cx,
21473 );
21474
21475 prompt
21476 });
21477
21478 Self {
21479 prompt,
21480 editor,
21481 breakpoint_anchor,
21482 breakpoint,
21483 edit_action,
21484 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21485 block_ids: Default::default(),
21486 _subscriptions: vec![],
21487 }
21488 }
21489
21490 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21491 self.block_ids.extend(block_ids)
21492 }
21493
21494 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21495 if let Some(editor) = self.editor.upgrade() {
21496 let message = self
21497 .prompt
21498 .read(cx)
21499 .buffer
21500 .read(cx)
21501 .as_singleton()
21502 .expect("A multi buffer in breakpoint prompt isn't possible")
21503 .read(cx)
21504 .as_rope()
21505 .to_string();
21506
21507 editor.update(cx, |editor, cx| {
21508 editor.edit_breakpoint_at_anchor(
21509 self.breakpoint_anchor,
21510 self.breakpoint.clone(),
21511 match self.edit_action {
21512 BreakpointPromptEditAction::Log => {
21513 BreakpointEditAction::EditLogMessage(message.into())
21514 }
21515 BreakpointPromptEditAction::Condition => {
21516 BreakpointEditAction::EditCondition(message.into())
21517 }
21518 BreakpointPromptEditAction::HitCondition => {
21519 BreakpointEditAction::EditHitCondition(message.into())
21520 }
21521 },
21522 cx,
21523 );
21524
21525 editor.remove_blocks(self.block_ids.clone(), None, cx);
21526 cx.focus_self(window);
21527 });
21528 }
21529 }
21530
21531 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21532 self.editor
21533 .update(cx, |editor, cx| {
21534 editor.remove_blocks(self.block_ids.clone(), None, cx);
21535 window.focus(&editor.focus_handle);
21536 })
21537 .log_err();
21538 }
21539
21540 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21541 let settings = ThemeSettings::get_global(cx);
21542 let text_style = TextStyle {
21543 color: if self.prompt.read(cx).read_only(cx) {
21544 cx.theme().colors().text_disabled
21545 } else {
21546 cx.theme().colors().text
21547 },
21548 font_family: settings.buffer_font.family.clone(),
21549 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21550 font_size: settings.buffer_font_size(cx).into(),
21551 font_weight: settings.buffer_font.weight,
21552 line_height: relative(settings.buffer_line_height.value()),
21553 ..Default::default()
21554 };
21555 EditorElement::new(
21556 &self.prompt,
21557 EditorStyle {
21558 background: cx.theme().colors().editor_background,
21559 local_player: cx.theme().players().local(),
21560 text: text_style,
21561 ..Default::default()
21562 },
21563 )
21564 }
21565}
21566
21567impl Render for BreakpointPromptEditor {
21568 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21569 let editor_margins = *self.editor_margins.lock();
21570 let gutter_dimensions = editor_margins.gutter;
21571 h_flex()
21572 .key_context("Editor")
21573 .bg(cx.theme().colors().editor_background)
21574 .border_y_1()
21575 .border_color(cx.theme().status().info_border)
21576 .size_full()
21577 .py(window.line_height() / 2.5)
21578 .on_action(cx.listener(Self::confirm))
21579 .on_action(cx.listener(Self::cancel))
21580 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21581 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21582 }
21583}
21584
21585impl Focusable for BreakpointPromptEditor {
21586 fn focus_handle(&self, cx: &App) -> FocusHandle {
21587 self.prompt.focus_handle(cx)
21588 }
21589}
21590
21591fn all_edits_insertions_or_deletions(
21592 edits: &Vec<(Range<Anchor>, String)>,
21593 snapshot: &MultiBufferSnapshot,
21594) -> bool {
21595 let mut all_insertions = true;
21596 let mut all_deletions = true;
21597
21598 for (range, new_text) in edits.iter() {
21599 let range_is_empty = range.to_offset(&snapshot).is_empty();
21600 let text_is_empty = new_text.is_empty();
21601
21602 if range_is_empty != text_is_empty {
21603 if range_is_empty {
21604 all_deletions = false;
21605 } else {
21606 all_insertions = false;
21607 }
21608 } else {
21609 return false;
21610 }
21611
21612 if !all_insertions && !all_deletions {
21613 return false;
21614 }
21615 }
21616 all_insertions || all_deletions
21617}
21618
21619struct MissingEditPredictionKeybindingTooltip;
21620
21621impl Render for MissingEditPredictionKeybindingTooltip {
21622 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21623 ui::tooltip_container(window, cx, |container, _, cx| {
21624 container
21625 .flex_shrink_0()
21626 .max_w_80()
21627 .min_h(rems_from_px(124.))
21628 .justify_between()
21629 .child(
21630 v_flex()
21631 .flex_1()
21632 .text_ui_sm(cx)
21633 .child(Label::new("Conflict with Accept Keybinding"))
21634 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21635 )
21636 .child(
21637 h_flex()
21638 .pb_1()
21639 .gap_1()
21640 .items_end()
21641 .w_full()
21642 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21643 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21644 }))
21645 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21646 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21647 })),
21648 )
21649 })
21650 }
21651}
21652
21653#[derive(Debug, Clone, Copy, PartialEq)]
21654pub struct LineHighlight {
21655 pub background: Background,
21656 pub border: Option<gpui::Hsla>,
21657 pub include_gutter: bool,
21658 pub type_id: Option<TypeId>,
21659}
21660
21661fn render_diff_hunk_controls(
21662 row: u32,
21663 status: &DiffHunkStatus,
21664 hunk_range: Range<Anchor>,
21665 is_created_file: bool,
21666 line_height: Pixels,
21667 editor: &Entity<Editor>,
21668 _window: &mut Window,
21669 cx: &mut App,
21670) -> AnyElement {
21671 h_flex()
21672 .h(line_height)
21673 .mr_1()
21674 .gap_1()
21675 .px_0p5()
21676 .pb_1()
21677 .border_x_1()
21678 .border_b_1()
21679 .border_color(cx.theme().colors().border_variant)
21680 .rounded_b_lg()
21681 .bg(cx.theme().colors().editor_background)
21682 .gap_1()
21683 .occlude()
21684 .shadow_md()
21685 .child(if status.has_secondary_hunk() {
21686 Button::new(("stage", row as u64), "Stage")
21687 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21688 .tooltip({
21689 let focus_handle = editor.focus_handle(cx);
21690 move |window, cx| {
21691 Tooltip::for_action_in(
21692 "Stage Hunk",
21693 &::git::ToggleStaged,
21694 &focus_handle,
21695 window,
21696 cx,
21697 )
21698 }
21699 })
21700 .on_click({
21701 let editor = editor.clone();
21702 move |_event, _window, cx| {
21703 editor.update(cx, |editor, cx| {
21704 editor.stage_or_unstage_diff_hunks(
21705 true,
21706 vec![hunk_range.start..hunk_range.start],
21707 cx,
21708 );
21709 });
21710 }
21711 })
21712 } else {
21713 Button::new(("unstage", row as u64), "Unstage")
21714 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21715 .tooltip({
21716 let focus_handle = editor.focus_handle(cx);
21717 move |window, cx| {
21718 Tooltip::for_action_in(
21719 "Unstage Hunk",
21720 &::git::ToggleStaged,
21721 &focus_handle,
21722 window,
21723 cx,
21724 )
21725 }
21726 })
21727 .on_click({
21728 let editor = editor.clone();
21729 move |_event, _window, cx| {
21730 editor.update(cx, |editor, cx| {
21731 editor.stage_or_unstage_diff_hunks(
21732 false,
21733 vec![hunk_range.start..hunk_range.start],
21734 cx,
21735 );
21736 });
21737 }
21738 })
21739 })
21740 .child(
21741 Button::new(("restore", row as u64), "Restore")
21742 .tooltip({
21743 let focus_handle = editor.focus_handle(cx);
21744 move |window, cx| {
21745 Tooltip::for_action_in(
21746 "Restore Hunk",
21747 &::git::Restore,
21748 &focus_handle,
21749 window,
21750 cx,
21751 )
21752 }
21753 })
21754 .on_click({
21755 let editor = editor.clone();
21756 move |_event, window, cx| {
21757 editor.update(cx, |editor, cx| {
21758 let snapshot = editor.snapshot(window, cx);
21759 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21760 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21761 });
21762 }
21763 })
21764 .disabled(is_created_file),
21765 )
21766 .when(
21767 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21768 |el| {
21769 el.child(
21770 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21771 .shape(IconButtonShape::Square)
21772 .icon_size(IconSize::Small)
21773 // .disabled(!has_multiple_hunks)
21774 .tooltip({
21775 let focus_handle = editor.focus_handle(cx);
21776 move |window, cx| {
21777 Tooltip::for_action_in(
21778 "Next Hunk",
21779 &GoToHunk,
21780 &focus_handle,
21781 window,
21782 cx,
21783 )
21784 }
21785 })
21786 .on_click({
21787 let editor = editor.clone();
21788 move |_event, window, cx| {
21789 editor.update(cx, |editor, cx| {
21790 let snapshot = editor.snapshot(window, cx);
21791 let position =
21792 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21793 editor.go_to_hunk_before_or_after_position(
21794 &snapshot,
21795 position,
21796 Direction::Next,
21797 window,
21798 cx,
21799 );
21800 editor.expand_selected_diff_hunks(cx);
21801 });
21802 }
21803 }),
21804 )
21805 .child(
21806 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21807 .shape(IconButtonShape::Square)
21808 .icon_size(IconSize::Small)
21809 // .disabled(!has_multiple_hunks)
21810 .tooltip({
21811 let focus_handle = editor.focus_handle(cx);
21812 move |window, cx| {
21813 Tooltip::for_action_in(
21814 "Previous Hunk",
21815 &GoToPreviousHunk,
21816 &focus_handle,
21817 window,
21818 cx,
21819 )
21820 }
21821 })
21822 .on_click({
21823 let editor = editor.clone();
21824 move |_event, window, cx| {
21825 editor.update(cx, |editor, cx| {
21826 let snapshot = editor.snapshot(window, cx);
21827 let point =
21828 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21829 editor.go_to_hunk_before_or_after_position(
21830 &snapshot,
21831 point,
21832 Direction::Prev,
21833 window,
21834 cx,
21835 );
21836 editor.expand_selected_diff_hunks(cx);
21837 });
21838 }
21839 }),
21840 )
21841 },
21842 )
21843 .into_any_element()
21844}