1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_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)]
780pub struct 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 == action.deployed_from {
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 = action.deployed_from.clone();
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 = match &action.deployed_from {
5409 Some(CodeActionSource::Indicator(row)) => {
5410 DisplayPoint::new(*row, 0).to_point(&snapshot)
5411 }
5412 _ => editor.selections.newest::<Point>(cx).head(),
5413 };
5414 let (buffer, buffer_row) = snapshot
5415 .buffer_snapshot
5416 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5417 .and_then(|(buffer_snapshot, range)| {
5418 editor
5419 .buffer
5420 .read(cx)
5421 .buffer(buffer_snapshot.remote_id())
5422 .map(|buffer| (buffer, range.start.row))
5423 })?;
5424 let (_, code_actions) = editor
5425 .available_code_actions
5426 .clone()
5427 .and_then(|(location, code_actions)| {
5428 let snapshot = location.buffer.read(cx).snapshot();
5429 let point_range = location.range.to_point(&snapshot);
5430 let point_range = point_range.start.row..=point_range.end.row;
5431 if point_range.contains(&buffer_row) {
5432 Some((location, code_actions))
5433 } else {
5434 None
5435 }
5436 })
5437 .unzip();
5438 let buffer_id = buffer.read(cx).remote_id();
5439 let tasks = editor
5440 .tasks
5441 .get(&(buffer_id, buffer_row))
5442 .map(|t| Arc::new(t.to_owned()));
5443 if tasks.is_none() && code_actions.is_none() {
5444 return None;
5445 }
5446
5447 editor.completion_tasks.clear();
5448 editor.discard_inline_completion(false, cx);
5449 let task_context =
5450 tasks
5451 .as_ref()
5452 .zip(editor.project.clone())
5453 .map(|(tasks, project)| {
5454 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5455 });
5456
5457 Some(cx.spawn_in(window, async move |editor, cx| {
5458 let task_context = match task_context {
5459 Some(task_context) => task_context.await,
5460 None => None,
5461 };
5462 let resolved_tasks =
5463 tasks
5464 .zip(task_context.clone())
5465 .map(|(tasks, task_context)| ResolvedTasks {
5466 templates: tasks.resolve(&task_context).collect(),
5467 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5468 multibuffer_point.row,
5469 tasks.column,
5470 )),
5471 });
5472 let debug_scenarios = editor.update(cx, |editor, cx| {
5473 if cx.has_flag::<DebuggerFeatureFlag>() {
5474 maybe!({
5475 let project = editor.project.as_ref()?;
5476 let dap_store = project.read(cx).dap_store();
5477 let mut scenarios = vec![];
5478 let resolved_tasks = resolved_tasks.as_ref()?;
5479 let buffer = buffer.read(cx);
5480 let language = buffer.language()?;
5481 let file = buffer.file();
5482 let debug_adapter =
5483 language_settings(language.name().into(), file, cx)
5484 .debuggers
5485 .first()
5486 .map(SharedString::from)
5487 .or_else(|| {
5488 language
5489 .config()
5490 .debuggers
5491 .first()
5492 .map(SharedString::from)
5493 })?;
5494
5495 dap_store.update(cx, |dap_store, cx| {
5496 for (_, task) in &resolved_tasks.templates {
5497 if let Some(scenario) = dap_store
5498 .debug_scenario_for_build_task(
5499 task.original_task().clone(),
5500 debug_adapter.clone().into(),
5501 task.display_label().to_owned().into(),
5502 cx,
5503 )
5504 {
5505 scenarios.push(scenario);
5506 }
5507 }
5508 });
5509 Some(scenarios)
5510 })
5511 .unwrap_or_default()
5512 } else {
5513 vec![]
5514 }
5515 })?;
5516 let spawn_straight_away = quick_launch
5517 && resolved_tasks
5518 .as_ref()
5519 .map_or(false, |tasks| tasks.templates.len() == 1)
5520 && code_actions
5521 .as_ref()
5522 .map_or(true, |actions| actions.is_empty())
5523 && debug_scenarios.is_empty();
5524 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5525 crate::hover_popover::hide_hover(editor, cx);
5526 *editor.context_menu.borrow_mut() =
5527 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5528 buffer,
5529 actions: CodeActionContents::new(
5530 resolved_tasks,
5531 code_actions,
5532 debug_scenarios,
5533 task_context.unwrap_or_default(),
5534 ),
5535 selected_item: Default::default(),
5536 scroll_handle: UniformListScrollHandle::default(),
5537 deployed_from,
5538 }));
5539 if spawn_straight_away {
5540 if let Some(task) = editor.confirm_code_action(
5541 &ConfirmCodeAction { item_ix: Some(0) },
5542 window,
5543 cx,
5544 ) {
5545 cx.notify();
5546 return task;
5547 }
5548 }
5549 cx.notify();
5550 Task::ready(Ok(()))
5551 }) {
5552 task.await
5553 } else {
5554 Ok(())
5555 }
5556 }))
5557 } else {
5558 Some(Task::ready(Ok(())))
5559 }
5560 })?;
5561 if let Some(task) = spawned_test_task {
5562 task.await?;
5563 }
5564
5565 anyhow::Ok(())
5566 })
5567 .detach_and_log_err(cx);
5568 }
5569
5570 pub fn confirm_code_action(
5571 &mut self,
5572 action: &ConfirmCodeAction,
5573 window: &mut Window,
5574 cx: &mut Context<Self>,
5575 ) -> Option<Task<Result<()>>> {
5576 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5577
5578 let actions_menu =
5579 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5580 menu
5581 } else {
5582 return None;
5583 };
5584
5585 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5586 let action = actions_menu.actions.get(action_ix)?;
5587 let title = action.label();
5588 let buffer = actions_menu.buffer;
5589 let workspace = self.workspace()?;
5590
5591 match action {
5592 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5593 workspace.update(cx, |workspace, cx| {
5594 workspace.schedule_resolved_task(
5595 task_source_kind,
5596 resolved_task,
5597 false,
5598 window,
5599 cx,
5600 );
5601
5602 Some(Task::ready(Ok(())))
5603 })
5604 }
5605 CodeActionsItem::CodeAction {
5606 excerpt_id,
5607 action,
5608 provider,
5609 } => {
5610 let apply_code_action =
5611 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5612 let workspace = workspace.downgrade();
5613 Some(cx.spawn_in(window, async move |editor, cx| {
5614 let project_transaction = apply_code_action.await?;
5615 Self::open_project_transaction(
5616 &editor,
5617 workspace,
5618 project_transaction,
5619 title,
5620 cx,
5621 )
5622 .await
5623 }))
5624 }
5625 CodeActionsItem::DebugScenario(scenario) => {
5626 let context = actions_menu.actions.context.clone();
5627
5628 workspace.update(cx, |workspace, cx| {
5629 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5630 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5631 });
5632 Some(Task::ready(Ok(())))
5633 }
5634 }
5635 }
5636
5637 pub async fn open_project_transaction(
5638 this: &WeakEntity<Editor>,
5639 workspace: WeakEntity<Workspace>,
5640 transaction: ProjectTransaction,
5641 title: String,
5642 cx: &mut AsyncWindowContext,
5643 ) -> Result<()> {
5644 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5645 cx.update(|_, cx| {
5646 entries.sort_unstable_by_key(|(buffer, _)| {
5647 buffer.read(cx).file().map(|f| f.path().clone())
5648 });
5649 })?;
5650
5651 // If the project transaction's edits are all contained within this editor, then
5652 // avoid opening a new editor to display them.
5653
5654 if let Some((buffer, transaction)) = entries.first() {
5655 if entries.len() == 1 {
5656 let excerpt = this.update(cx, |editor, cx| {
5657 editor
5658 .buffer()
5659 .read(cx)
5660 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5661 })?;
5662 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5663 if excerpted_buffer == *buffer {
5664 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5665 let excerpt_range = excerpt_range.to_offset(buffer);
5666 buffer
5667 .edited_ranges_for_transaction::<usize>(transaction)
5668 .all(|range| {
5669 excerpt_range.start <= range.start
5670 && excerpt_range.end >= range.end
5671 })
5672 })?;
5673
5674 if all_edits_within_excerpt {
5675 return Ok(());
5676 }
5677 }
5678 }
5679 }
5680 } else {
5681 return Ok(());
5682 }
5683
5684 let mut ranges_to_highlight = Vec::new();
5685 let excerpt_buffer = cx.new(|cx| {
5686 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5687 for (buffer_handle, transaction) in &entries {
5688 let edited_ranges = buffer_handle
5689 .read(cx)
5690 .edited_ranges_for_transaction::<Point>(transaction)
5691 .collect::<Vec<_>>();
5692 let (ranges, _) = multibuffer.set_excerpts_for_path(
5693 PathKey::for_buffer(buffer_handle, cx),
5694 buffer_handle.clone(),
5695 edited_ranges,
5696 DEFAULT_MULTIBUFFER_CONTEXT,
5697 cx,
5698 );
5699
5700 ranges_to_highlight.extend(ranges);
5701 }
5702 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5703 multibuffer
5704 })?;
5705
5706 workspace.update_in(cx, |workspace, window, cx| {
5707 let project = workspace.project().clone();
5708 let editor =
5709 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5710 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5711 editor.update(cx, |editor, cx| {
5712 editor.highlight_background::<Self>(
5713 &ranges_to_highlight,
5714 |theme| theme.editor_highlighted_line_background,
5715 cx,
5716 );
5717 });
5718 })?;
5719
5720 Ok(())
5721 }
5722
5723 pub fn clear_code_action_providers(&mut self) {
5724 self.code_action_providers.clear();
5725 self.available_code_actions.take();
5726 }
5727
5728 pub fn add_code_action_provider(
5729 &mut self,
5730 provider: Rc<dyn CodeActionProvider>,
5731 window: &mut Window,
5732 cx: &mut Context<Self>,
5733 ) {
5734 if self
5735 .code_action_providers
5736 .iter()
5737 .any(|existing_provider| existing_provider.id() == provider.id())
5738 {
5739 return;
5740 }
5741
5742 self.code_action_providers.push(provider);
5743 self.refresh_code_actions(window, cx);
5744 }
5745
5746 pub fn remove_code_action_provider(
5747 &mut self,
5748 id: Arc<str>,
5749 window: &mut Window,
5750 cx: &mut Context<Self>,
5751 ) {
5752 self.code_action_providers
5753 .retain(|provider| provider.id() != id);
5754 self.refresh_code_actions(window, cx);
5755 }
5756
5757 pub fn code_actions_enabled(&self, cx: &App) -> bool {
5758 !self.code_action_providers.is_empty()
5759 && EditorSettings::get_global(cx).toolbar.code_actions
5760 }
5761
5762 pub fn has_available_code_actions(&self) -> bool {
5763 self.available_code_actions
5764 .as_ref()
5765 .is_some_and(|(_, actions)| !actions.is_empty())
5766 }
5767
5768 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5769 &self.context_menu
5770 }
5771
5772 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5773 let newest_selection = self.selections.newest_anchor().clone();
5774 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5775 let buffer = self.buffer.read(cx);
5776 if newest_selection.head().diff_base_anchor.is_some() {
5777 return None;
5778 }
5779 let (start_buffer, start) =
5780 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5781 let (end_buffer, end) =
5782 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5783 if start_buffer != end_buffer {
5784 return None;
5785 }
5786
5787 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5788 cx.background_executor()
5789 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5790 .await;
5791
5792 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5793 let providers = this.code_action_providers.clone();
5794 let tasks = this
5795 .code_action_providers
5796 .iter()
5797 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5798 .collect::<Vec<_>>();
5799 (providers, tasks)
5800 })?;
5801
5802 let mut actions = Vec::new();
5803 for (provider, provider_actions) in
5804 providers.into_iter().zip(future::join_all(tasks).await)
5805 {
5806 if let Some(provider_actions) = provider_actions.log_err() {
5807 actions.extend(provider_actions.into_iter().map(|action| {
5808 AvailableCodeAction {
5809 excerpt_id: newest_selection.start.excerpt_id,
5810 action,
5811 provider: provider.clone(),
5812 }
5813 }));
5814 }
5815 }
5816
5817 this.update(cx, |this, cx| {
5818 this.available_code_actions = if actions.is_empty() {
5819 None
5820 } else {
5821 Some((
5822 Location {
5823 buffer: start_buffer,
5824 range: start..end,
5825 },
5826 actions.into(),
5827 ))
5828 };
5829 cx.notify();
5830 })
5831 }));
5832 None
5833 }
5834
5835 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5836 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5837 self.show_git_blame_inline = false;
5838
5839 self.show_git_blame_inline_delay_task =
5840 Some(cx.spawn_in(window, async move |this, cx| {
5841 cx.background_executor().timer(delay).await;
5842
5843 this.update(cx, |this, cx| {
5844 this.show_git_blame_inline = true;
5845 cx.notify();
5846 })
5847 .log_err();
5848 }));
5849 }
5850 }
5851
5852 fn show_blame_popover(
5853 &mut self,
5854 blame_entry: &BlameEntry,
5855 position: gpui::Point<Pixels>,
5856 cx: &mut Context<Self>,
5857 ) {
5858 if let Some(state) = &mut self.inline_blame_popover {
5859 state.hide_task.take();
5860 cx.notify();
5861 } else {
5862 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5863 let show_task = cx.spawn(async move |editor, cx| {
5864 cx.background_executor()
5865 .timer(std::time::Duration::from_millis(delay))
5866 .await;
5867 editor
5868 .update(cx, |editor, cx| {
5869 if let Some(state) = &mut editor.inline_blame_popover {
5870 state.show_task = None;
5871 cx.notify();
5872 }
5873 })
5874 .ok();
5875 });
5876 let Some(blame) = self.blame.as_ref() else {
5877 return;
5878 };
5879 let blame = blame.read(cx);
5880 let details = blame.details_for_entry(&blame_entry);
5881 let markdown = cx.new(|cx| {
5882 Markdown::new(
5883 details
5884 .as_ref()
5885 .map(|message| message.message.clone())
5886 .unwrap_or_default(),
5887 None,
5888 None,
5889 cx,
5890 )
5891 });
5892 self.inline_blame_popover = Some(InlineBlamePopover {
5893 position,
5894 show_task: Some(show_task),
5895 hide_task: None,
5896 popover_bounds: None,
5897 popover_state: InlineBlamePopoverState {
5898 scroll_handle: ScrollHandle::new(),
5899 commit_message: details,
5900 markdown,
5901 },
5902 });
5903 }
5904 }
5905
5906 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5907 if let Some(state) = &mut self.inline_blame_popover {
5908 if state.show_task.is_some() {
5909 self.inline_blame_popover.take();
5910 cx.notify();
5911 } else {
5912 let hide_task = cx.spawn(async move |editor, cx| {
5913 cx.background_executor()
5914 .timer(std::time::Duration::from_millis(100))
5915 .await;
5916 editor
5917 .update(cx, |editor, cx| {
5918 editor.inline_blame_popover.take();
5919 cx.notify();
5920 })
5921 .ok();
5922 });
5923 state.hide_task = Some(hide_task);
5924 }
5925 }
5926 }
5927
5928 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5929 if self.pending_rename.is_some() {
5930 return None;
5931 }
5932
5933 let provider = self.semantics_provider.clone()?;
5934 let buffer = self.buffer.read(cx);
5935 let newest_selection = self.selections.newest_anchor().clone();
5936 let cursor_position = newest_selection.head();
5937 let (cursor_buffer, cursor_buffer_position) =
5938 buffer.text_anchor_for_position(cursor_position, cx)?;
5939 let (tail_buffer, tail_buffer_position) =
5940 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5941 if cursor_buffer != tail_buffer {
5942 return None;
5943 }
5944
5945 let snapshot = cursor_buffer.read(cx).snapshot();
5946 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5947 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5948 if start_word_range != end_word_range {
5949 self.document_highlights_task.take();
5950 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5951 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5952 return None;
5953 }
5954
5955 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5956 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5957 cx.background_executor()
5958 .timer(Duration::from_millis(debounce))
5959 .await;
5960
5961 let highlights = if let Some(highlights) = cx
5962 .update(|cx| {
5963 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5964 })
5965 .ok()
5966 .flatten()
5967 {
5968 highlights.await.log_err()
5969 } else {
5970 None
5971 };
5972
5973 if let Some(highlights) = highlights {
5974 this.update(cx, |this, cx| {
5975 if this.pending_rename.is_some() {
5976 return;
5977 }
5978
5979 let buffer_id = cursor_position.buffer_id;
5980 let buffer = this.buffer.read(cx);
5981 if !buffer
5982 .text_anchor_for_position(cursor_position, cx)
5983 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5984 {
5985 return;
5986 }
5987
5988 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5989 let mut write_ranges = Vec::new();
5990 let mut read_ranges = Vec::new();
5991 for highlight in highlights {
5992 for (excerpt_id, excerpt_range) in
5993 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5994 {
5995 let start = highlight
5996 .range
5997 .start
5998 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5999 let end = highlight
6000 .range
6001 .end
6002 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6003 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6004 continue;
6005 }
6006
6007 let range = Anchor {
6008 buffer_id,
6009 excerpt_id,
6010 text_anchor: start,
6011 diff_base_anchor: None,
6012 }..Anchor {
6013 buffer_id,
6014 excerpt_id,
6015 text_anchor: end,
6016 diff_base_anchor: None,
6017 };
6018 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6019 write_ranges.push(range);
6020 } else {
6021 read_ranges.push(range);
6022 }
6023 }
6024 }
6025
6026 this.highlight_background::<DocumentHighlightRead>(
6027 &read_ranges,
6028 |theme| theme.editor_document_highlight_read_background,
6029 cx,
6030 );
6031 this.highlight_background::<DocumentHighlightWrite>(
6032 &write_ranges,
6033 |theme| theme.editor_document_highlight_write_background,
6034 cx,
6035 );
6036 cx.notify();
6037 })
6038 .log_err();
6039 }
6040 }));
6041 None
6042 }
6043
6044 fn prepare_highlight_query_from_selection(
6045 &mut self,
6046 cx: &mut Context<Editor>,
6047 ) -> Option<(String, Range<Anchor>)> {
6048 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6049 return None;
6050 }
6051 if !EditorSettings::get_global(cx).selection_highlight {
6052 return None;
6053 }
6054 if self.selections.count() != 1 || self.selections.line_mode {
6055 return None;
6056 }
6057 let selection = self.selections.newest::<Point>(cx);
6058 if selection.is_empty() || selection.start.row != selection.end.row {
6059 return None;
6060 }
6061 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6062 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6063 let query = multi_buffer_snapshot
6064 .text_for_range(selection_anchor_range.clone())
6065 .collect::<String>();
6066 if query.trim().is_empty() {
6067 return None;
6068 }
6069 Some((query, selection_anchor_range))
6070 }
6071
6072 fn update_selection_occurrence_highlights(
6073 &mut self,
6074 query_text: String,
6075 query_range: Range<Anchor>,
6076 multi_buffer_range_to_query: Range<Point>,
6077 use_debounce: bool,
6078 window: &mut Window,
6079 cx: &mut Context<Editor>,
6080 ) -> Task<()> {
6081 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6082 cx.spawn_in(window, async move |editor, cx| {
6083 if use_debounce {
6084 cx.background_executor()
6085 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6086 .await;
6087 }
6088 let match_task = cx.background_spawn(async move {
6089 let buffer_ranges = multi_buffer_snapshot
6090 .range_to_buffer_ranges(multi_buffer_range_to_query)
6091 .into_iter()
6092 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6093 let mut match_ranges = Vec::new();
6094 let Ok(regex) = project::search::SearchQuery::text(
6095 query_text.clone(),
6096 false,
6097 false,
6098 false,
6099 Default::default(),
6100 Default::default(),
6101 false,
6102 None,
6103 ) else {
6104 return Vec::default();
6105 };
6106 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6107 match_ranges.extend(
6108 regex
6109 .search(&buffer_snapshot, Some(search_range.clone()))
6110 .await
6111 .into_iter()
6112 .filter_map(|match_range| {
6113 let match_start = buffer_snapshot
6114 .anchor_after(search_range.start + match_range.start);
6115 let match_end = buffer_snapshot
6116 .anchor_before(search_range.start + match_range.end);
6117 let match_anchor_range = Anchor::range_in_buffer(
6118 excerpt_id,
6119 buffer_snapshot.remote_id(),
6120 match_start..match_end,
6121 );
6122 (match_anchor_range != query_range).then_some(match_anchor_range)
6123 }),
6124 );
6125 }
6126 match_ranges
6127 });
6128 let match_ranges = match_task.await;
6129 editor
6130 .update_in(cx, |editor, _, cx| {
6131 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6132 if !match_ranges.is_empty() {
6133 editor.highlight_background::<SelectedTextHighlight>(
6134 &match_ranges,
6135 |theme| theme.editor_document_highlight_bracket_background,
6136 cx,
6137 )
6138 }
6139 })
6140 .log_err();
6141 })
6142 }
6143
6144 fn refresh_selected_text_highlights(
6145 &mut self,
6146 on_buffer_edit: bool,
6147 window: &mut Window,
6148 cx: &mut Context<Editor>,
6149 ) {
6150 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6151 else {
6152 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6153 self.quick_selection_highlight_task.take();
6154 self.debounced_selection_highlight_task.take();
6155 return;
6156 };
6157 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6158 if on_buffer_edit
6159 || self
6160 .quick_selection_highlight_task
6161 .as_ref()
6162 .map_or(true, |(prev_anchor_range, _)| {
6163 prev_anchor_range != &query_range
6164 })
6165 {
6166 let multi_buffer_visible_start = self
6167 .scroll_manager
6168 .anchor()
6169 .anchor
6170 .to_point(&multi_buffer_snapshot);
6171 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6172 multi_buffer_visible_start
6173 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6174 Bias::Left,
6175 );
6176 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6177 self.quick_selection_highlight_task = Some((
6178 query_range.clone(),
6179 self.update_selection_occurrence_highlights(
6180 query_text.clone(),
6181 query_range.clone(),
6182 multi_buffer_visible_range,
6183 false,
6184 window,
6185 cx,
6186 ),
6187 ));
6188 }
6189 if on_buffer_edit
6190 || self
6191 .debounced_selection_highlight_task
6192 .as_ref()
6193 .map_or(true, |(prev_anchor_range, _)| {
6194 prev_anchor_range != &query_range
6195 })
6196 {
6197 let multi_buffer_start = multi_buffer_snapshot
6198 .anchor_before(0)
6199 .to_point(&multi_buffer_snapshot);
6200 let multi_buffer_end = multi_buffer_snapshot
6201 .anchor_after(multi_buffer_snapshot.len())
6202 .to_point(&multi_buffer_snapshot);
6203 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6204 self.debounced_selection_highlight_task = Some((
6205 query_range.clone(),
6206 self.update_selection_occurrence_highlights(
6207 query_text,
6208 query_range,
6209 multi_buffer_full_range,
6210 true,
6211 window,
6212 cx,
6213 ),
6214 ));
6215 }
6216 }
6217
6218 pub fn refresh_inline_completion(
6219 &mut self,
6220 debounce: bool,
6221 user_requested: bool,
6222 window: &mut Window,
6223 cx: &mut Context<Self>,
6224 ) -> Option<()> {
6225 let provider = self.edit_prediction_provider()?;
6226 let cursor = self.selections.newest_anchor().head();
6227 let (buffer, cursor_buffer_position) =
6228 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6229
6230 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6231 self.discard_inline_completion(false, cx);
6232 return None;
6233 }
6234
6235 if !user_requested
6236 && (!self.should_show_edit_predictions()
6237 || !self.is_focused(window)
6238 || buffer.read(cx).is_empty())
6239 {
6240 self.discard_inline_completion(false, cx);
6241 return None;
6242 }
6243
6244 self.update_visible_inline_completion(window, cx);
6245 provider.refresh(
6246 self.project.clone(),
6247 buffer,
6248 cursor_buffer_position,
6249 debounce,
6250 cx,
6251 );
6252 Some(())
6253 }
6254
6255 fn show_edit_predictions_in_menu(&self) -> bool {
6256 match self.edit_prediction_settings {
6257 EditPredictionSettings::Disabled => false,
6258 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6259 }
6260 }
6261
6262 pub fn edit_predictions_enabled(&self) -> bool {
6263 match self.edit_prediction_settings {
6264 EditPredictionSettings::Disabled => false,
6265 EditPredictionSettings::Enabled { .. } => true,
6266 }
6267 }
6268
6269 fn edit_prediction_requires_modifier(&self) -> bool {
6270 match self.edit_prediction_settings {
6271 EditPredictionSettings::Disabled => false,
6272 EditPredictionSettings::Enabled {
6273 preview_requires_modifier,
6274 ..
6275 } => preview_requires_modifier,
6276 }
6277 }
6278
6279 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6280 if self.edit_prediction_provider.is_none() {
6281 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6282 } else {
6283 let selection = self.selections.newest_anchor();
6284 let cursor = selection.head();
6285
6286 if let Some((buffer, cursor_buffer_position)) =
6287 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6288 {
6289 self.edit_prediction_settings =
6290 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6291 }
6292 }
6293 }
6294
6295 fn edit_prediction_settings_at_position(
6296 &self,
6297 buffer: &Entity<Buffer>,
6298 buffer_position: language::Anchor,
6299 cx: &App,
6300 ) -> EditPredictionSettings {
6301 if !self.mode.is_full()
6302 || !self.show_inline_completions_override.unwrap_or(true)
6303 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6304 {
6305 return EditPredictionSettings::Disabled;
6306 }
6307
6308 let buffer = buffer.read(cx);
6309
6310 let file = buffer.file();
6311
6312 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6313 return EditPredictionSettings::Disabled;
6314 };
6315
6316 let by_provider = matches!(
6317 self.menu_inline_completions_policy,
6318 MenuInlineCompletionsPolicy::ByProvider
6319 );
6320
6321 let show_in_menu = by_provider
6322 && self
6323 .edit_prediction_provider
6324 .as_ref()
6325 .map_or(false, |provider| {
6326 provider.provider.show_completions_in_menu()
6327 });
6328
6329 let preview_requires_modifier =
6330 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6331
6332 EditPredictionSettings::Enabled {
6333 show_in_menu,
6334 preview_requires_modifier,
6335 }
6336 }
6337
6338 fn should_show_edit_predictions(&self) -> bool {
6339 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6340 }
6341
6342 pub fn edit_prediction_preview_is_active(&self) -> bool {
6343 matches!(
6344 self.edit_prediction_preview,
6345 EditPredictionPreview::Active { .. }
6346 )
6347 }
6348
6349 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6350 let cursor = self.selections.newest_anchor().head();
6351 if let Some((buffer, cursor_position)) =
6352 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6353 {
6354 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6355 } else {
6356 false
6357 }
6358 }
6359
6360 pub fn supports_minimap(&self, cx: &App) -> bool {
6361 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6362 }
6363
6364 fn edit_predictions_enabled_in_buffer(
6365 &self,
6366 buffer: &Entity<Buffer>,
6367 buffer_position: language::Anchor,
6368 cx: &App,
6369 ) -> bool {
6370 maybe!({
6371 if self.read_only(cx) {
6372 return Some(false);
6373 }
6374 let provider = self.edit_prediction_provider()?;
6375 if !provider.is_enabled(&buffer, buffer_position, cx) {
6376 return Some(false);
6377 }
6378 let buffer = buffer.read(cx);
6379 let Some(file) = buffer.file() else {
6380 return Some(true);
6381 };
6382 let settings = all_language_settings(Some(file), cx);
6383 Some(settings.edit_predictions_enabled_for_file(file, cx))
6384 })
6385 .unwrap_or(false)
6386 }
6387
6388 fn cycle_inline_completion(
6389 &mut self,
6390 direction: Direction,
6391 window: &mut Window,
6392 cx: &mut Context<Self>,
6393 ) -> Option<()> {
6394 let provider = self.edit_prediction_provider()?;
6395 let cursor = self.selections.newest_anchor().head();
6396 let (buffer, cursor_buffer_position) =
6397 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6398 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6399 return None;
6400 }
6401
6402 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6403 self.update_visible_inline_completion(window, cx);
6404
6405 Some(())
6406 }
6407
6408 pub fn show_inline_completion(
6409 &mut self,
6410 _: &ShowEditPrediction,
6411 window: &mut Window,
6412 cx: &mut Context<Self>,
6413 ) {
6414 if !self.has_active_inline_completion() {
6415 self.refresh_inline_completion(false, true, window, cx);
6416 return;
6417 }
6418
6419 self.update_visible_inline_completion(window, cx);
6420 }
6421
6422 pub fn display_cursor_names(
6423 &mut self,
6424 _: &DisplayCursorNames,
6425 window: &mut Window,
6426 cx: &mut Context<Self>,
6427 ) {
6428 self.show_cursor_names(window, cx);
6429 }
6430
6431 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6432 self.show_cursor_names = true;
6433 cx.notify();
6434 cx.spawn_in(window, async move |this, cx| {
6435 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6436 this.update(cx, |this, cx| {
6437 this.show_cursor_names = false;
6438 cx.notify()
6439 })
6440 .ok()
6441 })
6442 .detach();
6443 }
6444
6445 pub fn next_edit_prediction(
6446 &mut self,
6447 _: &NextEditPrediction,
6448 window: &mut Window,
6449 cx: &mut Context<Self>,
6450 ) {
6451 if self.has_active_inline_completion() {
6452 self.cycle_inline_completion(Direction::Next, window, cx);
6453 } else {
6454 let is_copilot_disabled = self
6455 .refresh_inline_completion(false, true, window, cx)
6456 .is_none();
6457 if is_copilot_disabled {
6458 cx.propagate();
6459 }
6460 }
6461 }
6462
6463 pub fn previous_edit_prediction(
6464 &mut self,
6465 _: &PreviousEditPrediction,
6466 window: &mut Window,
6467 cx: &mut Context<Self>,
6468 ) {
6469 if self.has_active_inline_completion() {
6470 self.cycle_inline_completion(Direction::Prev, window, cx);
6471 } else {
6472 let is_copilot_disabled = self
6473 .refresh_inline_completion(false, true, window, cx)
6474 .is_none();
6475 if is_copilot_disabled {
6476 cx.propagate();
6477 }
6478 }
6479 }
6480
6481 pub fn accept_edit_prediction(
6482 &mut self,
6483 _: &AcceptEditPrediction,
6484 window: &mut Window,
6485 cx: &mut Context<Self>,
6486 ) {
6487 if self.show_edit_predictions_in_menu() {
6488 self.hide_context_menu(window, cx);
6489 }
6490
6491 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6492 return;
6493 };
6494
6495 self.report_inline_completion_event(
6496 active_inline_completion.completion_id.clone(),
6497 true,
6498 cx,
6499 );
6500
6501 match &active_inline_completion.completion {
6502 InlineCompletion::Move { target, .. } => {
6503 let target = *target;
6504
6505 if let Some(position_map) = &self.last_position_map {
6506 if position_map
6507 .visible_row_range
6508 .contains(&target.to_display_point(&position_map.snapshot).row())
6509 || !self.edit_prediction_requires_modifier()
6510 {
6511 self.unfold_ranges(&[target..target], true, false, cx);
6512 // Note that this is also done in vim's handler of the Tab action.
6513 self.change_selections(
6514 Some(Autoscroll::newest()),
6515 window,
6516 cx,
6517 |selections| {
6518 selections.select_anchor_ranges([target..target]);
6519 },
6520 );
6521 self.clear_row_highlights::<EditPredictionPreview>();
6522
6523 self.edit_prediction_preview
6524 .set_previous_scroll_position(None);
6525 } else {
6526 self.edit_prediction_preview
6527 .set_previous_scroll_position(Some(
6528 position_map.snapshot.scroll_anchor,
6529 ));
6530
6531 self.highlight_rows::<EditPredictionPreview>(
6532 target..target,
6533 cx.theme().colors().editor_highlighted_line_background,
6534 RowHighlightOptions {
6535 autoscroll: true,
6536 ..Default::default()
6537 },
6538 cx,
6539 );
6540 self.request_autoscroll(Autoscroll::fit(), cx);
6541 }
6542 }
6543 }
6544 InlineCompletion::Edit { edits, .. } => {
6545 if let Some(provider) = self.edit_prediction_provider() {
6546 provider.accept(cx);
6547 }
6548
6549 // Store the transaction ID and selections before applying the edit
6550 let transaction_id_prev =
6551 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6552
6553 let snapshot = self.buffer.read(cx).snapshot(cx);
6554 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6555
6556 self.buffer.update(cx, |buffer, cx| {
6557 buffer.edit(edits.iter().cloned(), None, cx)
6558 });
6559
6560 self.change_selections(None, window, cx, |s| {
6561 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6562 });
6563
6564 let selections = self.selections.disjoint_anchors();
6565 if let Some(transaction_id_now) =
6566 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6567 {
6568 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6569 if has_new_transaction {
6570 self.selection_history
6571 .insert_transaction(transaction_id_now, selections);
6572 }
6573 }
6574
6575 self.update_visible_inline_completion(window, cx);
6576 if self.active_inline_completion.is_none() {
6577 self.refresh_inline_completion(true, true, window, cx);
6578 }
6579
6580 cx.notify();
6581 }
6582 }
6583
6584 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6585 }
6586
6587 pub fn accept_partial_inline_completion(
6588 &mut self,
6589 _: &AcceptPartialEditPrediction,
6590 window: &mut Window,
6591 cx: &mut Context<Self>,
6592 ) {
6593 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6594 return;
6595 };
6596 if self.selections.count() != 1 {
6597 return;
6598 }
6599
6600 self.report_inline_completion_event(
6601 active_inline_completion.completion_id.clone(),
6602 true,
6603 cx,
6604 );
6605
6606 match &active_inline_completion.completion {
6607 InlineCompletion::Move { target, .. } => {
6608 let target = *target;
6609 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6610 selections.select_anchor_ranges([target..target]);
6611 });
6612 }
6613 InlineCompletion::Edit { edits, .. } => {
6614 // Find an insertion that starts at the cursor position.
6615 let snapshot = self.buffer.read(cx).snapshot(cx);
6616 let cursor_offset = self.selections.newest::<usize>(cx).head();
6617 let insertion = edits.iter().find_map(|(range, text)| {
6618 let range = range.to_offset(&snapshot);
6619 if range.is_empty() && range.start == cursor_offset {
6620 Some(text)
6621 } else {
6622 None
6623 }
6624 });
6625
6626 if let Some(text) = insertion {
6627 let mut partial_completion = text
6628 .chars()
6629 .by_ref()
6630 .take_while(|c| c.is_alphabetic())
6631 .collect::<String>();
6632 if partial_completion.is_empty() {
6633 partial_completion = text
6634 .chars()
6635 .by_ref()
6636 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6637 .collect::<String>();
6638 }
6639
6640 cx.emit(EditorEvent::InputHandled {
6641 utf16_range_to_replace: None,
6642 text: partial_completion.clone().into(),
6643 });
6644
6645 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6646
6647 self.refresh_inline_completion(true, true, window, cx);
6648 cx.notify();
6649 } else {
6650 self.accept_edit_prediction(&Default::default(), window, cx);
6651 }
6652 }
6653 }
6654 }
6655
6656 fn discard_inline_completion(
6657 &mut self,
6658 should_report_inline_completion_event: bool,
6659 cx: &mut Context<Self>,
6660 ) -> bool {
6661 if should_report_inline_completion_event {
6662 let completion_id = self
6663 .active_inline_completion
6664 .as_ref()
6665 .and_then(|active_completion| active_completion.completion_id.clone());
6666
6667 self.report_inline_completion_event(completion_id, false, cx);
6668 }
6669
6670 if let Some(provider) = self.edit_prediction_provider() {
6671 provider.discard(cx);
6672 }
6673
6674 self.take_active_inline_completion(cx)
6675 }
6676
6677 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6678 let Some(provider) = self.edit_prediction_provider() else {
6679 return;
6680 };
6681
6682 let Some((_, buffer, _)) = self
6683 .buffer
6684 .read(cx)
6685 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6686 else {
6687 return;
6688 };
6689
6690 let extension = buffer
6691 .read(cx)
6692 .file()
6693 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6694
6695 let event_type = match accepted {
6696 true => "Edit Prediction Accepted",
6697 false => "Edit Prediction Discarded",
6698 };
6699 telemetry::event!(
6700 event_type,
6701 provider = provider.name(),
6702 prediction_id = id,
6703 suggestion_accepted = accepted,
6704 file_extension = extension,
6705 );
6706 }
6707
6708 pub fn has_active_inline_completion(&self) -> bool {
6709 self.active_inline_completion.is_some()
6710 }
6711
6712 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6713 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6714 return false;
6715 };
6716
6717 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6718 self.clear_highlights::<InlineCompletionHighlight>(cx);
6719 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6720 true
6721 }
6722
6723 /// Returns true when we're displaying the edit prediction popover below the cursor
6724 /// like we are not previewing and the LSP autocomplete menu is visible
6725 /// or we are in `when_holding_modifier` mode.
6726 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6727 if self.edit_prediction_preview_is_active()
6728 || !self.show_edit_predictions_in_menu()
6729 || !self.edit_predictions_enabled()
6730 {
6731 return false;
6732 }
6733
6734 if self.has_visible_completions_menu() {
6735 return true;
6736 }
6737
6738 has_completion && self.edit_prediction_requires_modifier()
6739 }
6740
6741 fn handle_modifiers_changed(
6742 &mut self,
6743 modifiers: Modifiers,
6744 position_map: &PositionMap,
6745 window: &mut Window,
6746 cx: &mut Context<Self>,
6747 ) {
6748 if self.show_edit_predictions_in_menu() {
6749 self.update_edit_prediction_preview(&modifiers, window, cx);
6750 }
6751
6752 self.update_selection_mode(&modifiers, position_map, window, cx);
6753
6754 let mouse_position = window.mouse_position();
6755 if !position_map.text_hitbox.is_hovered(window) {
6756 return;
6757 }
6758
6759 self.update_hovered_link(
6760 position_map.point_for_position(mouse_position),
6761 &position_map.snapshot,
6762 modifiers,
6763 window,
6764 cx,
6765 )
6766 }
6767
6768 fn update_selection_mode(
6769 &mut self,
6770 modifiers: &Modifiers,
6771 position_map: &PositionMap,
6772 window: &mut Window,
6773 cx: &mut Context<Self>,
6774 ) {
6775 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6776 return;
6777 }
6778
6779 let mouse_position = window.mouse_position();
6780 let point_for_position = position_map.point_for_position(mouse_position);
6781 let position = point_for_position.previous_valid;
6782
6783 self.select(
6784 SelectPhase::BeginColumnar {
6785 position,
6786 reset: false,
6787 goal_column: point_for_position.exact_unclipped.column(),
6788 },
6789 window,
6790 cx,
6791 );
6792 }
6793
6794 fn update_edit_prediction_preview(
6795 &mut self,
6796 modifiers: &Modifiers,
6797 window: &mut Window,
6798 cx: &mut Context<Self>,
6799 ) {
6800 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6801 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6802 return;
6803 };
6804
6805 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6806 if matches!(
6807 self.edit_prediction_preview,
6808 EditPredictionPreview::Inactive { .. }
6809 ) {
6810 self.edit_prediction_preview = EditPredictionPreview::Active {
6811 previous_scroll_position: None,
6812 since: Instant::now(),
6813 };
6814
6815 self.update_visible_inline_completion(window, cx);
6816 cx.notify();
6817 }
6818 } else if let EditPredictionPreview::Active {
6819 previous_scroll_position,
6820 since,
6821 } = self.edit_prediction_preview
6822 {
6823 if let (Some(previous_scroll_position), Some(position_map)) =
6824 (previous_scroll_position, self.last_position_map.as_ref())
6825 {
6826 self.set_scroll_position(
6827 previous_scroll_position
6828 .scroll_position(&position_map.snapshot.display_snapshot),
6829 window,
6830 cx,
6831 );
6832 }
6833
6834 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6835 released_too_fast: since.elapsed() < Duration::from_millis(200),
6836 };
6837 self.clear_row_highlights::<EditPredictionPreview>();
6838 self.update_visible_inline_completion(window, cx);
6839 cx.notify();
6840 }
6841 }
6842
6843 fn update_visible_inline_completion(
6844 &mut self,
6845 _window: &mut Window,
6846 cx: &mut Context<Self>,
6847 ) -> Option<()> {
6848 let selection = self.selections.newest_anchor();
6849 let cursor = selection.head();
6850 let multibuffer = self.buffer.read(cx).snapshot(cx);
6851 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6852 let excerpt_id = cursor.excerpt_id;
6853
6854 let show_in_menu = self.show_edit_predictions_in_menu();
6855 let completions_menu_has_precedence = !show_in_menu
6856 && (self.context_menu.borrow().is_some()
6857 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6858
6859 if completions_menu_has_precedence
6860 || !offset_selection.is_empty()
6861 || self
6862 .active_inline_completion
6863 .as_ref()
6864 .map_or(false, |completion| {
6865 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6866 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6867 !invalidation_range.contains(&offset_selection.head())
6868 })
6869 {
6870 self.discard_inline_completion(false, cx);
6871 return None;
6872 }
6873
6874 self.take_active_inline_completion(cx);
6875 let Some(provider) = self.edit_prediction_provider() else {
6876 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6877 return None;
6878 };
6879
6880 let (buffer, cursor_buffer_position) =
6881 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6882
6883 self.edit_prediction_settings =
6884 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6885
6886 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6887
6888 if self.edit_prediction_indent_conflict {
6889 let cursor_point = cursor.to_point(&multibuffer);
6890
6891 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6892
6893 if let Some((_, indent)) = indents.iter().next() {
6894 if indent.len == cursor_point.column {
6895 self.edit_prediction_indent_conflict = false;
6896 }
6897 }
6898 }
6899
6900 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6901 let edits = inline_completion
6902 .edits
6903 .into_iter()
6904 .flat_map(|(range, new_text)| {
6905 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6906 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6907 Some((start..end, new_text))
6908 })
6909 .collect::<Vec<_>>();
6910 if edits.is_empty() {
6911 return None;
6912 }
6913
6914 let first_edit_start = edits.first().unwrap().0.start;
6915 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6916 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6917
6918 let last_edit_end = edits.last().unwrap().0.end;
6919 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6920 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6921
6922 let cursor_row = cursor.to_point(&multibuffer).row;
6923
6924 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6925
6926 let mut inlay_ids = Vec::new();
6927 let invalidation_row_range;
6928 let move_invalidation_row_range = if cursor_row < edit_start_row {
6929 Some(cursor_row..edit_end_row)
6930 } else if cursor_row > edit_end_row {
6931 Some(edit_start_row..cursor_row)
6932 } else {
6933 None
6934 };
6935 let is_move =
6936 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6937 let completion = if is_move {
6938 invalidation_row_range =
6939 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6940 let target = first_edit_start;
6941 InlineCompletion::Move { target, snapshot }
6942 } else {
6943 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6944 && !self.inline_completions_hidden_for_vim_mode;
6945
6946 if show_completions_in_buffer {
6947 if edits
6948 .iter()
6949 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6950 {
6951 let mut inlays = Vec::new();
6952 for (range, new_text) in &edits {
6953 let inlay = Inlay::inline_completion(
6954 post_inc(&mut self.next_inlay_id),
6955 range.start,
6956 new_text.as_str(),
6957 );
6958 inlay_ids.push(inlay.id);
6959 inlays.push(inlay);
6960 }
6961
6962 self.splice_inlays(&[], inlays, cx);
6963 } else {
6964 let background_color = cx.theme().status().deleted_background;
6965 self.highlight_text::<InlineCompletionHighlight>(
6966 edits.iter().map(|(range, _)| range.clone()).collect(),
6967 HighlightStyle {
6968 background_color: Some(background_color),
6969 ..Default::default()
6970 },
6971 cx,
6972 );
6973 }
6974 }
6975
6976 invalidation_row_range = edit_start_row..edit_end_row;
6977
6978 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6979 if provider.show_tab_accept_marker() {
6980 EditDisplayMode::TabAccept
6981 } else {
6982 EditDisplayMode::Inline
6983 }
6984 } else {
6985 EditDisplayMode::DiffPopover
6986 };
6987
6988 InlineCompletion::Edit {
6989 edits,
6990 edit_preview: inline_completion.edit_preview,
6991 display_mode,
6992 snapshot,
6993 }
6994 };
6995
6996 let invalidation_range = multibuffer
6997 .anchor_before(Point::new(invalidation_row_range.start, 0))
6998 ..multibuffer.anchor_after(Point::new(
6999 invalidation_row_range.end,
7000 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7001 ));
7002
7003 self.stale_inline_completion_in_menu = None;
7004 self.active_inline_completion = Some(InlineCompletionState {
7005 inlay_ids,
7006 completion,
7007 completion_id: inline_completion.id,
7008 invalidation_range,
7009 });
7010
7011 cx.notify();
7012
7013 Some(())
7014 }
7015
7016 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7017 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7018 }
7019
7020 fn clear_tasks(&mut self) {
7021 self.tasks.clear()
7022 }
7023
7024 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7025 if self.tasks.insert(key, value).is_some() {
7026 // This case should hopefully be rare, but just in case...
7027 log::error!(
7028 "multiple different run targets found on a single line, only the last target will be rendered"
7029 )
7030 }
7031 }
7032
7033 /// Get all display points of breakpoints that will be rendered within editor
7034 ///
7035 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7036 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7037 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7038 fn active_breakpoints(
7039 &self,
7040 range: Range<DisplayRow>,
7041 window: &mut Window,
7042 cx: &mut Context<Self>,
7043 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7044 let mut breakpoint_display_points = HashMap::default();
7045
7046 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7047 return breakpoint_display_points;
7048 };
7049
7050 let snapshot = self.snapshot(window, cx);
7051
7052 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7053 let Some(project) = self.project.as_ref() else {
7054 return breakpoint_display_points;
7055 };
7056
7057 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7058 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7059
7060 for (buffer_snapshot, range, excerpt_id) in
7061 multi_buffer_snapshot.range_to_buffer_ranges(range)
7062 {
7063 let Some(buffer) = project.read_with(cx, |this, cx| {
7064 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7065 }) else {
7066 continue;
7067 };
7068 let breakpoints = breakpoint_store.read(cx).breakpoints(
7069 &buffer,
7070 Some(
7071 buffer_snapshot.anchor_before(range.start)
7072 ..buffer_snapshot.anchor_after(range.end),
7073 ),
7074 buffer_snapshot,
7075 cx,
7076 );
7077 for (breakpoint, state) in breakpoints {
7078 let multi_buffer_anchor =
7079 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7080 let position = multi_buffer_anchor
7081 .to_point(&multi_buffer_snapshot)
7082 .to_display_point(&snapshot);
7083
7084 breakpoint_display_points.insert(
7085 position.row(),
7086 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7087 );
7088 }
7089 }
7090
7091 breakpoint_display_points
7092 }
7093
7094 fn breakpoint_context_menu(
7095 &self,
7096 anchor: Anchor,
7097 window: &mut Window,
7098 cx: &mut Context<Self>,
7099 ) -> Entity<ui::ContextMenu> {
7100 let weak_editor = cx.weak_entity();
7101 let focus_handle = self.focus_handle(cx);
7102
7103 let row = self
7104 .buffer
7105 .read(cx)
7106 .snapshot(cx)
7107 .summary_for_anchor::<Point>(&anchor)
7108 .row;
7109
7110 let breakpoint = self
7111 .breakpoint_at_row(row, window, cx)
7112 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7113
7114 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7115 "Edit Log Breakpoint"
7116 } else {
7117 "Set Log Breakpoint"
7118 };
7119
7120 let condition_breakpoint_msg = if breakpoint
7121 .as_ref()
7122 .is_some_and(|bp| bp.1.condition.is_some())
7123 {
7124 "Edit Condition Breakpoint"
7125 } else {
7126 "Set Condition Breakpoint"
7127 };
7128
7129 let hit_condition_breakpoint_msg = if breakpoint
7130 .as_ref()
7131 .is_some_and(|bp| bp.1.hit_condition.is_some())
7132 {
7133 "Edit Hit Condition Breakpoint"
7134 } else {
7135 "Set Hit Condition Breakpoint"
7136 };
7137
7138 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7139 "Unset Breakpoint"
7140 } else {
7141 "Set Breakpoint"
7142 };
7143
7144 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7145 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7146
7147 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7148 BreakpointState::Enabled => Some("Disable"),
7149 BreakpointState::Disabled => Some("Enable"),
7150 });
7151
7152 let (anchor, breakpoint) =
7153 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7154
7155 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7156 menu.on_blur_subscription(Subscription::new(|| {}))
7157 .context(focus_handle)
7158 .when(run_to_cursor, |this| {
7159 let weak_editor = weak_editor.clone();
7160 this.entry("Run to cursor", None, move |window, cx| {
7161 weak_editor
7162 .update(cx, |editor, cx| {
7163 editor.change_selections(None, window, cx, |s| {
7164 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7165 });
7166 })
7167 .ok();
7168
7169 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7170 })
7171 .separator()
7172 })
7173 .when_some(toggle_state_msg, |this, msg| {
7174 this.entry(msg, None, {
7175 let weak_editor = weak_editor.clone();
7176 let breakpoint = breakpoint.clone();
7177 move |_window, cx| {
7178 weak_editor
7179 .update(cx, |this, cx| {
7180 this.edit_breakpoint_at_anchor(
7181 anchor,
7182 breakpoint.as_ref().clone(),
7183 BreakpointEditAction::InvertState,
7184 cx,
7185 );
7186 })
7187 .log_err();
7188 }
7189 })
7190 })
7191 .entry(set_breakpoint_msg, None, {
7192 let weak_editor = weak_editor.clone();
7193 let breakpoint = breakpoint.clone();
7194 move |_window, cx| {
7195 weak_editor
7196 .update(cx, |this, cx| {
7197 this.edit_breakpoint_at_anchor(
7198 anchor,
7199 breakpoint.as_ref().clone(),
7200 BreakpointEditAction::Toggle,
7201 cx,
7202 );
7203 })
7204 .log_err();
7205 }
7206 })
7207 .entry(log_breakpoint_msg, None, {
7208 let breakpoint = breakpoint.clone();
7209 let weak_editor = weak_editor.clone();
7210 move |window, cx| {
7211 weak_editor
7212 .update(cx, |this, cx| {
7213 this.add_edit_breakpoint_block(
7214 anchor,
7215 breakpoint.as_ref(),
7216 BreakpointPromptEditAction::Log,
7217 window,
7218 cx,
7219 );
7220 })
7221 .log_err();
7222 }
7223 })
7224 .entry(condition_breakpoint_msg, None, {
7225 let breakpoint = breakpoint.clone();
7226 let weak_editor = weak_editor.clone();
7227 move |window, cx| {
7228 weak_editor
7229 .update(cx, |this, cx| {
7230 this.add_edit_breakpoint_block(
7231 anchor,
7232 breakpoint.as_ref(),
7233 BreakpointPromptEditAction::Condition,
7234 window,
7235 cx,
7236 );
7237 })
7238 .log_err();
7239 }
7240 })
7241 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7242 weak_editor
7243 .update(cx, |this, cx| {
7244 this.add_edit_breakpoint_block(
7245 anchor,
7246 breakpoint.as_ref(),
7247 BreakpointPromptEditAction::HitCondition,
7248 window,
7249 cx,
7250 );
7251 })
7252 .log_err();
7253 })
7254 })
7255 }
7256
7257 fn render_breakpoint(
7258 &self,
7259 position: Anchor,
7260 row: DisplayRow,
7261 breakpoint: &Breakpoint,
7262 state: Option<BreakpointSessionState>,
7263 cx: &mut Context<Self>,
7264 ) -> IconButton {
7265 let is_rejected = state.is_some_and(|s| !s.verified);
7266 // Is it a breakpoint that shows up when hovering over gutter?
7267 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7268 (false, false),
7269 |PhantomBreakpointIndicator {
7270 is_active,
7271 display_row,
7272 collides_with_existing_breakpoint,
7273 }| {
7274 (
7275 is_active && display_row == row,
7276 collides_with_existing_breakpoint,
7277 )
7278 },
7279 );
7280
7281 let (color, icon) = {
7282 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7283 (false, false) => ui::IconName::DebugBreakpoint,
7284 (true, false) => ui::IconName::DebugLogBreakpoint,
7285 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7286 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7287 };
7288
7289 let color = if is_phantom {
7290 Color::Hint
7291 } else if is_rejected {
7292 Color::Disabled
7293 } else {
7294 Color::Debugger
7295 };
7296
7297 (color, icon)
7298 };
7299
7300 let breakpoint = Arc::from(breakpoint.clone());
7301
7302 let alt_as_text = gpui::Keystroke {
7303 modifiers: Modifiers::secondary_key(),
7304 ..Default::default()
7305 };
7306 let primary_action_text = if breakpoint.is_disabled() {
7307 "Enable breakpoint"
7308 } else if is_phantom && !collides_with_existing {
7309 "Set breakpoint"
7310 } else {
7311 "Unset breakpoint"
7312 };
7313 let focus_handle = self.focus_handle.clone();
7314
7315 let meta = if is_rejected {
7316 SharedString::from("No executable code is associated with this line.")
7317 } else if collides_with_existing && !breakpoint.is_disabled() {
7318 SharedString::from(format!(
7319 "{alt_as_text}-click to disable,\nright-click for more options."
7320 ))
7321 } else {
7322 SharedString::from("Right-click for more options.")
7323 };
7324 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7325 .icon_size(IconSize::XSmall)
7326 .size(ui::ButtonSize::None)
7327 .when(is_rejected, |this| {
7328 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7329 })
7330 .icon_color(color)
7331 .style(ButtonStyle::Transparent)
7332 .on_click(cx.listener({
7333 let breakpoint = breakpoint.clone();
7334
7335 move |editor, event: &ClickEvent, window, cx| {
7336 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7337 BreakpointEditAction::InvertState
7338 } else {
7339 BreakpointEditAction::Toggle
7340 };
7341
7342 window.focus(&editor.focus_handle(cx));
7343 editor.edit_breakpoint_at_anchor(
7344 position,
7345 breakpoint.as_ref().clone(),
7346 edit_action,
7347 cx,
7348 );
7349 }
7350 }))
7351 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7352 editor.set_breakpoint_context_menu(
7353 row,
7354 Some(position),
7355 event.down.position,
7356 window,
7357 cx,
7358 );
7359 }))
7360 .tooltip(move |window, cx| {
7361 Tooltip::with_meta_in(
7362 primary_action_text,
7363 Some(&ToggleBreakpoint),
7364 meta.clone(),
7365 &focus_handle,
7366 window,
7367 cx,
7368 )
7369 })
7370 }
7371
7372 fn build_tasks_context(
7373 project: &Entity<Project>,
7374 buffer: &Entity<Buffer>,
7375 buffer_row: u32,
7376 tasks: &Arc<RunnableTasks>,
7377 cx: &mut Context<Self>,
7378 ) -> Task<Option<task::TaskContext>> {
7379 let position = Point::new(buffer_row, tasks.column);
7380 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7381 let location = Location {
7382 buffer: buffer.clone(),
7383 range: range_start..range_start,
7384 };
7385 // Fill in the environmental variables from the tree-sitter captures
7386 let mut captured_task_variables = TaskVariables::default();
7387 for (capture_name, value) in tasks.extra_variables.clone() {
7388 captured_task_variables.insert(
7389 task::VariableName::Custom(capture_name.into()),
7390 value.clone(),
7391 );
7392 }
7393 project.update(cx, |project, cx| {
7394 project.task_store().update(cx, |task_store, cx| {
7395 task_store.task_context_for_location(captured_task_variables, location, cx)
7396 })
7397 })
7398 }
7399
7400 pub fn spawn_nearest_task(
7401 &mut self,
7402 action: &SpawnNearestTask,
7403 window: &mut Window,
7404 cx: &mut Context<Self>,
7405 ) {
7406 let Some((workspace, _)) = self.workspace.clone() else {
7407 return;
7408 };
7409 let Some(project) = self.project.clone() else {
7410 return;
7411 };
7412
7413 // Try to find a closest, enclosing node using tree-sitter that has a
7414 // task
7415 let Some((buffer, buffer_row, tasks)) = self
7416 .find_enclosing_node_task(cx)
7417 // Or find the task that's closest in row-distance.
7418 .or_else(|| self.find_closest_task(cx))
7419 else {
7420 return;
7421 };
7422
7423 let reveal_strategy = action.reveal;
7424 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7425 cx.spawn_in(window, async move |_, cx| {
7426 let context = task_context.await?;
7427 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7428
7429 let resolved = &mut resolved_task.resolved;
7430 resolved.reveal = reveal_strategy;
7431
7432 workspace
7433 .update_in(cx, |workspace, window, cx| {
7434 workspace.schedule_resolved_task(
7435 task_source_kind,
7436 resolved_task,
7437 false,
7438 window,
7439 cx,
7440 );
7441 })
7442 .ok()
7443 })
7444 .detach();
7445 }
7446
7447 fn find_closest_task(
7448 &mut self,
7449 cx: &mut Context<Self>,
7450 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7451 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7452
7453 let ((buffer_id, row), tasks) = self
7454 .tasks
7455 .iter()
7456 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7457
7458 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7459 let tasks = Arc::new(tasks.to_owned());
7460 Some((buffer, *row, tasks))
7461 }
7462
7463 fn find_enclosing_node_task(
7464 &mut self,
7465 cx: &mut Context<Self>,
7466 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7467 let snapshot = self.buffer.read(cx).snapshot(cx);
7468 let offset = self.selections.newest::<usize>(cx).head();
7469 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7470 let buffer_id = excerpt.buffer().remote_id();
7471
7472 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7473 let mut cursor = layer.node().walk();
7474
7475 while cursor.goto_first_child_for_byte(offset).is_some() {
7476 if cursor.node().end_byte() == offset {
7477 cursor.goto_next_sibling();
7478 }
7479 }
7480
7481 // Ascend to the smallest ancestor that contains the range and has a task.
7482 loop {
7483 let node = cursor.node();
7484 let node_range = node.byte_range();
7485 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7486
7487 // Check if this node contains our offset
7488 if node_range.start <= offset && node_range.end >= offset {
7489 // If it contains offset, check for task
7490 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7491 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7492 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7493 }
7494 }
7495
7496 if !cursor.goto_parent() {
7497 break;
7498 }
7499 }
7500 None
7501 }
7502
7503 fn render_run_indicator(
7504 &self,
7505 _style: &EditorStyle,
7506 is_active: bool,
7507 row: DisplayRow,
7508 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7509 cx: &mut Context<Self>,
7510 ) -> IconButton {
7511 let color = Color::Muted;
7512 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7513
7514 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7515 .shape(ui::IconButtonShape::Square)
7516 .icon_size(IconSize::XSmall)
7517 .icon_color(color)
7518 .toggle_state(is_active)
7519 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7520 let quick_launch = e.down.button == MouseButton::Left;
7521 window.focus(&editor.focus_handle(cx));
7522 editor.toggle_code_actions(
7523 &ToggleCodeActions {
7524 deployed_from: Some(CodeActionSource::Indicator(row)),
7525 quick_launch,
7526 },
7527 window,
7528 cx,
7529 );
7530 }))
7531 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7532 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7533 }))
7534 }
7535
7536 pub fn context_menu_visible(&self) -> bool {
7537 !self.edit_prediction_preview_is_active()
7538 && self
7539 .context_menu
7540 .borrow()
7541 .as_ref()
7542 .map_or(false, |menu| menu.visible())
7543 }
7544
7545 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7546 self.context_menu
7547 .borrow()
7548 .as_ref()
7549 .map(|menu| menu.origin())
7550 }
7551
7552 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7553 self.context_menu_options = Some(options);
7554 }
7555
7556 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7557 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7558
7559 fn render_edit_prediction_popover(
7560 &mut self,
7561 text_bounds: &Bounds<Pixels>,
7562 content_origin: gpui::Point<Pixels>,
7563 right_margin: Pixels,
7564 editor_snapshot: &EditorSnapshot,
7565 visible_row_range: Range<DisplayRow>,
7566 scroll_top: f32,
7567 scroll_bottom: f32,
7568 line_layouts: &[LineWithInvisibles],
7569 line_height: Pixels,
7570 scroll_pixel_position: gpui::Point<Pixels>,
7571 newest_selection_head: Option<DisplayPoint>,
7572 editor_width: Pixels,
7573 style: &EditorStyle,
7574 window: &mut Window,
7575 cx: &mut App,
7576 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7577 if self.mode().is_minimap() {
7578 return None;
7579 }
7580 let active_inline_completion = self.active_inline_completion.as_ref()?;
7581
7582 if self.edit_prediction_visible_in_cursor_popover(true) {
7583 return None;
7584 }
7585
7586 match &active_inline_completion.completion {
7587 InlineCompletion::Move { target, .. } => {
7588 let target_display_point = target.to_display_point(editor_snapshot);
7589
7590 if self.edit_prediction_requires_modifier() {
7591 if !self.edit_prediction_preview_is_active() {
7592 return None;
7593 }
7594
7595 self.render_edit_prediction_modifier_jump_popover(
7596 text_bounds,
7597 content_origin,
7598 visible_row_range,
7599 line_layouts,
7600 line_height,
7601 scroll_pixel_position,
7602 newest_selection_head,
7603 target_display_point,
7604 window,
7605 cx,
7606 )
7607 } else {
7608 self.render_edit_prediction_eager_jump_popover(
7609 text_bounds,
7610 content_origin,
7611 editor_snapshot,
7612 visible_row_range,
7613 scroll_top,
7614 scroll_bottom,
7615 line_height,
7616 scroll_pixel_position,
7617 target_display_point,
7618 editor_width,
7619 window,
7620 cx,
7621 )
7622 }
7623 }
7624 InlineCompletion::Edit {
7625 display_mode: EditDisplayMode::Inline,
7626 ..
7627 } => None,
7628 InlineCompletion::Edit {
7629 display_mode: EditDisplayMode::TabAccept,
7630 edits,
7631 ..
7632 } => {
7633 let range = &edits.first()?.0;
7634 let target_display_point = range.end.to_display_point(editor_snapshot);
7635
7636 self.render_edit_prediction_end_of_line_popover(
7637 "Accept",
7638 editor_snapshot,
7639 visible_row_range,
7640 target_display_point,
7641 line_height,
7642 scroll_pixel_position,
7643 content_origin,
7644 editor_width,
7645 window,
7646 cx,
7647 )
7648 }
7649 InlineCompletion::Edit {
7650 edits,
7651 edit_preview,
7652 display_mode: EditDisplayMode::DiffPopover,
7653 snapshot,
7654 } => self.render_edit_prediction_diff_popover(
7655 text_bounds,
7656 content_origin,
7657 right_margin,
7658 editor_snapshot,
7659 visible_row_range,
7660 line_layouts,
7661 line_height,
7662 scroll_pixel_position,
7663 newest_selection_head,
7664 editor_width,
7665 style,
7666 edits,
7667 edit_preview,
7668 snapshot,
7669 window,
7670 cx,
7671 ),
7672 }
7673 }
7674
7675 fn render_edit_prediction_modifier_jump_popover(
7676 &mut self,
7677 text_bounds: &Bounds<Pixels>,
7678 content_origin: gpui::Point<Pixels>,
7679 visible_row_range: Range<DisplayRow>,
7680 line_layouts: &[LineWithInvisibles],
7681 line_height: Pixels,
7682 scroll_pixel_position: gpui::Point<Pixels>,
7683 newest_selection_head: Option<DisplayPoint>,
7684 target_display_point: DisplayPoint,
7685 window: &mut Window,
7686 cx: &mut App,
7687 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7688 let scrolled_content_origin =
7689 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7690
7691 const SCROLL_PADDING_Y: Pixels = px(12.);
7692
7693 if target_display_point.row() < visible_row_range.start {
7694 return self.render_edit_prediction_scroll_popover(
7695 |_| SCROLL_PADDING_Y,
7696 IconName::ArrowUp,
7697 visible_row_range,
7698 line_layouts,
7699 newest_selection_head,
7700 scrolled_content_origin,
7701 window,
7702 cx,
7703 );
7704 } else if target_display_point.row() >= visible_row_range.end {
7705 return self.render_edit_prediction_scroll_popover(
7706 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7707 IconName::ArrowDown,
7708 visible_row_range,
7709 line_layouts,
7710 newest_selection_head,
7711 scrolled_content_origin,
7712 window,
7713 cx,
7714 );
7715 }
7716
7717 const POLE_WIDTH: Pixels = px(2.);
7718
7719 let line_layout =
7720 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7721 let target_column = target_display_point.column() as usize;
7722
7723 let target_x = line_layout.x_for_index(target_column);
7724 let target_y =
7725 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7726
7727 let flag_on_right = target_x < text_bounds.size.width / 2.;
7728
7729 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7730 border_color.l += 0.001;
7731
7732 let mut element = v_flex()
7733 .items_end()
7734 .when(flag_on_right, |el| el.items_start())
7735 .child(if flag_on_right {
7736 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7737 .rounded_bl(px(0.))
7738 .rounded_tl(px(0.))
7739 .border_l_2()
7740 .border_color(border_color)
7741 } else {
7742 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7743 .rounded_br(px(0.))
7744 .rounded_tr(px(0.))
7745 .border_r_2()
7746 .border_color(border_color)
7747 })
7748 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7749 .into_any();
7750
7751 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7752
7753 let mut origin = scrolled_content_origin + point(target_x, target_y)
7754 - point(
7755 if flag_on_right {
7756 POLE_WIDTH
7757 } else {
7758 size.width - POLE_WIDTH
7759 },
7760 size.height - line_height,
7761 );
7762
7763 origin.x = origin.x.max(content_origin.x);
7764
7765 element.prepaint_at(origin, window, cx);
7766
7767 Some((element, origin))
7768 }
7769
7770 fn render_edit_prediction_scroll_popover(
7771 &mut self,
7772 to_y: impl Fn(Size<Pixels>) -> Pixels,
7773 scroll_icon: IconName,
7774 visible_row_range: Range<DisplayRow>,
7775 line_layouts: &[LineWithInvisibles],
7776 newest_selection_head: Option<DisplayPoint>,
7777 scrolled_content_origin: gpui::Point<Pixels>,
7778 window: &mut Window,
7779 cx: &mut App,
7780 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7781 let mut element = self
7782 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7783 .into_any();
7784
7785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7786
7787 let cursor = newest_selection_head?;
7788 let cursor_row_layout =
7789 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7790 let cursor_column = cursor.column() as usize;
7791
7792 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7793
7794 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7795
7796 element.prepaint_at(origin, window, cx);
7797 Some((element, origin))
7798 }
7799
7800 fn render_edit_prediction_eager_jump_popover(
7801 &mut self,
7802 text_bounds: &Bounds<Pixels>,
7803 content_origin: gpui::Point<Pixels>,
7804 editor_snapshot: &EditorSnapshot,
7805 visible_row_range: Range<DisplayRow>,
7806 scroll_top: f32,
7807 scroll_bottom: f32,
7808 line_height: Pixels,
7809 scroll_pixel_position: gpui::Point<Pixels>,
7810 target_display_point: DisplayPoint,
7811 editor_width: Pixels,
7812 window: &mut Window,
7813 cx: &mut App,
7814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7815 if target_display_point.row().as_f32() < scroll_top {
7816 let mut element = self
7817 .render_edit_prediction_line_popover(
7818 "Jump to Edit",
7819 Some(IconName::ArrowUp),
7820 window,
7821 cx,
7822 )?
7823 .into_any();
7824
7825 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7826 let offset = point(
7827 (text_bounds.size.width - size.width) / 2.,
7828 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7829 );
7830
7831 let origin = text_bounds.origin + offset;
7832 element.prepaint_at(origin, window, cx);
7833 Some((element, origin))
7834 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7835 let mut element = self
7836 .render_edit_prediction_line_popover(
7837 "Jump to Edit",
7838 Some(IconName::ArrowDown),
7839 window,
7840 cx,
7841 )?
7842 .into_any();
7843
7844 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7845 let offset = point(
7846 (text_bounds.size.width - size.width) / 2.,
7847 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7848 );
7849
7850 let origin = text_bounds.origin + offset;
7851 element.prepaint_at(origin, window, cx);
7852 Some((element, origin))
7853 } else {
7854 self.render_edit_prediction_end_of_line_popover(
7855 "Jump to Edit",
7856 editor_snapshot,
7857 visible_row_range,
7858 target_display_point,
7859 line_height,
7860 scroll_pixel_position,
7861 content_origin,
7862 editor_width,
7863 window,
7864 cx,
7865 )
7866 }
7867 }
7868
7869 fn render_edit_prediction_end_of_line_popover(
7870 self: &mut Editor,
7871 label: &'static str,
7872 editor_snapshot: &EditorSnapshot,
7873 visible_row_range: Range<DisplayRow>,
7874 target_display_point: DisplayPoint,
7875 line_height: Pixels,
7876 scroll_pixel_position: gpui::Point<Pixels>,
7877 content_origin: gpui::Point<Pixels>,
7878 editor_width: Pixels,
7879 window: &mut Window,
7880 cx: &mut App,
7881 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7882 let target_line_end = DisplayPoint::new(
7883 target_display_point.row(),
7884 editor_snapshot.line_len(target_display_point.row()),
7885 );
7886
7887 let mut element = self
7888 .render_edit_prediction_line_popover(label, None, window, cx)?
7889 .into_any();
7890
7891 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7892
7893 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7894
7895 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7896 let mut origin = start_point
7897 + line_origin
7898 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7899 origin.x = origin.x.max(content_origin.x);
7900
7901 let max_x = content_origin.x + editor_width - size.width;
7902
7903 if origin.x > max_x {
7904 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7905
7906 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7907 origin.y += offset;
7908 IconName::ArrowUp
7909 } else {
7910 origin.y -= offset;
7911 IconName::ArrowDown
7912 };
7913
7914 element = self
7915 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7916 .into_any();
7917
7918 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7919
7920 origin.x = content_origin.x + editor_width - size.width - px(2.);
7921 }
7922
7923 element.prepaint_at(origin, window, cx);
7924 Some((element, origin))
7925 }
7926
7927 fn render_edit_prediction_diff_popover(
7928 self: &Editor,
7929 text_bounds: &Bounds<Pixels>,
7930 content_origin: gpui::Point<Pixels>,
7931 right_margin: Pixels,
7932 editor_snapshot: &EditorSnapshot,
7933 visible_row_range: Range<DisplayRow>,
7934 line_layouts: &[LineWithInvisibles],
7935 line_height: Pixels,
7936 scroll_pixel_position: gpui::Point<Pixels>,
7937 newest_selection_head: Option<DisplayPoint>,
7938 editor_width: Pixels,
7939 style: &EditorStyle,
7940 edits: &Vec<(Range<Anchor>, String)>,
7941 edit_preview: &Option<language::EditPreview>,
7942 snapshot: &language::BufferSnapshot,
7943 window: &mut Window,
7944 cx: &mut App,
7945 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7946 let edit_start = edits
7947 .first()
7948 .unwrap()
7949 .0
7950 .start
7951 .to_display_point(editor_snapshot);
7952 let edit_end = edits
7953 .last()
7954 .unwrap()
7955 .0
7956 .end
7957 .to_display_point(editor_snapshot);
7958
7959 let is_visible = visible_row_range.contains(&edit_start.row())
7960 || visible_row_range.contains(&edit_end.row());
7961 if !is_visible {
7962 return None;
7963 }
7964
7965 let highlighted_edits =
7966 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7967
7968 let styled_text = highlighted_edits.to_styled_text(&style.text);
7969 let line_count = highlighted_edits.text.lines().count();
7970
7971 const BORDER_WIDTH: Pixels = px(1.);
7972
7973 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7974 let has_keybind = keybind.is_some();
7975
7976 let mut element = h_flex()
7977 .items_start()
7978 .child(
7979 h_flex()
7980 .bg(cx.theme().colors().editor_background)
7981 .border(BORDER_WIDTH)
7982 .shadow_sm()
7983 .border_color(cx.theme().colors().border)
7984 .rounded_l_lg()
7985 .when(line_count > 1, |el| el.rounded_br_lg())
7986 .pr_1()
7987 .child(styled_text),
7988 )
7989 .child(
7990 h_flex()
7991 .h(line_height + BORDER_WIDTH * 2.)
7992 .px_1p5()
7993 .gap_1()
7994 // Workaround: For some reason, there's a gap if we don't do this
7995 .ml(-BORDER_WIDTH)
7996 .shadow(smallvec![gpui::BoxShadow {
7997 color: gpui::black().opacity(0.05),
7998 offset: point(px(1.), px(1.)),
7999 blur_radius: px(2.),
8000 spread_radius: px(0.),
8001 }])
8002 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8003 .border(BORDER_WIDTH)
8004 .border_color(cx.theme().colors().border)
8005 .rounded_r_lg()
8006 .id("edit_prediction_diff_popover_keybind")
8007 .when(!has_keybind, |el| {
8008 let status_colors = cx.theme().status();
8009
8010 el.bg(status_colors.error_background)
8011 .border_color(status_colors.error.opacity(0.6))
8012 .child(Icon::new(IconName::Info).color(Color::Error))
8013 .cursor_default()
8014 .hoverable_tooltip(move |_window, cx| {
8015 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8016 })
8017 })
8018 .children(keybind),
8019 )
8020 .into_any();
8021
8022 let longest_row =
8023 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8024 let longest_line_width = if visible_row_range.contains(&longest_row) {
8025 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8026 } else {
8027 layout_line(
8028 longest_row,
8029 editor_snapshot,
8030 style,
8031 editor_width,
8032 |_| false,
8033 window,
8034 cx,
8035 )
8036 .width
8037 };
8038
8039 let viewport_bounds =
8040 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8041 right: -right_margin,
8042 ..Default::default()
8043 });
8044
8045 let x_after_longest =
8046 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8047 - scroll_pixel_position.x;
8048
8049 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8050
8051 // Fully visible if it can be displayed within the window (allow overlapping other
8052 // panes). However, this is only allowed if the popover starts within text_bounds.
8053 let can_position_to_the_right = x_after_longest < text_bounds.right()
8054 && x_after_longest + element_bounds.width < viewport_bounds.right();
8055
8056 let mut origin = if can_position_to_the_right {
8057 point(
8058 x_after_longest,
8059 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8060 - scroll_pixel_position.y,
8061 )
8062 } else {
8063 let cursor_row = newest_selection_head.map(|head| head.row());
8064 let above_edit = edit_start
8065 .row()
8066 .0
8067 .checked_sub(line_count as u32)
8068 .map(DisplayRow);
8069 let below_edit = Some(edit_end.row() + 1);
8070 let above_cursor =
8071 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8072 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8073
8074 // Place the edit popover adjacent to the edit if there is a location
8075 // available that is onscreen and does not obscure the cursor. Otherwise,
8076 // place it adjacent to the cursor.
8077 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8078 .into_iter()
8079 .flatten()
8080 .find(|&start_row| {
8081 let end_row = start_row + line_count as u32;
8082 visible_row_range.contains(&start_row)
8083 && visible_row_range.contains(&end_row)
8084 && cursor_row.map_or(true, |cursor_row| {
8085 !((start_row..end_row).contains(&cursor_row))
8086 })
8087 })?;
8088
8089 content_origin
8090 + point(
8091 -scroll_pixel_position.x,
8092 row_target.as_f32() * line_height - scroll_pixel_position.y,
8093 )
8094 };
8095
8096 origin.x -= BORDER_WIDTH;
8097
8098 window.defer_draw(element, origin, 1);
8099
8100 // Do not return an element, since it will already be drawn due to defer_draw.
8101 None
8102 }
8103
8104 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8105 px(30.)
8106 }
8107
8108 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8109 if self.read_only(cx) {
8110 cx.theme().players().read_only()
8111 } else {
8112 self.style.as_ref().unwrap().local_player
8113 }
8114 }
8115
8116 fn render_edit_prediction_accept_keybind(
8117 &self,
8118 window: &mut Window,
8119 cx: &App,
8120 ) -> Option<AnyElement> {
8121 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8122 let accept_keystroke = accept_binding.keystroke()?;
8123
8124 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8125
8126 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8127 Color::Accent
8128 } else {
8129 Color::Muted
8130 };
8131
8132 h_flex()
8133 .px_0p5()
8134 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8135 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8136 .text_size(TextSize::XSmall.rems(cx))
8137 .child(h_flex().children(ui::render_modifiers(
8138 &accept_keystroke.modifiers,
8139 PlatformStyle::platform(),
8140 Some(modifiers_color),
8141 Some(IconSize::XSmall.rems().into()),
8142 true,
8143 )))
8144 .when(is_platform_style_mac, |parent| {
8145 parent.child(accept_keystroke.key.clone())
8146 })
8147 .when(!is_platform_style_mac, |parent| {
8148 parent.child(
8149 Key::new(
8150 util::capitalize(&accept_keystroke.key),
8151 Some(Color::Default),
8152 )
8153 .size(Some(IconSize::XSmall.rems().into())),
8154 )
8155 })
8156 .into_any()
8157 .into()
8158 }
8159
8160 fn render_edit_prediction_line_popover(
8161 &self,
8162 label: impl Into<SharedString>,
8163 icon: Option<IconName>,
8164 window: &mut Window,
8165 cx: &App,
8166 ) -> Option<Stateful<Div>> {
8167 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8168
8169 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8170 let has_keybind = keybind.is_some();
8171
8172 let result = h_flex()
8173 .id("ep-line-popover")
8174 .py_0p5()
8175 .pl_1()
8176 .pr(padding_right)
8177 .gap_1()
8178 .rounded_md()
8179 .border_1()
8180 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8181 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8182 .shadow_sm()
8183 .when(!has_keybind, |el| {
8184 let status_colors = cx.theme().status();
8185
8186 el.bg(status_colors.error_background)
8187 .border_color(status_colors.error.opacity(0.6))
8188 .pl_2()
8189 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8190 .cursor_default()
8191 .hoverable_tooltip(move |_window, cx| {
8192 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8193 })
8194 })
8195 .children(keybind)
8196 .child(
8197 Label::new(label)
8198 .size(LabelSize::Small)
8199 .when(!has_keybind, |el| {
8200 el.color(cx.theme().status().error.into()).strikethrough()
8201 }),
8202 )
8203 .when(!has_keybind, |el| {
8204 el.child(
8205 h_flex().ml_1().child(
8206 Icon::new(IconName::Info)
8207 .size(IconSize::Small)
8208 .color(cx.theme().status().error.into()),
8209 ),
8210 )
8211 })
8212 .when_some(icon, |element, icon| {
8213 element.child(
8214 div()
8215 .mt(px(1.5))
8216 .child(Icon::new(icon).size(IconSize::Small)),
8217 )
8218 });
8219
8220 Some(result)
8221 }
8222
8223 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8224 let accent_color = cx.theme().colors().text_accent;
8225 let editor_bg_color = cx.theme().colors().editor_background;
8226 editor_bg_color.blend(accent_color.opacity(0.1))
8227 }
8228
8229 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8230 let accent_color = cx.theme().colors().text_accent;
8231 let editor_bg_color = cx.theme().colors().editor_background;
8232 editor_bg_color.blend(accent_color.opacity(0.6))
8233 }
8234
8235 fn render_edit_prediction_cursor_popover(
8236 &self,
8237 min_width: Pixels,
8238 max_width: Pixels,
8239 cursor_point: Point,
8240 style: &EditorStyle,
8241 accept_keystroke: Option<&gpui::Keystroke>,
8242 _window: &Window,
8243 cx: &mut Context<Editor>,
8244 ) -> Option<AnyElement> {
8245 let provider = self.edit_prediction_provider.as_ref()?;
8246
8247 if provider.provider.needs_terms_acceptance(cx) {
8248 return Some(
8249 h_flex()
8250 .min_w(min_width)
8251 .flex_1()
8252 .px_2()
8253 .py_1()
8254 .gap_3()
8255 .elevation_2(cx)
8256 .hover(|style| style.bg(cx.theme().colors().element_hover))
8257 .id("accept-terms")
8258 .cursor_pointer()
8259 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8260 .on_click(cx.listener(|this, _event, window, cx| {
8261 cx.stop_propagation();
8262 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8263 window.dispatch_action(
8264 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8265 cx,
8266 );
8267 }))
8268 .child(
8269 h_flex()
8270 .flex_1()
8271 .gap_2()
8272 .child(Icon::new(IconName::ZedPredict))
8273 .child(Label::new("Accept Terms of Service"))
8274 .child(div().w_full())
8275 .child(
8276 Icon::new(IconName::ArrowUpRight)
8277 .color(Color::Muted)
8278 .size(IconSize::Small),
8279 )
8280 .into_any_element(),
8281 )
8282 .into_any(),
8283 );
8284 }
8285
8286 let is_refreshing = provider.provider.is_refreshing(cx);
8287
8288 fn pending_completion_container() -> Div {
8289 h_flex()
8290 .h_full()
8291 .flex_1()
8292 .gap_2()
8293 .child(Icon::new(IconName::ZedPredict))
8294 }
8295
8296 let completion = match &self.active_inline_completion {
8297 Some(prediction) => {
8298 if !self.has_visible_completions_menu() {
8299 const RADIUS: Pixels = px(6.);
8300 const BORDER_WIDTH: Pixels = px(1.);
8301
8302 return Some(
8303 h_flex()
8304 .elevation_2(cx)
8305 .border(BORDER_WIDTH)
8306 .border_color(cx.theme().colors().border)
8307 .when(accept_keystroke.is_none(), |el| {
8308 el.border_color(cx.theme().status().error)
8309 })
8310 .rounded(RADIUS)
8311 .rounded_tl(px(0.))
8312 .overflow_hidden()
8313 .child(div().px_1p5().child(match &prediction.completion {
8314 InlineCompletion::Move { target, snapshot } => {
8315 use text::ToPoint as _;
8316 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8317 {
8318 Icon::new(IconName::ZedPredictDown)
8319 } else {
8320 Icon::new(IconName::ZedPredictUp)
8321 }
8322 }
8323 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8324 }))
8325 .child(
8326 h_flex()
8327 .gap_1()
8328 .py_1()
8329 .px_2()
8330 .rounded_r(RADIUS - BORDER_WIDTH)
8331 .border_l_1()
8332 .border_color(cx.theme().colors().border)
8333 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8334 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8335 el.child(
8336 Label::new("Hold")
8337 .size(LabelSize::Small)
8338 .when(accept_keystroke.is_none(), |el| {
8339 el.strikethrough()
8340 })
8341 .line_height_style(LineHeightStyle::UiLabel),
8342 )
8343 })
8344 .id("edit_prediction_cursor_popover_keybind")
8345 .when(accept_keystroke.is_none(), |el| {
8346 let status_colors = cx.theme().status();
8347
8348 el.bg(status_colors.error_background)
8349 .border_color(status_colors.error.opacity(0.6))
8350 .child(Icon::new(IconName::Info).color(Color::Error))
8351 .cursor_default()
8352 .hoverable_tooltip(move |_window, cx| {
8353 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8354 .into()
8355 })
8356 })
8357 .when_some(
8358 accept_keystroke.as_ref(),
8359 |el, accept_keystroke| {
8360 el.child(h_flex().children(ui::render_modifiers(
8361 &accept_keystroke.modifiers,
8362 PlatformStyle::platform(),
8363 Some(Color::Default),
8364 Some(IconSize::XSmall.rems().into()),
8365 false,
8366 )))
8367 },
8368 ),
8369 )
8370 .into_any(),
8371 );
8372 }
8373
8374 self.render_edit_prediction_cursor_popover_preview(
8375 prediction,
8376 cursor_point,
8377 style,
8378 cx,
8379 )?
8380 }
8381
8382 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8383 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8384 stale_completion,
8385 cursor_point,
8386 style,
8387 cx,
8388 )?,
8389
8390 None => {
8391 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8392 }
8393 },
8394
8395 None => pending_completion_container().child(Label::new("No Prediction")),
8396 };
8397
8398 let completion = if is_refreshing {
8399 completion
8400 .with_animation(
8401 "loading-completion",
8402 Animation::new(Duration::from_secs(2))
8403 .repeat()
8404 .with_easing(pulsating_between(0.4, 0.8)),
8405 |label, delta| label.opacity(delta),
8406 )
8407 .into_any_element()
8408 } else {
8409 completion.into_any_element()
8410 };
8411
8412 let has_completion = self.active_inline_completion.is_some();
8413
8414 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8415 Some(
8416 h_flex()
8417 .min_w(min_width)
8418 .max_w(max_width)
8419 .flex_1()
8420 .elevation_2(cx)
8421 .border_color(cx.theme().colors().border)
8422 .child(
8423 div()
8424 .flex_1()
8425 .py_1()
8426 .px_2()
8427 .overflow_hidden()
8428 .child(completion),
8429 )
8430 .when_some(accept_keystroke, |el, accept_keystroke| {
8431 if !accept_keystroke.modifiers.modified() {
8432 return el;
8433 }
8434
8435 el.child(
8436 h_flex()
8437 .h_full()
8438 .border_l_1()
8439 .rounded_r_lg()
8440 .border_color(cx.theme().colors().border)
8441 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8442 .gap_1()
8443 .py_1()
8444 .px_2()
8445 .child(
8446 h_flex()
8447 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8448 .when(is_platform_style_mac, |parent| parent.gap_1())
8449 .child(h_flex().children(ui::render_modifiers(
8450 &accept_keystroke.modifiers,
8451 PlatformStyle::platform(),
8452 Some(if !has_completion {
8453 Color::Muted
8454 } else {
8455 Color::Default
8456 }),
8457 None,
8458 false,
8459 ))),
8460 )
8461 .child(Label::new("Preview").into_any_element())
8462 .opacity(if has_completion { 1.0 } else { 0.4 }),
8463 )
8464 })
8465 .into_any(),
8466 )
8467 }
8468
8469 fn render_edit_prediction_cursor_popover_preview(
8470 &self,
8471 completion: &InlineCompletionState,
8472 cursor_point: Point,
8473 style: &EditorStyle,
8474 cx: &mut Context<Editor>,
8475 ) -> Option<Div> {
8476 use text::ToPoint as _;
8477
8478 fn render_relative_row_jump(
8479 prefix: impl Into<String>,
8480 current_row: u32,
8481 target_row: u32,
8482 ) -> Div {
8483 let (row_diff, arrow) = if target_row < current_row {
8484 (current_row - target_row, IconName::ArrowUp)
8485 } else {
8486 (target_row - current_row, IconName::ArrowDown)
8487 };
8488
8489 h_flex()
8490 .child(
8491 Label::new(format!("{}{}", prefix.into(), row_diff))
8492 .color(Color::Muted)
8493 .size(LabelSize::Small),
8494 )
8495 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8496 }
8497
8498 match &completion.completion {
8499 InlineCompletion::Move {
8500 target, snapshot, ..
8501 } => Some(
8502 h_flex()
8503 .px_2()
8504 .gap_2()
8505 .flex_1()
8506 .child(
8507 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8508 Icon::new(IconName::ZedPredictDown)
8509 } else {
8510 Icon::new(IconName::ZedPredictUp)
8511 },
8512 )
8513 .child(Label::new("Jump to Edit")),
8514 ),
8515
8516 InlineCompletion::Edit {
8517 edits,
8518 edit_preview,
8519 snapshot,
8520 display_mode: _,
8521 } => {
8522 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8523
8524 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8525 &snapshot,
8526 &edits,
8527 edit_preview.as_ref()?,
8528 true,
8529 cx,
8530 )
8531 .first_line_preview();
8532
8533 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8534 .with_default_highlights(&style.text, highlighted_edits.highlights);
8535
8536 let preview = h_flex()
8537 .gap_1()
8538 .min_w_16()
8539 .child(styled_text)
8540 .when(has_more_lines, |parent| parent.child("…"));
8541
8542 let left = if first_edit_row != cursor_point.row {
8543 render_relative_row_jump("", cursor_point.row, first_edit_row)
8544 .into_any_element()
8545 } else {
8546 Icon::new(IconName::ZedPredict).into_any_element()
8547 };
8548
8549 Some(
8550 h_flex()
8551 .h_full()
8552 .flex_1()
8553 .gap_2()
8554 .pr_1()
8555 .overflow_x_hidden()
8556 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8557 .child(left)
8558 .child(preview),
8559 )
8560 }
8561 }
8562 }
8563
8564 pub fn render_context_menu(
8565 &self,
8566 style: &EditorStyle,
8567 max_height_in_lines: u32,
8568 window: &mut Window,
8569 cx: &mut Context<Editor>,
8570 ) -> Option<AnyElement> {
8571 let menu = self.context_menu.borrow();
8572 let menu = menu.as_ref()?;
8573 if !menu.visible() {
8574 return None;
8575 };
8576 Some(menu.render(style, max_height_in_lines, window, cx))
8577 }
8578
8579 fn render_context_menu_aside(
8580 &mut self,
8581 max_size: Size<Pixels>,
8582 window: &mut Window,
8583 cx: &mut Context<Editor>,
8584 ) -> Option<AnyElement> {
8585 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8586 if menu.visible() {
8587 menu.render_aside(self, max_size, window, cx)
8588 } else {
8589 None
8590 }
8591 })
8592 }
8593
8594 fn hide_context_menu(
8595 &mut self,
8596 window: &mut Window,
8597 cx: &mut Context<Self>,
8598 ) -> Option<CodeContextMenu> {
8599 cx.notify();
8600 self.completion_tasks.clear();
8601 let context_menu = self.context_menu.borrow_mut().take();
8602 self.stale_inline_completion_in_menu.take();
8603 self.update_visible_inline_completion(window, cx);
8604 context_menu
8605 }
8606
8607 fn show_snippet_choices(
8608 &mut self,
8609 choices: &Vec<String>,
8610 selection: Range<Anchor>,
8611 cx: &mut Context<Self>,
8612 ) {
8613 if selection.start.buffer_id.is_none() {
8614 return;
8615 }
8616 let buffer_id = selection.start.buffer_id.unwrap();
8617 let buffer = self.buffer().read(cx).buffer(buffer_id);
8618 let id = post_inc(&mut self.next_completion_id);
8619 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8620
8621 if let Some(buffer) = buffer {
8622 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8623 CompletionsMenu::new_snippet_choices(
8624 id,
8625 true,
8626 choices,
8627 selection,
8628 buffer,
8629 snippet_sort_order,
8630 ),
8631 ));
8632 }
8633 }
8634
8635 pub fn insert_snippet(
8636 &mut self,
8637 insertion_ranges: &[Range<usize>],
8638 snippet: Snippet,
8639 window: &mut Window,
8640 cx: &mut Context<Self>,
8641 ) -> Result<()> {
8642 struct Tabstop<T> {
8643 is_end_tabstop: bool,
8644 ranges: Vec<Range<T>>,
8645 choices: Option<Vec<String>>,
8646 }
8647
8648 let tabstops = self.buffer.update(cx, |buffer, cx| {
8649 let snippet_text: Arc<str> = snippet.text.clone().into();
8650 let edits = insertion_ranges
8651 .iter()
8652 .cloned()
8653 .map(|range| (range, snippet_text.clone()));
8654 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8655
8656 let snapshot = &*buffer.read(cx);
8657 let snippet = &snippet;
8658 snippet
8659 .tabstops
8660 .iter()
8661 .map(|tabstop| {
8662 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8663 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8664 });
8665 let mut tabstop_ranges = tabstop
8666 .ranges
8667 .iter()
8668 .flat_map(|tabstop_range| {
8669 let mut delta = 0_isize;
8670 insertion_ranges.iter().map(move |insertion_range| {
8671 let insertion_start = insertion_range.start as isize + delta;
8672 delta +=
8673 snippet.text.len() as isize - insertion_range.len() as isize;
8674
8675 let start = ((insertion_start + tabstop_range.start) as usize)
8676 .min(snapshot.len());
8677 let end = ((insertion_start + tabstop_range.end) as usize)
8678 .min(snapshot.len());
8679 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8680 })
8681 })
8682 .collect::<Vec<_>>();
8683 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8684
8685 Tabstop {
8686 is_end_tabstop,
8687 ranges: tabstop_ranges,
8688 choices: tabstop.choices.clone(),
8689 }
8690 })
8691 .collect::<Vec<_>>()
8692 });
8693 if let Some(tabstop) = tabstops.first() {
8694 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8695 s.select_ranges(tabstop.ranges.iter().cloned());
8696 });
8697
8698 if let Some(choices) = &tabstop.choices {
8699 if let Some(selection) = tabstop.ranges.first() {
8700 self.show_snippet_choices(choices, selection.clone(), cx)
8701 }
8702 }
8703
8704 // If we're already at the last tabstop and it's at the end of the snippet,
8705 // we're done, we don't need to keep the state around.
8706 if !tabstop.is_end_tabstop {
8707 let choices = tabstops
8708 .iter()
8709 .map(|tabstop| tabstop.choices.clone())
8710 .collect();
8711
8712 let ranges = tabstops
8713 .into_iter()
8714 .map(|tabstop| tabstop.ranges)
8715 .collect::<Vec<_>>();
8716
8717 self.snippet_stack.push(SnippetState {
8718 active_index: 0,
8719 ranges,
8720 choices,
8721 });
8722 }
8723
8724 // Check whether the just-entered snippet ends with an auto-closable bracket.
8725 if self.autoclose_regions.is_empty() {
8726 let snapshot = self.buffer.read(cx).snapshot(cx);
8727 for selection in &mut self.selections.all::<Point>(cx) {
8728 let selection_head = selection.head();
8729 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8730 continue;
8731 };
8732
8733 let mut bracket_pair = None;
8734 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8735 let prev_chars = snapshot
8736 .reversed_chars_at(selection_head)
8737 .collect::<String>();
8738 for (pair, enabled) in scope.brackets() {
8739 if enabled
8740 && pair.close
8741 && prev_chars.starts_with(pair.start.as_str())
8742 && next_chars.starts_with(pair.end.as_str())
8743 {
8744 bracket_pair = Some(pair.clone());
8745 break;
8746 }
8747 }
8748 if let Some(pair) = bracket_pair {
8749 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8750 let autoclose_enabled =
8751 self.use_autoclose && snapshot_settings.use_autoclose;
8752 if autoclose_enabled {
8753 let start = snapshot.anchor_after(selection_head);
8754 let end = snapshot.anchor_after(selection_head);
8755 self.autoclose_regions.push(AutocloseRegion {
8756 selection_id: selection.id,
8757 range: start..end,
8758 pair,
8759 });
8760 }
8761 }
8762 }
8763 }
8764 }
8765 Ok(())
8766 }
8767
8768 pub fn move_to_next_snippet_tabstop(
8769 &mut self,
8770 window: &mut Window,
8771 cx: &mut Context<Self>,
8772 ) -> bool {
8773 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8774 }
8775
8776 pub fn move_to_prev_snippet_tabstop(
8777 &mut self,
8778 window: &mut Window,
8779 cx: &mut Context<Self>,
8780 ) -> bool {
8781 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8782 }
8783
8784 pub fn move_to_snippet_tabstop(
8785 &mut self,
8786 bias: Bias,
8787 window: &mut Window,
8788 cx: &mut Context<Self>,
8789 ) -> bool {
8790 if let Some(mut snippet) = self.snippet_stack.pop() {
8791 match bias {
8792 Bias::Left => {
8793 if snippet.active_index > 0 {
8794 snippet.active_index -= 1;
8795 } else {
8796 self.snippet_stack.push(snippet);
8797 return false;
8798 }
8799 }
8800 Bias::Right => {
8801 if snippet.active_index + 1 < snippet.ranges.len() {
8802 snippet.active_index += 1;
8803 } else {
8804 self.snippet_stack.push(snippet);
8805 return false;
8806 }
8807 }
8808 }
8809 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8811 s.select_anchor_ranges(current_ranges.iter().cloned())
8812 });
8813
8814 if let Some(choices) = &snippet.choices[snippet.active_index] {
8815 if let Some(selection) = current_ranges.first() {
8816 self.show_snippet_choices(&choices, selection.clone(), cx);
8817 }
8818 }
8819
8820 // If snippet state is not at the last tabstop, push it back on the stack
8821 if snippet.active_index + 1 < snippet.ranges.len() {
8822 self.snippet_stack.push(snippet);
8823 }
8824 return true;
8825 }
8826 }
8827
8828 false
8829 }
8830
8831 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8832 self.transact(window, cx, |this, window, cx| {
8833 this.select_all(&SelectAll, window, cx);
8834 this.insert("", window, cx);
8835 });
8836 }
8837
8838 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8839 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8840 self.transact(window, cx, |this, window, cx| {
8841 this.select_autoclose_pair(window, cx);
8842 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8843 if !this.linked_edit_ranges.is_empty() {
8844 let selections = this.selections.all::<MultiBufferPoint>(cx);
8845 let snapshot = this.buffer.read(cx).snapshot(cx);
8846
8847 for selection in selections.iter() {
8848 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8849 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8850 if selection_start.buffer_id != selection_end.buffer_id {
8851 continue;
8852 }
8853 if let Some(ranges) =
8854 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8855 {
8856 for (buffer, entries) in ranges {
8857 linked_ranges.entry(buffer).or_default().extend(entries);
8858 }
8859 }
8860 }
8861 }
8862
8863 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8864 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8865 for selection in &mut selections {
8866 if selection.is_empty() {
8867 let old_head = selection.head();
8868 let mut new_head =
8869 movement::left(&display_map, old_head.to_display_point(&display_map))
8870 .to_point(&display_map);
8871 if let Some((buffer, line_buffer_range)) = display_map
8872 .buffer_snapshot
8873 .buffer_line_for_row(MultiBufferRow(old_head.row))
8874 {
8875 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8876 let indent_len = match indent_size.kind {
8877 IndentKind::Space => {
8878 buffer.settings_at(line_buffer_range.start, cx).tab_size
8879 }
8880 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8881 };
8882 if old_head.column <= indent_size.len && old_head.column > 0 {
8883 let indent_len = indent_len.get();
8884 new_head = cmp::min(
8885 new_head,
8886 MultiBufferPoint::new(
8887 old_head.row,
8888 ((old_head.column - 1) / indent_len) * indent_len,
8889 ),
8890 );
8891 }
8892 }
8893
8894 selection.set_head(new_head, SelectionGoal::None);
8895 }
8896 }
8897
8898 this.signature_help_state.set_backspace_pressed(true);
8899 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8900 s.select(selections)
8901 });
8902 this.insert("", window, cx);
8903 let empty_str: Arc<str> = Arc::from("");
8904 for (buffer, edits) in linked_ranges {
8905 let snapshot = buffer.read(cx).snapshot();
8906 use text::ToPoint as TP;
8907
8908 let edits = edits
8909 .into_iter()
8910 .map(|range| {
8911 let end_point = TP::to_point(&range.end, &snapshot);
8912 let mut start_point = TP::to_point(&range.start, &snapshot);
8913
8914 if end_point == start_point {
8915 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8916 .saturating_sub(1);
8917 start_point =
8918 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8919 };
8920
8921 (start_point..end_point, empty_str.clone())
8922 })
8923 .sorted_by_key(|(range, _)| range.start)
8924 .collect::<Vec<_>>();
8925 buffer.update(cx, |this, cx| {
8926 this.edit(edits, None, cx);
8927 })
8928 }
8929 this.refresh_inline_completion(true, false, window, cx);
8930 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8931 });
8932 }
8933
8934 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8935 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8936 self.transact(window, cx, |this, window, cx| {
8937 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8938 s.move_with(|map, selection| {
8939 if selection.is_empty() {
8940 let cursor = movement::right(map, selection.head());
8941 selection.end = cursor;
8942 selection.reversed = true;
8943 selection.goal = SelectionGoal::None;
8944 }
8945 })
8946 });
8947 this.insert("", window, cx);
8948 this.refresh_inline_completion(true, false, window, cx);
8949 });
8950 }
8951
8952 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8953 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8954 if self.move_to_prev_snippet_tabstop(window, cx) {
8955 return;
8956 }
8957 self.outdent(&Outdent, window, cx);
8958 }
8959
8960 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8961 if self.move_to_next_snippet_tabstop(window, cx) {
8962 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8963 return;
8964 }
8965 if self.read_only(cx) {
8966 return;
8967 }
8968 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8969 let mut selections = self.selections.all_adjusted(cx);
8970 let buffer = self.buffer.read(cx);
8971 let snapshot = buffer.snapshot(cx);
8972 let rows_iter = selections.iter().map(|s| s.head().row);
8973 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8974
8975 let has_some_cursor_in_whitespace = selections
8976 .iter()
8977 .filter(|selection| selection.is_empty())
8978 .any(|selection| {
8979 let cursor = selection.head();
8980 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8981 cursor.column < current_indent.len
8982 });
8983
8984 let mut edits = Vec::new();
8985 let mut prev_edited_row = 0;
8986 let mut row_delta = 0;
8987 for selection in &mut selections {
8988 if selection.start.row != prev_edited_row {
8989 row_delta = 0;
8990 }
8991 prev_edited_row = selection.end.row;
8992
8993 // If the selection is non-empty, then increase the indentation of the selected lines.
8994 if !selection.is_empty() {
8995 row_delta =
8996 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8997 continue;
8998 }
8999
9000 let cursor = selection.head();
9001 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9002 if let Some(suggested_indent) =
9003 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9004 {
9005 // Don't do anything if already at suggested indent
9006 // and there is any other cursor which is not
9007 if has_some_cursor_in_whitespace
9008 && cursor.column == current_indent.len
9009 && current_indent.len == suggested_indent.len
9010 {
9011 continue;
9012 }
9013
9014 // Adjust line and move cursor to suggested indent
9015 // if cursor is not at suggested indent
9016 if cursor.column < suggested_indent.len
9017 && cursor.column <= current_indent.len
9018 && current_indent.len <= suggested_indent.len
9019 {
9020 selection.start = Point::new(cursor.row, suggested_indent.len);
9021 selection.end = selection.start;
9022 if row_delta == 0 {
9023 edits.extend(Buffer::edit_for_indent_size_adjustment(
9024 cursor.row,
9025 current_indent,
9026 suggested_indent,
9027 ));
9028 row_delta = suggested_indent.len - current_indent.len;
9029 }
9030 continue;
9031 }
9032
9033 // If current indent is more than suggested indent
9034 // only move cursor to current indent and skip indent
9035 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9036 selection.start = Point::new(cursor.row, current_indent.len);
9037 selection.end = selection.start;
9038 continue;
9039 }
9040 }
9041
9042 // Otherwise, insert a hard or soft tab.
9043 let settings = buffer.language_settings_at(cursor, cx);
9044 let tab_size = if settings.hard_tabs {
9045 IndentSize::tab()
9046 } else {
9047 let tab_size = settings.tab_size.get();
9048 let indent_remainder = snapshot
9049 .text_for_range(Point::new(cursor.row, 0)..cursor)
9050 .flat_map(str::chars)
9051 .fold(row_delta % tab_size, |counter: u32, c| {
9052 if c == '\t' {
9053 0
9054 } else {
9055 (counter + 1) % tab_size
9056 }
9057 });
9058
9059 let chars_to_next_tab_stop = tab_size - indent_remainder;
9060 IndentSize::spaces(chars_to_next_tab_stop)
9061 };
9062 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9063 selection.end = selection.start;
9064 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9065 row_delta += tab_size.len;
9066 }
9067
9068 self.transact(window, cx, |this, window, cx| {
9069 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9070 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9071 s.select(selections)
9072 });
9073 this.refresh_inline_completion(true, false, window, cx);
9074 });
9075 }
9076
9077 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9078 if self.read_only(cx) {
9079 return;
9080 }
9081 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9082 let mut selections = self.selections.all::<Point>(cx);
9083 let mut prev_edited_row = 0;
9084 let mut row_delta = 0;
9085 let mut edits = Vec::new();
9086 let buffer = self.buffer.read(cx);
9087 let snapshot = buffer.snapshot(cx);
9088 for selection in &mut selections {
9089 if selection.start.row != prev_edited_row {
9090 row_delta = 0;
9091 }
9092 prev_edited_row = selection.end.row;
9093
9094 row_delta =
9095 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9096 }
9097
9098 self.transact(window, cx, |this, window, cx| {
9099 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9100 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9101 s.select(selections)
9102 });
9103 });
9104 }
9105
9106 fn indent_selection(
9107 buffer: &MultiBuffer,
9108 snapshot: &MultiBufferSnapshot,
9109 selection: &mut Selection<Point>,
9110 edits: &mut Vec<(Range<Point>, String)>,
9111 delta_for_start_row: u32,
9112 cx: &App,
9113 ) -> u32 {
9114 let settings = buffer.language_settings_at(selection.start, cx);
9115 let tab_size = settings.tab_size.get();
9116 let indent_kind = if settings.hard_tabs {
9117 IndentKind::Tab
9118 } else {
9119 IndentKind::Space
9120 };
9121 let mut start_row = selection.start.row;
9122 let mut end_row = selection.end.row + 1;
9123
9124 // If a selection ends at the beginning of a line, don't indent
9125 // that last line.
9126 if selection.end.column == 0 && selection.end.row > selection.start.row {
9127 end_row -= 1;
9128 }
9129
9130 // Avoid re-indenting a row that has already been indented by a
9131 // previous selection, but still update this selection's column
9132 // to reflect that indentation.
9133 if delta_for_start_row > 0 {
9134 start_row += 1;
9135 selection.start.column += delta_for_start_row;
9136 if selection.end.row == selection.start.row {
9137 selection.end.column += delta_for_start_row;
9138 }
9139 }
9140
9141 let mut delta_for_end_row = 0;
9142 let has_multiple_rows = start_row + 1 != end_row;
9143 for row in start_row..end_row {
9144 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9145 let indent_delta = match (current_indent.kind, indent_kind) {
9146 (IndentKind::Space, IndentKind::Space) => {
9147 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9148 IndentSize::spaces(columns_to_next_tab_stop)
9149 }
9150 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9151 (_, IndentKind::Tab) => IndentSize::tab(),
9152 };
9153
9154 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9155 0
9156 } else {
9157 selection.start.column
9158 };
9159 let row_start = Point::new(row, start);
9160 edits.push((
9161 row_start..row_start,
9162 indent_delta.chars().collect::<String>(),
9163 ));
9164
9165 // Update this selection's endpoints to reflect the indentation.
9166 if row == selection.start.row {
9167 selection.start.column += indent_delta.len;
9168 }
9169 if row == selection.end.row {
9170 selection.end.column += indent_delta.len;
9171 delta_for_end_row = indent_delta.len;
9172 }
9173 }
9174
9175 if selection.start.row == selection.end.row {
9176 delta_for_start_row + delta_for_end_row
9177 } else {
9178 delta_for_end_row
9179 }
9180 }
9181
9182 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9183 if self.read_only(cx) {
9184 return;
9185 }
9186 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9188 let selections = self.selections.all::<Point>(cx);
9189 let mut deletion_ranges = Vec::new();
9190 let mut last_outdent = None;
9191 {
9192 let buffer = self.buffer.read(cx);
9193 let snapshot = buffer.snapshot(cx);
9194 for selection in &selections {
9195 let settings = buffer.language_settings_at(selection.start, cx);
9196 let tab_size = settings.tab_size.get();
9197 let mut rows = selection.spanned_rows(false, &display_map);
9198
9199 // Avoid re-outdenting a row that has already been outdented by a
9200 // previous selection.
9201 if let Some(last_row) = last_outdent {
9202 if last_row == rows.start {
9203 rows.start = rows.start.next_row();
9204 }
9205 }
9206 let has_multiple_rows = rows.len() > 1;
9207 for row in rows.iter_rows() {
9208 let indent_size = snapshot.indent_size_for_line(row);
9209 if indent_size.len > 0 {
9210 let deletion_len = match indent_size.kind {
9211 IndentKind::Space => {
9212 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9213 if columns_to_prev_tab_stop == 0 {
9214 tab_size
9215 } else {
9216 columns_to_prev_tab_stop
9217 }
9218 }
9219 IndentKind::Tab => 1,
9220 };
9221 let start = if has_multiple_rows
9222 || deletion_len > selection.start.column
9223 || indent_size.len < selection.start.column
9224 {
9225 0
9226 } else {
9227 selection.start.column - deletion_len
9228 };
9229 deletion_ranges.push(
9230 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9231 );
9232 last_outdent = Some(row);
9233 }
9234 }
9235 }
9236 }
9237
9238 self.transact(window, cx, |this, window, cx| {
9239 this.buffer.update(cx, |buffer, cx| {
9240 let empty_str: Arc<str> = Arc::default();
9241 buffer.edit(
9242 deletion_ranges
9243 .into_iter()
9244 .map(|range| (range, empty_str.clone())),
9245 None,
9246 cx,
9247 );
9248 });
9249 let selections = this.selections.all::<usize>(cx);
9250 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9251 s.select(selections)
9252 });
9253 });
9254 }
9255
9256 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9257 if self.read_only(cx) {
9258 return;
9259 }
9260 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9261 let selections = self
9262 .selections
9263 .all::<usize>(cx)
9264 .into_iter()
9265 .map(|s| s.range());
9266
9267 self.transact(window, cx, |this, window, cx| {
9268 this.buffer.update(cx, |buffer, cx| {
9269 buffer.autoindent_ranges(selections, cx);
9270 });
9271 let selections = this.selections.all::<usize>(cx);
9272 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9273 s.select(selections)
9274 });
9275 });
9276 }
9277
9278 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9279 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9281 let selections = self.selections.all::<Point>(cx);
9282
9283 let mut new_cursors = Vec::new();
9284 let mut edit_ranges = Vec::new();
9285 let mut selections = selections.iter().peekable();
9286 while let Some(selection) = selections.next() {
9287 let mut rows = selection.spanned_rows(false, &display_map);
9288 let goal_display_column = selection.head().to_display_point(&display_map).column();
9289
9290 // Accumulate contiguous regions of rows that we want to delete.
9291 while let Some(next_selection) = selections.peek() {
9292 let next_rows = next_selection.spanned_rows(false, &display_map);
9293 if next_rows.start <= rows.end {
9294 rows.end = next_rows.end;
9295 selections.next().unwrap();
9296 } else {
9297 break;
9298 }
9299 }
9300
9301 let buffer = &display_map.buffer_snapshot;
9302 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9303 let edit_end;
9304 let cursor_buffer_row;
9305 if buffer.max_point().row >= rows.end.0 {
9306 // If there's a line after the range, delete the \n from the end of the row range
9307 // and position the cursor on the next line.
9308 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9309 cursor_buffer_row = rows.end;
9310 } else {
9311 // If there isn't a line after the range, delete the \n from the line before the
9312 // start of the row range and position the cursor there.
9313 edit_start = edit_start.saturating_sub(1);
9314 edit_end = buffer.len();
9315 cursor_buffer_row = rows.start.previous_row();
9316 }
9317
9318 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9319 *cursor.column_mut() =
9320 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9321
9322 new_cursors.push((
9323 selection.id,
9324 buffer.anchor_after(cursor.to_point(&display_map)),
9325 ));
9326 edit_ranges.push(edit_start..edit_end);
9327 }
9328
9329 self.transact(window, cx, |this, window, cx| {
9330 let buffer = this.buffer.update(cx, |buffer, cx| {
9331 let empty_str: Arc<str> = Arc::default();
9332 buffer.edit(
9333 edit_ranges
9334 .into_iter()
9335 .map(|range| (range, empty_str.clone())),
9336 None,
9337 cx,
9338 );
9339 buffer.snapshot(cx)
9340 });
9341 let new_selections = new_cursors
9342 .into_iter()
9343 .map(|(id, cursor)| {
9344 let cursor = cursor.to_point(&buffer);
9345 Selection {
9346 id,
9347 start: cursor,
9348 end: cursor,
9349 reversed: false,
9350 goal: SelectionGoal::None,
9351 }
9352 })
9353 .collect();
9354
9355 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9356 s.select(new_selections);
9357 });
9358 });
9359 }
9360
9361 pub fn join_lines_impl(
9362 &mut self,
9363 insert_whitespace: bool,
9364 window: &mut Window,
9365 cx: &mut Context<Self>,
9366 ) {
9367 if self.read_only(cx) {
9368 return;
9369 }
9370 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9371 for selection in self.selections.all::<Point>(cx) {
9372 let start = MultiBufferRow(selection.start.row);
9373 // Treat single line selections as if they include the next line. Otherwise this action
9374 // would do nothing for single line selections individual cursors.
9375 let end = if selection.start.row == selection.end.row {
9376 MultiBufferRow(selection.start.row + 1)
9377 } else {
9378 MultiBufferRow(selection.end.row)
9379 };
9380
9381 if let Some(last_row_range) = row_ranges.last_mut() {
9382 if start <= last_row_range.end {
9383 last_row_range.end = end;
9384 continue;
9385 }
9386 }
9387 row_ranges.push(start..end);
9388 }
9389
9390 let snapshot = self.buffer.read(cx).snapshot(cx);
9391 let mut cursor_positions = Vec::new();
9392 for row_range in &row_ranges {
9393 let anchor = snapshot.anchor_before(Point::new(
9394 row_range.end.previous_row().0,
9395 snapshot.line_len(row_range.end.previous_row()),
9396 ));
9397 cursor_positions.push(anchor..anchor);
9398 }
9399
9400 self.transact(window, cx, |this, window, cx| {
9401 for row_range in row_ranges.into_iter().rev() {
9402 for row in row_range.iter_rows().rev() {
9403 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9404 let next_line_row = row.next_row();
9405 let indent = snapshot.indent_size_for_line(next_line_row);
9406 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9407
9408 let replace =
9409 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9410 " "
9411 } else {
9412 ""
9413 };
9414
9415 this.buffer.update(cx, |buffer, cx| {
9416 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9417 });
9418 }
9419 }
9420
9421 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9422 s.select_anchor_ranges(cursor_positions)
9423 });
9424 });
9425 }
9426
9427 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9428 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9429 self.join_lines_impl(true, window, cx);
9430 }
9431
9432 pub fn sort_lines_case_sensitive(
9433 &mut self,
9434 _: &SortLinesCaseSensitive,
9435 window: &mut Window,
9436 cx: &mut Context<Self>,
9437 ) {
9438 self.manipulate_lines(window, cx, |lines| lines.sort())
9439 }
9440
9441 pub fn sort_lines_case_insensitive(
9442 &mut self,
9443 _: &SortLinesCaseInsensitive,
9444 window: &mut Window,
9445 cx: &mut Context<Self>,
9446 ) {
9447 self.manipulate_lines(window, cx, |lines| {
9448 lines.sort_by_key(|line| line.to_lowercase())
9449 })
9450 }
9451
9452 pub fn unique_lines_case_insensitive(
9453 &mut self,
9454 _: &UniqueLinesCaseInsensitive,
9455 window: &mut Window,
9456 cx: &mut Context<Self>,
9457 ) {
9458 self.manipulate_lines(window, cx, |lines| {
9459 let mut seen = HashSet::default();
9460 lines.retain(|line| seen.insert(line.to_lowercase()));
9461 })
9462 }
9463
9464 pub fn unique_lines_case_sensitive(
9465 &mut self,
9466 _: &UniqueLinesCaseSensitive,
9467 window: &mut Window,
9468 cx: &mut Context<Self>,
9469 ) {
9470 self.manipulate_lines(window, cx, |lines| {
9471 let mut seen = HashSet::default();
9472 lines.retain(|line| seen.insert(*line));
9473 })
9474 }
9475
9476 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9477 let Some(project) = self.project.clone() else {
9478 return;
9479 };
9480 self.reload(project, window, cx)
9481 .detach_and_notify_err(window, cx);
9482 }
9483
9484 pub fn restore_file(
9485 &mut self,
9486 _: &::git::RestoreFile,
9487 window: &mut Window,
9488 cx: &mut Context<Self>,
9489 ) {
9490 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9491 let mut buffer_ids = HashSet::default();
9492 let snapshot = self.buffer().read(cx).snapshot(cx);
9493 for selection in self.selections.all::<usize>(cx) {
9494 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9495 }
9496
9497 let buffer = self.buffer().read(cx);
9498 let ranges = buffer_ids
9499 .into_iter()
9500 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9501 .collect::<Vec<_>>();
9502
9503 self.restore_hunks_in_ranges(ranges, window, cx);
9504 }
9505
9506 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9507 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9508 let selections = self
9509 .selections
9510 .all(cx)
9511 .into_iter()
9512 .map(|s| s.range())
9513 .collect();
9514 self.restore_hunks_in_ranges(selections, window, cx);
9515 }
9516
9517 pub fn restore_hunks_in_ranges(
9518 &mut self,
9519 ranges: Vec<Range<Point>>,
9520 window: &mut Window,
9521 cx: &mut Context<Editor>,
9522 ) {
9523 let mut revert_changes = HashMap::default();
9524 let chunk_by = self
9525 .snapshot(window, cx)
9526 .hunks_for_ranges(ranges)
9527 .into_iter()
9528 .chunk_by(|hunk| hunk.buffer_id);
9529 for (buffer_id, hunks) in &chunk_by {
9530 let hunks = hunks.collect::<Vec<_>>();
9531 for hunk in &hunks {
9532 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9533 }
9534 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9535 }
9536 drop(chunk_by);
9537 if !revert_changes.is_empty() {
9538 self.transact(window, cx, |editor, window, cx| {
9539 editor.restore(revert_changes, window, cx);
9540 });
9541 }
9542 }
9543
9544 pub fn open_active_item_in_terminal(
9545 &mut self,
9546 _: &OpenInTerminal,
9547 window: &mut Window,
9548 cx: &mut Context<Self>,
9549 ) {
9550 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9551 let project_path = buffer.read(cx).project_path(cx)?;
9552 let project = self.project.as_ref()?.read(cx);
9553 let entry = project.entry_for_path(&project_path, cx)?;
9554 let parent = match &entry.canonical_path {
9555 Some(canonical_path) => canonical_path.to_path_buf(),
9556 None => project.absolute_path(&project_path, cx)?,
9557 }
9558 .parent()?
9559 .to_path_buf();
9560 Some(parent)
9561 }) {
9562 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9563 }
9564 }
9565
9566 fn set_breakpoint_context_menu(
9567 &mut self,
9568 display_row: DisplayRow,
9569 position: Option<Anchor>,
9570 clicked_point: gpui::Point<Pixels>,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) {
9574 if !cx.has_flag::<DebuggerFeatureFlag>() {
9575 return;
9576 }
9577 let source = self
9578 .buffer
9579 .read(cx)
9580 .snapshot(cx)
9581 .anchor_before(Point::new(display_row.0, 0u32));
9582
9583 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9584
9585 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9586 self,
9587 source,
9588 clicked_point,
9589 context_menu,
9590 window,
9591 cx,
9592 );
9593 }
9594
9595 fn add_edit_breakpoint_block(
9596 &mut self,
9597 anchor: Anchor,
9598 breakpoint: &Breakpoint,
9599 edit_action: BreakpointPromptEditAction,
9600 window: &mut Window,
9601 cx: &mut Context<Self>,
9602 ) {
9603 let weak_editor = cx.weak_entity();
9604 let bp_prompt = cx.new(|cx| {
9605 BreakpointPromptEditor::new(
9606 weak_editor,
9607 anchor,
9608 breakpoint.clone(),
9609 edit_action,
9610 window,
9611 cx,
9612 )
9613 });
9614
9615 let height = bp_prompt.update(cx, |this, cx| {
9616 this.prompt
9617 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9618 });
9619 let cloned_prompt = bp_prompt.clone();
9620 let blocks = vec![BlockProperties {
9621 style: BlockStyle::Sticky,
9622 placement: BlockPlacement::Above(anchor),
9623 height: Some(height),
9624 render: Arc::new(move |cx| {
9625 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9626 cloned_prompt.clone().into_any_element()
9627 }),
9628 priority: 0,
9629 render_in_minimap: true,
9630 }];
9631
9632 let focus_handle = bp_prompt.focus_handle(cx);
9633 window.focus(&focus_handle);
9634
9635 let block_ids = self.insert_blocks(blocks, None, cx);
9636 bp_prompt.update(cx, |prompt, _| {
9637 prompt.add_block_ids(block_ids);
9638 });
9639 }
9640
9641 pub(crate) fn breakpoint_at_row(
9642 &self,
9643 row: u32,
9644 window: &mut Window,
9645 cx: &mut Context<Self>,
9646 ) -> Option<(Anchor, Breakpoint)> {
9647 let snapshot = self.snapshot(window, cx);
9648 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9649
9650 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9651 }
9652
9653 pub(crate) fn breakpoint_at_anchor(
9654 &self,
9655 breakpoint_position: Anchor,
9656 snapshot: &EditorSnapshot,
9657 cx: &mut Context<Self>,
9658 ) -> Option<(Anchor, Breakpoint)> {
9659 let project = self.project.clone()?;
9660
9661 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9662 snapshot
9663 .buffer_snapshot
9664 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9665 })?;
9666
9667 let enclosing_excerpt = breakpoint_position.excerpt_id;
9668 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9669 let buffer_snapshot = buffer.read(cx).snapshot();
9670
9671 let row = buffer_snapshot
9672 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9673 .row;
9674
9675 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9676 let anchor_end = snapshot
9677 .buffer_snapshot
9678 .anchor_after(Point::new(row, line_len));
9679
9680 let bp = self
9681 .breakpoint_store
9682 .as_ref()?
9683 .read_with(cx, |breakpoint_store, cx| {
9684 breakpoint_store
9685 .breakpoints(
9686 &buffer,
9687 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9688 &buffer_snapshot,
9689 cx,
9690 )
9691 .next()
9692 .and_then(|(bp, _)| {
9693 let breakpoint_row = buffer_snapshot
9694 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9695 .row;
9696
9697 if breakpoint_row == row {
9698 snapshot
9699 .buffer_snapshot
9700 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9701 .map(|position| (position, bp.bp.clone()))
9702 } else {
9703 None
9704 }
9705 })
9706 });
9707 bp
9708 }
9709
9710 pub fn edit_log_breakpoint(
9711 &mut self,
9712 _: &EditLogBreakpoint,
9713 window: &mut Window,
9714 cx: &mut Context<Self>,
9715 ) {
9716 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9717 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9718 message: None,
9719 state: BreakpointState::Enabled,
9720 condition: None,
9721 hit_condition: None,
9722 });
9723
9724 self.add_edit_breakpoint_block(
9725 anchor,
9726 &breakpoint,
9727 BreakpointPromptEditAction::Log,
9728 window,
9729 cx,
9730 );
9731 }
9732 }
9733
9734 fn breakpoints_at_cursors(
9735 &self,
9736 window: &mut Window,
9737 cx: &mut Context<Self>,
9738 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9739 let snapshot = self.snapshot(window, cx);
9740 let cursors = self
9741 .selections
9742 .disjoint_anchors()
9743 .into_iter()
9744 .map(|selection| {
9745 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9746
9747 let breakpoint_position = self
9748 .breakpoint_at_row(cursor_position.row, window, cx)
9749 .map(|bp| bp.0)
9750 .unwrap_or_else(|| {
9751 snapshot
9752 .display_snapshot
9753 .buffer_snapshot
9754 .anchor_after(Point::new(cursor_position.row, 0))
9755 });
9756
9757 let breakpoint = self
9758 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9759 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9760
9761 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9762 })
9763 // 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.
9764 .collect::<HashMap<Anchor, _>>();
9765
9766 cursors.into_iter().collect()
9767 }
9768
9769 pub fn enable_breakpoint(
9770 &mut self,
9771 _: &crate::actions::EnableBreakpoint,
9772 window: &mut Window,
9773 cx: &mut Context<Self>,
9774 ) {
9775 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9776 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9777 continue;
9778 };
9779 self.edit_breakpoint_at_anchor(
9780 anchor,
9781 breakpoint,
9782 BreakpointEditAction::InvertState,
9783 cx,
9784 );
9785 }
9786 }
9787
9788 pub fn disable_breakpoint(
9789 &mut self,
9790 _: &crate::actions::DisableBreakpoint,
9791 window: &mut Window,
9792 cx: &mut Context<Self>,
9793 ) {
9794 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9795 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9796 continue;
9797 };
9798 self.edit_breakpoint_at_anchor(
9799 anchor,
9800 breakpoint,
9801 BreakpointEditAction::InvertState,
9802 cx,
9803 );
9804 }
9805 }
9806
9807 pub fn toggle_breakpoint(
9808 &mut self,
9809 _: &crate::actions::ToggleBreakpoint,
9810 window: &mut Window,
9811 cx: &mut Context<Self>,
9812 ) {
9813 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9814 if let Some(breakpoint) = breakpoint {
9815 self.edit_breakpoint_at_anchor(
9816 anchor,
9817 breakpoint,
9818 BreakpointEditAction::Toggle,
9819 cx,
9820 );
9821 } else {
9822 self.edit_breakpoint_at_anchor(
9823 anchor,
9824 Breakpoint::new_standard(),
9825 BreakpointEditAction::Toggle,
9826 cx,
9827 );
9828 }
9829 }
9830 }
9831
9832 pub fn edit_breakpoint_at_anchor(
9833 &mut self,
9834 breakpoint_position: Anchor,
9835 breakpoint: Breakpoint,
9836 edit_action: BreakpointEditAction,
9837 cx: &mut Context<Self>,
9838 ) {
9839 let Some(breakpoint_store) = &self.breakpoint_store else {
9840 return;
9841 };
9842
9843 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9844 if breakpoint_position == Anchor::min() {
9845 self.buffer()
9846 .read(cx)
9847 .excerpt_buffer_ids()
9848 .into_iter()
9849 .next()
9850 } else {
9851 None
9852 }
9853 }) else {
9854 return;
9855 };
9856
9857 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9858 return;
9859 };
9860
9861 breakpoint_store.update(cx, |breakpoint_store, cx| {
9862 breakpoint_store.toggle_breakpoint(
9863 buffer,
9864 BreakpointWithPosition {
9865 position: breakpoint_position.text_anchor,
9866 bp: breakpoint,
9867 },
9868 edit_action,
9869 cx,
9870 );
9871 });
9872
9873 cx.notify();
9874 }
9875
9876 #[cfg(any(test, feature = "test-support"))]
9877 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9878 self.breakpoint_store.clone()
9879 }
9880
9881 pub fn prepare_restore_change(
9882 &self,
9883 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9884 hunk: &MultiBufferDiffHunk,
9885 cx: &mut App,
9886 ) -> Option<()> {
9887 if hunk.is_created_file() {
9888 return None;
9889 }
9890 let buffer = self.buffer.read(cx);
9891 let diff = buffer.diff_for(hunk.buffer_id)?;
9892 let buffer = buffer.buffer(hunk.buffer_id)?;
9893 let buffer = buffer.read(cx);
9894 let original_text = diff
9895 .read(cx)
9896 .base_text()
9897 .as_rope()
9898 .slice(hunk.diff_base_byte_range.clone());
9899 let buffer_snapshot = buffer.snapshot();
9900 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9901 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9902 probe
9903 .0
9904 .start
9905 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9906 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9907 }) {
9908 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9909 Some(())
9910 } else {
9911 None
9912 }
9913 }
9914
9915 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9916 self.manipulate_lines(window, cx, |lines| lines.reverse())
9917 }
9918
9919 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9920 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9921 }
9922
9923 fn manipulate_lines<Fn>(
9924 &mut self,
9925 window: &mut Window,
9926 cx: &mut Context<Self>,
9927 mut callback: Fn,
9928 ) where
9929 Fn: FnMut(&mut Vec<&str>),
9930 {
9931 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9932
9933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9934 let buffer = self.buffer.read(cx).snapshot(cx);
9935
9936 let mut edits = Vec::new();
9937
9938 let selections = self.selections.all::<Point>(cx);
9939 let mut selections = selections.iter().peekable();
9940 let mut contiguous_row_selections = Vec::new();
9941 let mut new_selections = Vec::new();
9942 let mut added_lines = 0;
9943 let mut removed_lines = 0;
9944
9945 while let Some(selection) = selections.next() {
9946 let (start_row, end_row) = consume_contiguous_rows(
9947 &mut contiguous_row_selections,
9948 selection,
9949 &display_map,
9950 &mut selections,
9951 );
9952
9953 let start_point = Point::new(start_row.0, 0);
9954 let end_point = Point::new(
9955 end_row.previous_row().0,
9956 buffer.line_len(end_row.previous_row()),
9957 );
9958 let text = buffer
9959 .text_for_range(start_point..end_point)
9960 .collect::<String>();
9961
9962 let mut lines = text.split('\n').collect_vec();
9963
9964 let lines_before = lines.len();
9965 callback(&mut lines);
9966 let lines_after = lines.len();
9967
9968 edits.push((start_point..end_point, lines.join("\n")));
9969
9970 // Selections must change based on added and removed line count
9971 let start_row =
9972 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9973 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9974 new_selections.push(Selection {
9975 id: selection.id,
9976 start: start_row,
9977 end: end_row,
9978 goal: SelectionGoal::None,
9979 reversed: selection.reversed,
9980 });
9981
9982 if lines_after > lines_before {
9983 added_lines += lines_after - lines_before;
9984 } else if lines_before > lines_after {
9985 removed_lines += lines_before - lines_after;
9986 }
9987 }
9988
9989 self.transact(window, cx, |this, window, cx| {
9990 let buffer = this.buffer.update(cx, |buffer, cx| {
9991 buffer.edit(edits, None, cx);
9992 buffer.snapshot(cx)
9993 });
9994
9995 // Recalculate offsets on newly edited buffer
9996 let new_selections = new_selections
9997 .iter()
9998 .map(|s| {
9999 let start_point = Point::new(s.start.0, 0);
10000 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10001 Selection {
10002 id: s.id,
10003 start: buffer.point_to_offset(start_point),
10004 end: buffer.point_to_offset(end_point),
10005 goal: s.goal,
10006 reversed: s.reversed,
10007 }
10008 })
10009 .collect();
10010
10011 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10012 s.select(new_selections);
10013 });
10014
10015 this.request_autoscroll(Autoscroll::fit(), cx);
10016 });
10017 }
10018
10019 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10020 self.manipulate_text(window, cx, |text| {
10021 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10022 if has_upper_case_characters {
10023 text.to_lowercase()
10024 } else {
10025 text.to_uppercase()
10026 }
10027 })
10028 }
10029
10030 pub fn convert_to_upper_case(
10031 &mut self,
10032 _: &ConvertToUpperCase,
10033 window: &mut Window,
10034 cx: &mut Context<Self>,
10035 ) {
10036 self.manipulate_text(window, cx, |text| text.to_uppercase())
10037 }
10038
10039 pub fn convert_to_lower_case(
10040 &mut self,
10041 _: &ConvertToLowerCase,
10042 window: &mut Window,
10043 cx: &mut Context<Self>,
10044 ) {
10045 self.manipulate_text(window, cx, |text| text.to_lowercase())
10046 }
10047
10048 pub fn convert_to_title_case(
10049 &mut self,
10050 _: &ConvertToTitleCase,
10051 window: &mut Window,
10052 cx: &mut Context<Self>,
10053 ) {
10054 self.manipulate_text(window, cx, |text| {
10055 text.split('\n')
10056 .map(|line| line.to_case(Case::Title))
10057 .join("\n")
10058 })
10059 }
10060
10061 pub fn convert_to_snake_case(
10062 &mut self,
10063 _: &ConvertToSnakeCase,
10064 window: &mut Window,
10065 cx: &mut Context<Self>,
10066 ) {
10067 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10068 }
10069
10070 pub fn convert_to_kebab_case(
10071 &mut self,
10072 _: &ConvertToKebabCase,
10073 window: &mut Window,
10074 cx: &mut Context<Self>,
10075 ) {
10076 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10077 }
10078
10079 pub fn convert_to_upper_camel_case(
10080 &mut self,
10081 _: &ConvertToUpperCamelCase,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) {
10085 self.manipulate_text(window, cx, |text| {
10086 text.split('\n')
10087 .map(|line| line.to_case(Case::UpperCamel))
10088 .join("\n")
10089 })
10090 }
10091
10092 pub fn convert_to_lower_camel_case(
10093 &mut self,
10094 _: &ConvertToLowerCamelCase,
10095 window: &mut Window,
10096 cx: &mut Context<Self>,
10097 ) {
10098 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10099 }
10100
10101 pub fn convert_to_opposite_case(
10102 &mut self,
10103 _: &ConvertToOppositeCase,
10104 window: &mut Window,
10105 cx: &mut Context<Self>,
10106 ) {
10107 self.manipulate_text(window, cx, |text| {
10108 text.chars()
10109 .fold(String::with_capacity(text.len()), |mut t, c| {
10110 if c.is_uppercase() {
10111 t.extend(c.to_lowercase());
10112 } else {
10113 t.extend(c.to_uppercase());
10114 }
10115 t
10116 })
10117 })
10118 }
10119
10120 pub fn convert_to_rot13(
10121 &mut self,
10122 _: &ConvertToRot13,
10123 window: &mut Window,
10124 cx: &mut Context<Self>,
10125 ) {
10126 self.manipulate_text(window, cx, |text| {
10127 text.chars()
10128 .map(|c| match c {
10129 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10130 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10131 _ => c,
10132 })
10133 .collect()
10134 })
10135 }
10136
10137 pub fn convert_to_rot47(
10138 &mut self,
10139 _: &ConvertToRot47,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 self.manipulate_text(window, cx, |text| {
10144 text.chars()
10145 .map(|c| {
10146 let code_point = c as u32;
10147 if code_point >= 33 && code_point <= 126 {
10148 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10149 }
10150 c
10151 })
10152 .collect()
10153 })
10154 }
10155
10156 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10157 where
10158 Fn: FnMut(&str) -> String,
10159 {
10160 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10161 let buffer = self.buffer.read(cx).snapshot(cx);
10162
10163 let mut new_selections = Vec::new();
10164 let mut edits = Vec::new();
10165 let mut selection_adjustment = 0i32;
10166
10167 for selection in self.selections.all::<usize>(cx) {
10168 let selection_is_empty = selection.is_empty();
10169
10170 let (start, end) = if selection_is_empty {
10171 let word_range = movement::surrounding_word(
10172 &display_map,
10173 selection.start.to_display_point(&display_map),
10174 );
10175 let start = word_range.start.to_offset(&display_map, Bias::Left);
10176 let end = word_range.end.to_offset(&display_map, Bias::Left);
10177 (start, end)
10178 } else {
10179 (selection.start, selection.end)
10180 };
10181
10182 let text = buffer.text_for_range(start..end).collect::<String>();
10183 let old_length = text.len() as i32;
10184 let text = callback(&text);
10185
10186 new_selections.push(Selection {
10187 start: (start as i32 - selection_adjustment) as usize,
10188 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10189 goal: SelectionGoal::None,
10190 ..selection
10191 });
10192
10193 selection_adjustment += old_length - text.len() as i32;
10194
10195 edits.push((start..end, text));
10196 }
10197
10198 self.transact(window, cx, |this, window, cx| {
10199 this.buffer.update(cx, |buffer, cx| {
10200 buffer.edit(edits, None, cx);
10201 });
10202
10203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10204 s.select(new_selections);
10205 });
10206
10207 this.request_autoscroll(Autoscroll::fit(), cx);
10208 });
10209 }
10210
10211 pub fn duplicate(
10212 &mut self,
10213 upwards: bool,
10214 whole_lines: bool,
10215 window: &mut Window,
10216 cx: &mut Context<Self>,
10217 ) {
10218 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10219
10220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10221 let buffer = &display_map.buffer_snapshot;
10222 let selections = self.selections.all::<Point>(cx);
10223
10224 let mut edits = Vec::new();
10225 let mut selections_iter = selections.iter().peekable();
10226 while let Some(selection) = selections_iter.next() {
10227 let mut rows = selection.spanned_rows(false, &display_map);
10228 // duplicate line-wise
10229 if whole_lines || selection.start == selection.end {
10230 // Avoid duplicating the same lines twice.
10231 while let Some(next_selection) = selections_iter.peek() {
10232 let next_rows = next_selection.spanned_rows(false, &display_map);
10233 if next_rows.start < rows.end {
10234 rows.end = next_rows.end;
10235 selections_iter.next().unwrap();
10236 } else {
10237 break;
10238 }
10239 }
10240
10241 // Copy the text from the selected row region and splice it either at the start
10242 // or end of the region.
10243 let start = Point::new(rows.start.0, 0);
10244 let end = Point::new(
10245 rows.end.previous_row().0,
10246 buffer.line_len(rows.end.previous_row()),
10247 );
10248 let text = buffer
10249 .text_for_range(start..end)
10250 .chain(Some("\n"))
10251 .collect::<String>();
10252 let insert_location = if upwards {
10253 Point::new(rows.end.0, 0)
10254 } else {
10255 start
10256 };
10257 edits.push((insert_location..insert_location, text));
10258 } else {
10259 // duplicate character-wise
10260 let start = selection.start;
10261 let end = selection.end;
10262 let text = buffer.text_for_range(start..end).collect::<String>();
10263 edits.push((selection.end..selection.end, text));
10264 }
10265 }
10266
10267 self.transact(window, cx, |this, _, cx| {
10268 this.buffer.update(cx, |buffer, cx| {
10269 buffer.edit(edits, None, cx);
10270 });
10271
10272 this.request_autoscroll(Autoscroll::fit(), cx);
10273 });
10274 }
10275
10276 pub fn duplicate_line_up(
10277 &mut self,
10278 _: &DuplicateLineUp,
10279 window: &mut Window,
10280 cx: &mut Context<Self>,
10281 ) {
10282 self.duplicate(true, true, window, cx);
10283 }
10284
10285 pub fn duplicate_line_down(
10286 &mut self,
10287 _: &DuplicateLineDown,
10288 window: &mut Window,
10289 cx: &mut Context<Self>,
10290 ) {
10291 self.duplicate(false, true, window, cx);
10292 }
10293
10294 pub fn duplicate_selection(
10295 &mut self,
10296 _: &DuplicateSelection,
10297 window: &mut Window,
10298 cx: &mut Context<Self>,
10299 ) {
10300 self.duplicate(false, false, window, cx);
10301 }
10302
10303 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10304 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10305
10306 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10307 let buffer = self.buffer.read(cx).snapshot(cx);
10308
10309 let mut edits = Vec::new();
10310 let mut unfold_ranges = Vec::new();
10311 let mut refold_creases = Vec::new();
10312
10313 let selections = self.selections.all::<Point>(cx);
10314 let mut selections = selections.iter().peekable();
10315 let mut contiguous_row_selections = Vec::new();
10316 let mut new_selections = Vec::new();
10317
10318 while let Some(selection) = selections.next() {
10319 // Find all the selections that span a contiguous row range
10320 let (start_row, end_row) = consume_contiguous_rows(
10321 &mut contiguous_row_selections,
10322 selection,
10323 &display_map,
10324 &mut selections,
10325 );
10326
10327 // Move the text spanned by the row range to be before the line preceding the row range
10328 if start_row.0 > 0 {
10329 let range_to_move = Point::new(
10330 start_row.previous_row().0,
10331 buffer.line_len(start_row.previous_row()),
10332 )
10333 ..Point::new(
10334 end_row.previous_row().0,
10335 buffer.line_len(end_row.previous_row()),
10336 );
10337 let insertion_point = display_map
10338 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10339 .0;
10340
10341 // Don't move lines across excerpts
10342 if buffer
10343 .excerpt_containing(insertion_point..range_to_move.end)
10344 .is_some()
10345 {
10346 let text = buffer
10347 .text_for_range(range_to_move.clone())
10348 .flat_map(|s| s.chars())
10349 .skip(1)
10350 .chain(['\n'])
10351 .collect::<String>();
10352
10353 edits.push((
10354 buffer.anchor_after(range_to_move.start)
10355 ..buffer.anchor_before(range_to_move.end),
10356 String::new(),
10357 ));
10358 let insertion_anchor = buffer.anchor_after(insertion_point);
10359 edits.push((insertion_anchor..insertion_anchor, text));
10360
10361 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10362
10363 // Move selections up
10364 new_selections.extend(contiguous_row_selections.drain(..).map(
10365 |mut selection| {
10366 selection.start.row -= row_delta;
10367 selection.end.row -= row_delta;
10368 selection
10369 },
10370 ));
10371
10372 // Move folds up
10373 unfold_ranges.push(range_to_move.clone());
10374 for fold in display_map.folds_in_range(
10375 buffer.anchor_before(range_to_move.start)
10376 ..buffer.anchor_after(range_to_move.end),
10377 ) {
10378 let mut start = fold.range.start.to_point(&buffer);
10379 let mut end = fold.range.end.to_point(&buffer);
10380 start.row -= row_delta;
10381 end.row -= row_delta;
10382 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10383 }
10384 }
10385 }
10386
10387 // If we didn't move line(s), preserve the existing selections
10388 new_selections.append(&mut contiguous_row_selections);
10389 }
10390
10391 self.transact(window, cx, |this, window, cx| {
10392 this.unfold_ranges(&unfold_ranges, true, true, cx);
10393 this.buffer.update(cx, |buffer, cx| {
10394 for (range, text) in edits {
10395 buffer.edit([(range, text)], None, cx);
10396 }
10397 });
10398 this.fold_creases(refold_creases, true, window, cx);
10399 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10400 s.select(new_selections);
10401 })
10402 });
10403 }
10404
10405 pub fn move_line_down(
10406 &mut self,
10407 _: &MoveLineDown,
10408 window: &mut Window,
10409 cx: &mut Context<Self>,
10410 ) {
10411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10412
10413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10414 let buffer = self.buffer.read(cx).snapshot(cx);
10415
10416 let mut edits = Vec::new();
10417 let mut unfold_ranges = Vec::new();
10418 let mut refold_creases = Vec::new();
10419
10420 let selections = self.selections.all::<Point>(cx);
10421 let mut selections = selections.iter().peekable();
10422 let mut contiguous_row_selections = Vec::new();
10423 let mut new_selections = Vec::new();
10424
10425 while let Some(selection) = selections.next() {
10426 // Find all the selections that span a contiguous row range
10427 let (start_row, end_row) = consume_contiguous_rows(
10428 &mut contiguous_row_selections,
10429 selection,
10430 &display_map,
10431 &mut selections,
10432 );
10433
10434 // Move the text spanned by the row range to be after the last line of the row range
10435 if end_row.0 <= buffer.max_point().row {
10436 let range_to_move =
10437 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10438 let insertion_point = display_map
10439 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10440 .0;
10441
10442 // Don't move lines across excerpt boundaries
10443 if buffer
10444 .excerpt_containing(range_to_move.start..insertion_point)
10445 .is_some()
10446 {
10447 let mut text = String::from("\n");
10448 text.extend(buffer.text_for_range(range_to_move.clone()));
10449 text.pop(); // Drop trailing newline
10450 edits.push((
10451 buffer.anchor_after(range_to_move.start)
10452 ..buffer.anchor_before(range_to_move.end),
10453 String::new(),
10454 ));
10455 let insertion_anchor = buffer.anchor_after(insertion_point);
10456 edits.push((insertion_anchor..insertion_anchor, text));
10457
10458 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10459
10460 // Move selections down
10461 new_selections.extend(contiguous_row_selections.drain(..).map(
10462 |mut selection| {
10463 selection.start.row += row_delta;
10464 selection.end.row += row_delta;
10465 selection
10466 },
10467 ));
10468
10469 // Move folds down
10470 unfold_ranges.push(range_to_move.clone());
10471 for fold in display_map.folds_in_range(
10472 buffer.anchor_before(range_to_move.start)
10473 ..buffer.anchor_after(range_to_move.end),
10474 ) {
10475 let mut start = fold.range.start.to_point(&buffer);
10476 let mut end = fold.range.end.to_point(&buffer);
10477 start.row += row_delta;
10478 end.row += row_delta;
10479 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10480 }
10481 }
10482 }
10483
10484 // If we didn't move line(s), preserve the existing selections
10485 new_selections.append(&mut contiguous_row_selections);
10486 }
10487
10488 self.transact(window, cx, |this, window, cx| {
10489 this.unfold_ranges(&unfold_ranges, true, true, cx);
10490 this.buffer.update(cx, |buffer, cx| {
10491 for (range, text) in edits {
10492 buffer.edit([(range, text)], None, cx);
10493 }
10494 });
10495 this.fold_creases(refold_creases, true, window, cx);
10496 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10497 s.select(new_selections)
10498 });
10499 });
10500 }
10501
10502 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10503 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10504 let text_layout_details = &self.text_layout_details(window);
10505 self.transact(window, cx, |this, window, cx| {
10506 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10507 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10508 s.move_with(|display_map, selection| {
10509 if !selection.is_empty() {
10510 return;
10511 }
10512
10513 let mut head = selection.head();
10514 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10515 if head.column() == display_map.line_len(head.row()) {
10516 transpose_offset = display_map
10517 .buffer_snapshot
10518 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10519 }
10520
10521 if transpose_offset == 0 {
10522 return;
10523 }
10524
10525 *head.column_mut() += 1;
10526 head = display_map.clip_point(head, Bias::Right);
10527 let goal = SelectionGoal::HorizontalPosition(
10528 display_map
10529 .x_for_display_point(head, text_layout_details)
10530 .into(),
10531 );
10532 selection.collapse_to(head, goal);
10533
10534 let transpose_start = display_map
10535 .buffer_snapshot
10536 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10537 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10538 let transpose_end = display_map
10539 .buffer_snapshot
10540 .clip_offset(transpose_offset + 1, Bias::Right);
10541 if let Some(ch) =
10542 display_map.buffer_snapshot.chars_at(transpose_start).next()
10543 {
10544 edits.push((transpose_start..transpose_offset, String::new()));
10545 edits.push((transpose_end..transpose_end, ch.to_string()));
10546 }
10547 }
10548 });
10549 edits
10550 });
10551 this.buffer
10552 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10553 let selections = this.selections.all::<usize>(cx);
10554 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10555 s.select(selections);
10556 });
10557 });
10558 }
10559
10560 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10561 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10562 self.rewrap_impl(RewrapOptions::default(), cx)
10563 }
10564
10565 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10566 let buffer = self.buffer.read(cx).snapshot(cx);
10567 let selections = self.selections.all::<Point>(cx);
10568 let mut selections = selections.iter().peekable();
10569
10570 let mut edits = Vec::new();
10571 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10572
10573 while let Some(selection) = selections.next() {
10574 let mut start_row = selection.start.row;
10575 let mut end_row = selection.end.row;
10576
10577 // Skip selections that overlap with a range that has already been rewrapped.
10578 let selection_range = start_row..end_row;
10579 if rewrapped_row_ranges
10580 .iter()
10581 .any(|range| range.overlaps(&selection_range))
10582 {
10583 continue;
10584 }
10585
10586 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10587
10588 // Since not all lines in the selection may be at the same indent
10589 // level, choose the indent size that is the most common between all
10590 // of the lines.
10591 //
10592 // If there is a tie, we use the deepest indent.
10593 let (indent_size, indent_end) = {
10594 let mut indent_size_occurrences = HashMap::default();
10595 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10596
10597 for row in start_row..=end_row {
10598 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10599 rows_by_indent_size.entry(indent).or_default().push(row);
10600 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10601 }
10602
10603 let indent_size = indent_size_occurrences
10604 .into_iter()
10605 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10606 .map(|(indent, _)| indent)
10607 .unwrap_or_default();
10608 let row = rows_by_indent_size[&indent_size][0];
10609 let indent_end = Point::new(row, indent_size.len);
10610
10611 (indent_size, indent_end)
10612 };
10613
10614 let mut line_prefix = indent_size.chars().collect::<String>();
10615
10616 let mut inside_comment = false;
10617 if let Some(comment_prefix) =
10618 buffer
10619 .language_scope_at(selection.head())
10620 .and_then(|language| {
10621 language
10622 .line_comment_prefixes()
10623 .iter()
10624 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10625 .cloned()
10626 })
10627 {
10628 line_prefix.push_str(&comment_prefix);
10629 inside_comment = true;
10630 }
10631
10632 let language_settings = buffer.language_settings_at(selection.head(), cx);
10633 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10634 RewrapBehavior::InComments => inside_comment,
10635 RewrapBehavior::InSelections => !selection.is_empty(),
10636 RewrapBehavior::Anywhere => true,
10637 };
10638
10639 let should_rewrap = options.override_language_settings
10640 || allow_rewrap_based_on_language
10641 || self.hard_wrap.is_some();
10642 if !should_rewrap {
10643 continue;
10644 }
10645
10646 if selection.is_empty() {
10647 'expand_upwards: while start_row > 0 {
10648 let prev_row = start_row - 1;
10649 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10650 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10651 {
10652 start_row = prev_row;
10653 } else {
10654 break 'expand_upwards;
10655 }
10656 }
10657
10658 'expand_downwards: while end_row < buffer.max_point().row {
10659 let next_row = end_row + 1;
10660 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10661 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10662 {
10663 end_row = next_row;
10664 } else {
10665 break 'expand_downwards;
10666 }
10667 }
10668 }
10669
10670 let start = Point::new(start_row, 0);
10671 let start_offset = start.to_offset(&buffer);
10672 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10673 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10674 let Some(lines_without_prefixes) = selection_text
10675 .lines()
10676 .map(|line| {
10677 line.strip_prefix(&line_prefix)
10678 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10679 .with_context(|| {
10680 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10681 })
10682 })
10683 .collect::<Result<Vec<_>, _>>()
10684 .log_err()
10685 else {
10686 continue;
10687 };
10688
10689 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10690 buffer
10691 .language_settings_at(Point::new(start_row, 0), cx)
10692 .preferred_line_length as usize
10693 });
10694 let wrapped_text = wrap_with_prefix(
10695 line_prefix,
10696 lines_without_prefixes.join("\n"),
10697 wrap_column,
10698 tab_size,
10699 options.preserve_existing_whitespace,
10700 );
10701
10702 // TODO: should always use char-based diff while still supporting cursor behavior that
10703 // matches vim.
10704 let mut diff_options = DiffOptions::default();
10705 if options.override_language_settings {
10706 diff_options.max_word_diff_len = 0;
10707 diff_options.max_word_diff_line_count = 0;
10708 } else {
10709 diff_options.max_word_diff_len = usize::MAX;
10710 diff_options.max_word_diff_line_count = usize::MAX;
10711 }
10712
10713 for (old_range, new_text) in
10714 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10715 {
10716 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10717 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10718 edits.push((edit_start..edit_end, new_text));
10719 }
10720
10721 rewrapped_row_ranges.push(start_row..=end_row);
10722 }
10723
10724 self.buffer
10725 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10726 }
10727
10728 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10729 let mut text = String::new();
10730 let buffer = self.buffer.read(cx).snapshot(cx);
10731 let mut selections = self.selections.all::<Point>(cx);
10732 let mut clipboard_selections = Vec::with_capacity(selections.len());
10733 {
10734 let max_point = buffer.max_point();
10735 let mut is_first = true;
10736 for selection in &mut selections {
10737 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10738 if is_entire_line {
10739 selection.start = Point::new(selection.start.row, 0);
10740 if !selection.is_empty() && selection.end.column == 0 {
10741 selection.end = cmp::min(max_point, selection.end);
10742 } else {
10743 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10744 }
10745 selection.goal = SelectionGoal::None;
10746 }
10747 if is_first {
10748 is_first = false;
10749 } else {
10750 text += "\n";
10751 }
10752 let mut len = 0;
10753 for chunk in buffer.text_for_range(selection.start..selection.end) {
10754 text.push_str(chunk);
10755 len += chunk.len();
10756 }
10757 clipboard_selections.push(ClipboardSelection {
10758 len,
10759 is_entire_line,
10760 first_line_indent: buffer
10761 .indent_size_for_line(MultiBufferRow(selection.start.row))
10762 .len,
10763 });
10764 }
10765 }
10766
10767 self.transact(window, cx, |this, window, cx| {
10768 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10769 s.select(selections);
10770 });
10771 this.insert("", window, cx);
10772 });
10773 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10774 }
10775
10776 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10777 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10778 let item = self.cut_common(window, cx);
10779 cx.write_to_clipboard(item);
10780 }
10781
10782 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10783 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10784 self.change_selections(None, window, cx, |s| {
10785 s.move_with(|snapshot, sel| {
10786 if sel.is_empty() {
10787 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10788 }
10789 });
10790 });
10791 let item = self.cut_common(window, cx);
10792 cx.set_global(KillRing(item))
10793 }
10794
10795 pub fn kill_ring_yank(
10796 &mut self,
10797 _: &KillRingYank,
10798 window: &mut Window,
10799 cx: &mut Context<Self>,
10800 ) {
10801 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10802 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10803 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10804 (kill_ring.text().to_string(), kill_ring.metadata_json())
10805 } else {
10806 return;
10807 }
10808 } else {
10809 return;
10810 };
10811 self.do_paste(&text, metadata, false, window, cx);
10812 }
10813
10814 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10815 self.do_copy(true, cx);
10816 }
10817
10818 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10819 self.do_copy(false, cx);
10820 }
10821
10822 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10823 let selections = self.selections.all::<Point>(cx);
10824 let buffer = self.buffer.read(cx).read(cx);
10825 let mut text = String::new();
10826
10827 let mut clipboard_selections = Vec::with_capacity(selections.len());
10828 {
10829 let max_point = buffer.max_point();
10830 let mut is_first = true;
10831 for selection in &selections {
10832 let mut start = selection.start;
10833 let mut end = selection.end;
10834 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10835 if is_entire_line {
10836 start = Point::new(start.row, 0);
10837 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10838 }
10839
10840 let mut trimmed_selections = Vec::new();
10841 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10842 let row = MultiBufferRow(start.row);
10843 let first_indent = buffer.indent_size_for_line(row);
10844 if first_indent.len == 0 || start.column > first_indent.len {
10845 trimmed_selections.push(start..end);
10846 } else {
10847 trimmed_selections.push(
10848 Point::new(row.0, first_indent.len)
10849 ..Point::new(row.0, buffer.line_len(row)),
10850 );
10851 for row in start.row + 1..=end.row {
10852 let mut line_len = buffer.line_len(MultiBufferRow(row));
10853 if row == end.row {
10854 line_len = end.column;
10855 }
10856 if line_len == 0 {
10857 trimmed_selections
10858 .push(Point::new(row, 0)..Point::new(row, line_len));
10859 continue;
10860 }
10861 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10862 if row_indent_size.len >= first_indent.len {
10863 trimmed_selections.push(
10864 Point::new(row, first_indent.len)..Point::new(row, line_len),
10865 );
10866 } else {
10867 trimmed_selections.clear();
10868 trimmed_selections.push(start..end);
10869 break;
10870 }
10871 }
10872 }
10873 } else {
10874 trimmed_selections.push(start..end);
10875 }
10876
10877 for trimmed_range in trimmed_selections {
10878 if is_first {
10879 is_first = false;
10880 } else {
10881 text += "\n";
10882 }
10883 let mut len = 0;
10884 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10885 text.push_str(chunk);
10886 len += chunk.len();
10887 }
10888 clipboard_selections.push(ClipboardSelection {
10889 len,
10890 is_entire_line,
10891 first_line_indent: buffer
10892 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10893 .len,
10894 });
10895 }
10896 }
10897 }
10898
10899 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10900 text,
10901 clipboard_selections,
10902 ));
10903 }
10904
10905 pub fn do_paste(
10906 &mut self,
10907 text: &String,
10908 clipboard_selections: Option<Vec<ClipboardSelection>>,
10909 handle_entire_lines: bool,
10910 window: &mut Window,
10911 cx: &mut Context<Self>,
10912 ) {
10913 if self.read_only(cx) {
10914 return;
10915 }
10916
10917 let clipboard_text = Cow::Borrowed(text);
10918
10919 self.transact(window, cx, |this, window, cx| {
10920 if let Some(mut clipboard_selections) = clipboard_selections {
10921 let old_selections = this.selections.all::<usize>(cx);
10922 let all_selections_were_entire_line =
10923 clipboard_selections.iter().all(|s| s.is_entire_line);
10924 let first_selection_indent_column =
10925 clipboard_selections.first().map(|s| s.first_line_indent);
10926 if clipboard_selections.len() != old_selections.len() {
10927 clipboard_selections.drain(..);
10928 }
10929 let cursor_offset = this.selections.last::<usize>(cx).head();
10930 let mut auto_indent_on_paste = true;
10931
10932 this.buffer.update(cx, |buffer, cx| {
10933 let snapshot = buffer.read(cx);
10934 auto_indent_on_paste = snapshot
10935 .language_settings_at(cursor_offset, cx)
10936 .auto_indent_on_paste;
10937
10938 let mut start_offset = 0;
10939 let mut edits = Vec::new();
10940 let mut original_indent_columns = Vec::new();
10941 for (ix, selection) in old_selections.iter().enumerate() {
10942 let to_insert;
10943 let entire_line;
10944 let original_indent_column;
10945 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10946 let end_offset = start_offset + clipboard_selection.len;
10947 to_insert = &clipboard_text[start_offset..end_offset];
10948 entire_line = clipboard_selection.is_entire_line;
10949 start_offset = end_offset + 1;
10950 original_indent_column = Some(clipboard_selection.first_line_indent);
10951 } else {
10952 to_insert = clipboard_text.as_str();
10953 entire_line = all_selections_were_entire_line;
10954 original_indent_column = first_selection_indent_column
10955 }
10956
10957 // If the corresponding selection was empty when this slice of the
10958 // clipboard text was written, then the entire line containing the
10959 // selection was copied. If this selection is also currently empty,
10960 // then paste the line before the current line of the buffer.
10961 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10962 let column = selection.start.to_point(&snapshot).column as usize;
10963 let line_start = selection.start - column;
10964 line_start..line_start
10965 } else {
10966 selection.range()
10967 };
10968
10969 edits.push((range, to_insert));
10970 original_indent_columns.push(original_indent_column);
10971 }
10972 drop(snapshot);
10973
10974 buffer.edit(
10975 edits,
10976 if auto_indent_on_paste {
10977 Some(AutoindentMode::Block {
10978 original_indent_columns,
10979 })
10980 } else {
10981 None
10982 },
10983 cx,
10984 );
10985 });
10986
10987 let selections = this.selections.all::<usize>(cx);
10988 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10989 s.select(selections)
10990 });
10991 } else {
10992 this.insert(&clipboard_text, window, cx);
10993 }
10994 });
10995 }
10996
10997 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10998 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10999 if let Some(item) = cx.read_from_clipboard() {
11000 let entries = item.entries();
11001
11002 match entries.first() {
11003 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11004 // of all the pasted entries.
11005 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11006 .do_paste(
11007 clipboard_string.text(),
11008 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11009 true,
11010 window,
11011 cx,
11012 ),
11013 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11014 }
11015 }
11016 }
11017
11018 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11019 if self.read_only(cx) {
11020 return;
11021 }
11022
11023 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11024
11025 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11026 if let Some((selections, _)) =
11027 self.selection_history.transaction(transaction_id).cloned()
11028 {
11029 self.change_selections(None, window, cx, |s| {
11030 s.select_anchors(selections.to_vec());
11031 });
11032 } else {
11033 log::error!(
11034 "No entry in selection_history found for undo. \
11035 This may correspond to a bug where undo does not update the selection. \
11036 If this is occurring, please add details to \
11037 https://github.com/zed-industries/zed/issues/22692"
11038 );
11039 }
11040 self.request_autoscroll(Autoscroll::fit(), cx);
11041 self.unmark_text(window, cx);
11042 self.refresh_inline_completion(true, false, window, cx);
11043 cx.emit(EditorEvent::Edited { transaction_id });
11044 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11045 }
11046 }
11047
11048 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11049 if self.read_only(cx) {
11050 return;
11051 }
11052
11053 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11054
11055 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11056 if let Some((_, Some(selections))) =
11057 self.selection_history.transaction(transaction_id).cloned()
11058 {
11059 self.change_selections(None, window, cx, |s| {
11060 s.select_anchors(selections.to_vec());
11061 });
11062 } else {
11063 log::error!(
11064 "No entry in selection_history found for redo. \
11065 This may correspond to a bug where undo does not update the selection. \
11066 If this is occurring, please add details to \
11067 https://github.com/zed-industries/zed/issues/22692"
11068 );
11069 }
11070 self.request_autoscroll(Autoscroll::fit(), cx);
11071 self.unmark_text(window, cx);
11072 self.refresh_inline_completion(true, false, window, cx);
11073 cx.emit(EditorEvent::Edited { transaction_id });
11074 }
11075 }
11076
11077 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11078 self.buffer
11079 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11080 }
11081
11082 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11083 self.buffer
11084 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11085 }
11086
11087 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11088 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11089 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11090 s.move_with(|map, selection| {
11091 let cursor = if selection.is_empty() {
11092 movement::left(map, selection.start)
11093 } else {
11094 selection.start
11095 };
11096 selection.collapse_to(cursor, SelectionGoal::None);
11097 });
11098 })
11099 }
11100
11101 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11102 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11103 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11104 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11105 })
11106 }
11107
11108 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11109 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11110 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11111 s.move_with(|map, selection| {
11112 let cursor = if selection.is_empty() {
11113 movement::right(map, selection.end)
11114 } else {
11115 selection.end
11116 };
11117 selection.collapse_to(cursor, SelectionGoal::None)
11118 });
11119 })
11120 }
11121
11122 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11123 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11124 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11125 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11126 })
11127 }
11128
11129 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11130 if self.take_rename(true, window, cx).is_some() {
11131 return;
11132 }
11133
11134 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11135 cx.propagate();
11136 return;
11137 }
11138
11139 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11140
11141 let text_layout_details = &self.text_layout_details(window);
11142 let selection_count = self.selections.count();
11143 let first_selection = self.selections.first_anchor();
11144
11145 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11146 s.move_with(|map, selection| {
11147 if !selection.is_empty() {
11148 selection.goal = SelectionGoal::None;
11149 }
11150 let (cursor, goal) = movement::up(
11151 map,
11152 selection.start,
11153 selection.goal,
11154 false,
11155 text_layout_details,
11156 );
11157 selection.collapse_to(cursor, goal);
11158 });
11159 });
11160
11161 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11162 {
11163 cx.propagate();
11164 }
11165 }
11166
11167 pub fn move_up_by_lines(
11168 &mut self,
11169 action: &MoveUpByLines,
11170 window: &mut Window,
11171 cx: &mut Context<Self>,
11172 ) {
11173 if self.take_rename(true, window, cx).is_some() {
11174 return;
11175 }
11176
11177 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11178 cx.propagate();
11179 return;
11180 }
11181
11182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11183
11184 let text_layout_details = &self.text_layout_details(window);
11185
11186 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11187 s.move_with(|map, selection| {
11188 if !selection.is_empty() {
11189 selection.goal = SelectionGoal::None;
11190 }
11191 let (cursor, goal) = movement::up_by_rows(
11192 map,
11193 selection.start,
11194 action.lines,
11195 selection.goal,
11196 false,
11197 text_layout_details,
11198 );
11199 selection.collapse_to(cursor, goal);
11200 });
11201 })
11202 }
11203
11204 pub fn move_down_by_lines(
11205 &mut self,
11206 action: &MoveDownByLines,
11207 window: &mut Window,
11208 cx: &mut Context<Self>,
11209 ) {
11210 if self.take_rename(true, window, cx).is_some() {
11211 return;
11212 }
11213
11214 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11215 cx.propagate();
11216 return;
11217 }
11218
11219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11220
11221 let text_layout_details = &self.text_layout_details(window);
11222
11223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11224 s.move_with(|map, selection| {
11225 if !selection.is_empty() {
11226 selection.goal = SelectionGoal::None;
11227 }
11228 let (cursor, goal) = movement::down_by_rows(
11229 map,
11230 selection.start,
11231 action.lines,
11232 selection.goal,
11233 false,
11234 text_layout_details,
11235 );
11236 selection.collapse_to(cursor, goal);
11237 });
11238 })
11239 }
11240
11241 pub fn select_down_by_lines(
11242 &mut self,
11243 action: &SelectDownByLines,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11248 let text_layout_details = &self.text_layout_details(window);
11249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11250 s.move_heads_with(|map, head, goal| {
11251 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11252 })
11253 })
11254 }
11255
11256 pub fn select_up_by_lines(
11257 &mut self,
11258 action: &SelectUpByLines,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11263 let text_layout_details = &self.text_layout_details(window);
11264 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11265 s.move_heads_with(|map, head, goal| {
11266 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11267 })
11268 })
11269 }
11270
11271 pub fn select_page_up(
11272 &mut self,
11273 _: &SelectPageUp,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 let Some(row_count) = self.visible_row_count() else {
11278 return;
11279 };
11280
11281 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11282
11283 let text_layout_details = &self.text_layout_details(window);
11284
11285 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11286 s.move_heads_with(|map, head, goal| {
11287 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11288 })
11289 })
11290 }
11291
11292 pub fn move_page_up(
11293 &mut self,
11294 action: &MovePageUp,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 if self.take_rename(true, window, cx).is_some() {
11299 return;
11300 }
11301
11302 if self
11303 .context_menu
11304 .borrow_mut()
11305 .as_mut()
11306 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11307 .unwrap_or(false)
11308 {
11309 return;
11310 }
11311
11312 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11313 cx.propagate();
11314 return;
11315 }
11316
11317 let Some(row_count) = self.visible_row_count() else {
11318 return;
11319 };
11320
11321 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11322
11323 let autoscroll = if action.center_cursor {
11324 Autoscroll::center()
11325 } else {
11326 Autoscroll::fit()
11327 };
11328
11329 let text_layout_details = &self.text_layout_details(window);
11330
11331 self.change_selections(Some(autoscroll), window, cx, |s| {
11332 s.move_with(|map, selection| {
11333 if !selection.is_empty() {
11334 selection.goal = SelectionGoal::None;
11335 }
11336 let (cursor, goal) = movement::up_by_rows(
11337 map,
11338 selection.end,
11339 row_count,
11340 selection.goal,
11341 false,
11342 text_layout_details,
11343 );
11344 selection.collapse_to(cursor, goal);
11345 });
11346 });
11347 }
11348
11349 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11350 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11351 let text_layout_details = &self.text_layout_details(window);
11352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11353 s.move_heads_with(|map, head, goal| {
11354 movement::up(map, head, goal, false, text_layout_details)
11355 })
11356 })
11357 }
11358
11359 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11360 self.take_rename(true, window, cx);
11361
11362 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11363 cx.propagate();
11364 return;
11365 }
11366
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11368
11369 let text_layout_details = &self.text_layout_details(window);
11370 let selection_count = self.selections.count();
11371 let first_selection = self.selections.first_anchor();
11372
11373 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11374 s.move_with(|map, selection| {
11375 if !selection.is_empty() {
11376 selection.goal = SelectionGoal::None;
11377 }
11378 let (cursor, goal) = movement::down(
11379 map,
11380 selection.end,
11381 selection.goal,
11382 false,
11383 text_layout_details,
11384 );
11385 selection.collapse_to(cursor, goal);
11386 });
11387 });
11388
11389 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11390 {
11391 cx.propagate();
11392 }
11393 }
11394
11395 pub fn select_page_down(
11396 &mut self,
11397 _: &SelectPageDown,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 let Some(row_count) = self.visible_row_count() else {
11402 return;
11403 };
11404
11405 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11406
11407 let text_layout_details = &self.text_layout_details(window);
11408
11409 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11410 s.move_heads_with(|map, head, goal| {
11411 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11412 })
11413 })
11414 }
11415
11416 pub fn move_page_down(
11417 &mut self,
11418 action: &MovePageDown,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 if self.take_rename(true, window, cx).is_some() {
11423 return;
11424 }
11425
11426 if self
11427 .context_menu
11428 .borrow_mut()
11429 .as_mut()
11430 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11431 .unwrap_or(false)
11432 {
11433 return;
11434 }
11435
11436 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11437 cx.propagate();
11438 return;
11439 }
11440
11441 let Some(row_count) = self.visible_row_count() else {
11442 return;
11443 };
11444
11445 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11446
11447 let autoscroll = if action.center_cursor {
11448 Autoscroll::center()
11449 } else {
11450 Autoscroll::fit()
11451 };
11452
11453 let text_layout_details = &self.text_layout_details(window);
11454 self.change_selections(Some(autoscroll), window, cx, |s| {
11455 s.move_with(|map, selection| {
11456 if !selection.is_empty() {
11457 selection.goal = SelectionGoal::None;
11458 }
11459 let (cursor, goal) = movement::down_by_rows(
11460 map,
11461 selection.end,
11462 row_count,
11463 selection.goal,
11464 false,
11465 text_layout_details,
11466 );
11467 selection.collapse_to(cursor, goal);
11468 });
11469 });
11470 }
11471
11472 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11474 let text_layout_details = &self.text_layout_details(window);
11475 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11476 s.move_heads_with(|map, head, goal| {
11477 movement::down(map, head, goal, false, text_layout_details)
11478 })
11479 });
11480 }
11481
11482 pub fn context_menu_first(
11483 &mut self,
11484 _: &ContextMenuFirst,
11485 _window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11489 context_menu.select_first(self.completion_provider.as_deref(), cx);
11490 }
11491 }
11492
11493 pub fn context_menu_prev(
11494 &mut self,
11495 _: &ContextMenuPrevious,
11496 _window: &mut Window,
11497 cx: &mut Context<Self>,
11498 ) {
11499 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11500 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11501 }
11502 }
11503
11504 pub fn context_menu_next(
11505 &mut self,
11506 _: &ContextMenuNext,
11507 _window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11511 context_menu.select_next(self.completion_provider.as_deref(), cx);
11512 }
11513 }
11514
11515 pub fn context_menu_last(
11516 &mut self,
11517 _: &ContextMenuLast,
11518 _window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11522 context_menu.select_last(self.completion_provider.as_deref(), cx);
11523 }
11524 }
11525
11526 pub fn move_to_previous_word_start(
11527 &mut self,
11528 _: &MoveToPreviousWordStart,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11533 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11534 s.move_cursors_with(|map, head, _| {
11535 (
11536 movement::previous_word_start(map, head),
11537 SelectionGoal::None,
11538 )
11539 });
11540 })
11541 }
11542
11543 pub fn move_to_previous_subword_start(
11544 &mut self,
11545 _: &MoveToPreviousSubwordStart,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11551 s.move_cursors_with(|map, head, _| {
11552 (
11553 movement::previous_subword_start(map, head),
11554 SelectionGoal::None,
11555 )
11556 });
11557 })
11558 }
11559
11560 pub fn select_to_previous_word_start(
11561 &mut self,
11562 _: &SelectToPreviousWordStart,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11568 s.move_heads_with(|map, head, _| {
11569 (
11570 movement::previous_word_start(map, head),
11571 SelectionGoal::None,
11572 )
11573 });
11574 })
11575 }
11576
11577 pub fn select_to_previous_subword_start(
11578 &mut self,
11579 _: &SelectToPreviousSubwordStart,
11580 window: &mut Window,
11581 cx: &mut Context<Self>,
11582 ) {
11583 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11584 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11585 s.move_heads_with(|map, head, _| {
11586 (
11587 movement::previous_subword_start(map, head),
11588 SelectionGoal::None,
11589 )
11590 });
11591 })
11592 }
11593
11594 pub fn delete_to_previous_word_start(
11595 &mut self,
11596 action: &DeleteToPreviousWordStart,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11601 self.transact(window, cx, |this, window, cx| {
11602 this.select_autoclose_pair(window, cx);
11603 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11604 s.move_with(|map, selection| {
11605 if selection.is_empty() {
11606 let cursor = if action.ignore_newlines {
11607 movement::previous_word_start(map, selection.head())
11608 } else {
11609 movement::previous_word_start_or_newline(map, selection.head())
11610 };
11611 selection.set_head(cursor, SelectionGoal::None);
11612 }
11613 });
11614 });
11615 this.insert("", window, cx);
11616 });
11617 }
11618
11619 pub fn delete_to_previous_subword_start(
11620 &mut self,
11621 _: &DeleteToPreviousSubwordStart,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11626 self.transact(window, cx, |this, window, cx| {
11627 this.select_autoclose_pair(window, cx);
11628 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11629 s.move_with(|map, selection| {
11630 if selection.is_empty() {
11631 let cursor = movement::previous_subword_start(map, selection.head());
11632 selection.set_head(cursor, SelectionGoal::None);
11633 }
11634 });
11635 });
11636 this.insert("", window, cx);
11637 });
11638 }
11639
11640 pub fn move_to_next_word_end(
11641 &mut self,
11642 _: &MoveToNextWordEnd,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11647 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11648 s.move_cursors_with(|map, head, _| {
11649 (movement::next_word_end(map, head), SelectionGoal::None)
11650 });
11651 })
11652 }
11653
11654 pub fn move_to_next_subword_end(
11655 &mut self,
11656 _: &MoveToNextSubwordEnd,
11657 window: &mut Window,
11658 cx: &mut Context<Self>,
11659 ) {
11660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11662 s.move_cursors_with(|map, head, _| {
11663 (movement::next_subword_end(map, head), SelectionGoal::None)
11664 });
11665 })
11666 }
11667
11668 pub fn select_to_next_word_end(
11669 &mut self,
11670 _: &SelectToNextWordEnd,
11671 window: &mut Window,
11672 cx: &mut Context<Self>,
11673 ) {
11674 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11675 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11676 s.move_heads_with(|map, head, _| {
11677 (movement::next_word_end(map, head), SelectionGoal::None)
11678 });
11679 })
11680 }
11681
11682 pub fn select_to_next_subword_end(
11683 &mut self,
11684 _: &SelectToNextSubwordEnd,
11685 window: &mut Window,
11686 cx: &mut Context<Self>,
11687 ) {
11688 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11689 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11690 s.move_heads_with(|map, head, _| {
11691 (movement::next_subword_end(map, head), SelectionGoal::None)
11692 });
11693 })
11694 }
11695
11696 pub fn delete_to_next_word_end(
11697 &mut self,
11698 action: &DeleteToNextWordEnd,
11699 window: &mut Window,
11700 cx: &mut Context<Self>,
11701 ) {
11702 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11703 self.transact(window, cx, |this, window, cx| {
11704 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11705 s.move_with(|map, selection| {
11706 if selection.is_empty() {
11707 let cursor = if action.ignore_newlines {
11708 movement::next_word_end(map, selection.head())
11709 } else {
11710 movement::next_word_end_or_newline(map, selection.head())
11711 };
11712 selection.set_head(cursor, SelectionGoal::None);
11713 }
11714 });
11715 });
11716 this.insert("", window, cx);
11717 });
11718 }
11719
11720 pub fn delete_to_next_subword_end(
11721 &mut self,
11722 _: &DeleteToNextSubwordEnd,
11723 window: &mut Window,
11724 cx: &mut Context<Self>,
11725 ) {
11726 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11727 self.transact(window, cx, |this, window, cx| {
11728 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11729 s.move_with(|map, selection| {
11730 if selection.is_empty() {
11731 let cursor = movement::next_subword_end(map, selection.head());
11732 selection.set_head(cursor, SelectionGoal::None);
11733 }
11734 });
11735 });
11736 this.insert("", window, cx);
11737 });
11738 }
11739
11740 pub fn move_to_beginning_of_line(
11741 &mut self,
11742 action: &MoveToBeginningOfLine,
11743 window: &mut Window,
11744 cx: &mut Context<Self>,
11745 ) {
11746 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11747 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11748 s.move_cursors_with(|map, head, _| {
11749 (
11750 movement::indented_line_beginning(
11751 map,
11752 head,
11753 action.stop_at_soft_wraps,
11754 action.stop_at_indent,
11755 ),
11756 SelectionGoal::None,
11757 )
11758 });
11759 })
11760 }
11761
11762 pub fn select_to_beginning_of_line(
11763 &mut self,
11764 action: &SelectToBeginningOfLine,
11765 window: &mut Window,
11766 cx: &mut Context<Self>,
11767 ) {
11768 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11770 s.move_heads_with(|map, head, _| {
11771 (
11772 movement::indented_line_beginning(
11773 map,
11774 head,
11775 action.stop_at_soft_wraps,
11776 action.stop_at_indent,
11777 ),
11778 SelectionGoal::None,
11779 )
11780 });
11781 });
11782 }
11783
11784 pub fn delete_to_beginning_of_line(
11785 &mut self,
11786 action: &DeleteToBeginningOfLine,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 ) {
11790 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11791 self.transact(window, cx, |this, window, cx| {
11792 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11793 s.move_with(|_, selection| {
11794 selection.reversed = true;
11795 });
11796 });
11797
11798 this.select_to_beginning_of_line(
11799 &SelectToBeginningOfLine {
11800 stop_at_soft_wraps: false,
11801 stop_at_indent: action.stop_at_indent,
11802 },
11803 window,
11804 cx,
11805 );
11806 this.backspace(&Backspace, window, cx);
11807 });
11808 }
11809
11810 pub fn move_to_end_of_line(
11811 &mut self,
11812 action: &MoveToEndOfLine,
11813 window: &mut Window,
11814 cx: &mut Context<Self>,
11815 ) {
11816 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11817 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11818 s.move_cursors_with(|map, head, _| {
11819 (
11820 movement::line_end(map, head, action.stop_at_soft_wraps),
11821 SelectionGoal::None,
11822 )
11823 });
11824 })
11825 }
11826
11827 pub fn select_to_end_of_line(
11828 &mut self,
11829 action: &SelectToEndOfLine,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11834 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11835 s.move_heads_with(|map, head, _| {
11836 (
11837 movement::line_end(map, head, action.stop_at_soft_wraps),
11838 SelectionGoal::None,
11839 )
11840 });
11841 })
11842 }
11843
11844 pub fn delete_to_end_of_line(
11845 &mut self,
11846 _: &DeleteToEndOfLine,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11851 self.transact(window, cx, |this, window, cx| {
11852 this.select_to_end_of_line(
11853 &SelectToEndOfLine {
11854 stop_at_soft_wraps: false,
11855 },
11856 window,
11857 cx,
11858 );
11859 this.delete(&Delete, window, cx);
11860 });
11861 }
11862
11863 pub fn cut_to_end_of_line(
11864 &mut self,
11865 _: &CutToEndOfLine,
11866 window: &mut Window,
11867 cx: &mut Context<Self>,
11868 ) {
11869 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11870 self.transact(window, cx, |this, window, cx| {
11871 this.select_to_end_of_line(
11872 &SelectToEndOfLine {
11873 stop_at_soft_wraps: false,
11874 },
11875 window,
11876 cx,
11877 );
11878 this.cut(&Cut, window, cx);
11879 });
11880 }
11881
11882 pub fn move_to_start_of_paragraph(
11883 &mut self,
11884 _: &MoveToStartOfParagraph,
11885 window: &mut Window,
11886 cx: &mut Context<Self>,
11887 ) {
11888 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11889 cx.propagate();
11890 return;
11891 }
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11894 s.move_with(|map, selection| {
11895 selection.collapse_to(
11896 movement::start_of_paragraph(map, selection.head(), 1),
11897 SelectionGoal::None,
11898 )
11899 });
11900 })
11901 }
11902
11903 pub fn move_to_end_of_paragraph(
11904 &mut self,
11905 _: &MoveToEndOfParagraph,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11910 cx.propagate();
11911 return;
11912 }
11913 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11915 s.move_with(|map, selection| {
11916 selection.collapse_to(
11917 movement::end_of_paragraph(map, selection.head(), 1),
11918 SelectionGoal::None,
11919 )
11920 });
11921 })
11922 }
11923
11924 pub fn select_to_start_of_paragraph(
11925 &mut self,
11926 _: &SelectToStartOfParagraph,
11927 window: &mut Window,
11928 cx: &mut Context<Self>,
11929 ) {
11930 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11931 cx.propagate();
11932 return;
11933 }
11934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11936 s.move_heads_with(|map, head, _| {
11937 (
11938 movement::start_of_paragraph(map, head, 1),
11939 SelectionGoal::None,
11940 )
11941 });
11942 })
11943 }
11944
11945 pub fn select_to_end_of_paragraph(
11946 &mut self,
11947 _: &SelectToEndOfParagraph,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11952 cx.propagate();
11953 return;
11954 }
11955 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11957 s.move_heads_with(|map, head, _| {
11958 (
11959 movement::end_of_paragraph(map, head, 1),
11960 SelectionGoal::None,
11961 )
11962 });
11963 })
11964 }
11965
11966 pub fn move_to_start_of_excerpt(
11967 &mut self,
11968 _: &MoveToStartOfExcerpt,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11973 cx.propagate();
11974 return;
11975 }
11976 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11978 s.move_with(|map, selection| {
11979 selection.collapse_to(
11980 movement::start_of_excerpt(
11981 map,
11982 selection.head(),
11983 workspace::searchable::Direction::Prev,
11984 ),
11985 SelectionGoal::None,
11986 )
11987 });
11988 })
11989 }
11990
11991 pub fn move_to_start_of_next_excerpt(
11992 &mut self,
11993 _: &MoveToStartOfNextExcerpt,
11994 window: &mut Window,
11995 cx: &mut Context<Self>,
11996 ) {
11997 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11998 cx.propagate();
11999 return;
12000 }
12001
12002 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12003 s.move_with(|map, selection| {
12004 selection.collapse_to(
12005 movement::start_of_excerpt(
12006 map,
12007 selection.head(),
12008 workspace::searchable::Direction::Next,
12009 ),
12010 SelectionGoal::None,
12011 )
12012 });
12013 })
12014 }
12015
12016 pub fn move_to_end_of_excerpt(
12017 &mut self,
12018 _: &MoveToEndOfExcerpt,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12023 cx.propagate();
12024 return;
12025 }
12026 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12027 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12028 s.move_with(|map, selection| {
12029 selection.collapse_to(
12030 movement::end_of_excerpt(
12031 map,
12032 selection.head(),
12033 workspace::searchable::Direction::Next,
12034 ),
12035 SelectionGoal::None,
12036 )
12037 });
12038 })
12039 }
12040
12041 pub fn move_to_end_of_previous_excerpt(
12042 &mut self,
12043 _: &MoveToEndOfPreviousExcerpt,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12048 cx.propagate();
12049 return;
12050 }
12051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12052 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12053 s.move_with(|map, selection| {
12054 selection.collapse_to(
12055 movement::end_of_excerpt(
12056 map,
12057 selection.head(),
12058 workspace::searchable::Direction::Prev,
12059 ),
12060 SelectionGoal::None,
12061 )
12062 });
12063 })
12064 }
12065
12066 pub fn select_to_start_of_excerpt(
12067 &mut self,
12068 _: &SelectToStartOfExcerpt,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12073 cx.propagate();
12074 return;
12075 }
12076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12078 s.move_heads_with(|map, head, _| {
12079 (
12080 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12081 SelectionGoal::None,
12082 )
12083 });
12084 })
12085 }
12086
12087 pub fn select_to_start_of_next_excerpt(
12088 &mut self,
12089 _: &SelectToStartOfNextExcerpt,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12094 cx.propagate();
12095 return;
12096 }
12097 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12099 s.move_heads_with(|map, head, _| {
12100 (
12101 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12102 SelectionGoal::None,
12103 )
12104 });
12105 })
12106 }
12107
12108 pub fn select_to_end_of_excerpt(
12109 &mut self,
12110 _: &SelectToEndOfExcerpt,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12115 cx.propagate();
12116 return;
12117 }
12118 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12119 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12120 s.move_heads_with(|map, head, _| {
12121 (
12122 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12123 SelectionGoal::None,
12124 )
12125 });
12126 })
12127 }
12128
12129 pub fn select_to_end_of_previous_excerpt(
12130 &mut self,
12131 _: &SelectToEndOfPreviousExcerpt,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12136 cx.propagate();
12137 return;
12138 }
12139 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_heads_with(|map, head, _| {
12142 (
12143 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12144 SelectionGoal::None,
12145 )
12146 });
12147 })
12148 }
12149
12150 pub fn move_to_beginning(
12151 &mut self,
12152 _: &MoveToBeginning,
12153 window: &mut Window,
12154 cx: &mut Context<Self>,
12155 ) {
12156 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12157 cx.propagate();
12158 return;
12159 }
12160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12162 s.select_ranges(vec![0..0]);
12163 });
12164 }
12165
12166 pub fn select_to_beginning(
12167 &mut self,
12168 _: &SelectToBeginning,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 let mut selection = self.selections.last::<Point>(cx);
12173 selection.set_head(Point::zero(), SelectionGoal::None);
12174 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12175 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12176 s.select(vec![selection]);
12177 });
12178 }
12179
12180 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12181 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12182 cx.propagate();
12183 return;
12184 }
12185 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12186 let cursor = self.buffer.read(cx).read(cx).len();
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.select_ranges(vec![cursor..cursor])
12189 });
12190 }
12191
12192 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12193 self.nav_history = nav_history;
12194 }
12195
12196 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12197 self.nav_history.as_ref()
12198 }
12199
12200 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12201 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12202 }
12203
12204 fn push_to_nav_history(
12205 &mut self,
12206 cursor_anchor: Anchor,
12207 new_position: Option<Point>,
12208 is_deactivate: bool,
12209 cx: &mut Context<Self>,
12210 ) {
12211 if let Some(nav_history) = self.nav_history.as_mut() {
12212 let buffer = self.buffer.read(cx).read(cx);
12213 let cursor_position = cursor_anchor.to_point(&buffer);
12214 let scroll_state = self.scroll_manager.anchor();
12215 let scroll_top_row = scroll_state.top_row(&buffer);
12216 drop(buffer);
12217
12218 if let Some(new_position) = new_position {
12219 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12220 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12221 return;
12222 }
12223 }
12224
12225 nav_history.push(
12226 Some(NavigationData {
12227 cursor_anchor,
12228 cursor_position,
12229 scroll_anchor: scroll_state,
12230 scroll_top_row,
12231 }),
12232 cx,
12233 );
12234 cx.emit(EditorEvent::PushedToNavHistory {
12235 anchor: cursor_anchor,
12236 is_deactivate,
12237 })
12238 }
12239 }
12240
12241 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12242 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12243 let buffer = self.buffer.read(cx).snapshot(cx);
12244 let mut selection = self.selections.first::<usize>(cx);
12245 selection.set_head(buffer.len(), SelectionGoal::None);
12246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12247 s.select(vec![selection]);
12248 });
12249 }
12250
12251 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12252 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12253 let end = self.buffer.read(cx).read(cx).len();
12254 self.change_selections(None, window, cx, |s| {
12255 s.select_ranges(vec![0..end]);
12256 });
12257 }
12258
12259 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12262 let mut selections = self.selections.all::<Point>(cx);
12263 let max_point = display_map.buffer_snapshot.max_point();
12264 for selection in &mut selections {
12265 let rows = selection.spanned_rows(true, &display_map);
12266 selection.start = Point::new(rows.start.0, 0);
12267 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12268 selection.reversed = false;
12269 }
12270 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12271 s.select(selections);
12272 });
12273 }
12274
12275 pub fn split_selection_into_lines(
12276 &mut self,
12277 _: &SplitSelectionIntoLines,
12278 window: &mut Window,
12279 cx: &mut Context<Self>,
12280 ) {
12281 let selections = self
12282 .selections
12283 .all::<Point>(cx)
12284 .into_iter()
12285 .map(|selection| selection.start..selection.end)
12286 .collect::<Vec<_>>();
12287 self.unfold_ranges(&selections, true, true, cx);
12288
12289 let mut new_selection_ranges = Vec::new();
12290 {
12291 let buffer = self.buffer.read(cx).read(cx);
12292 for selection in selections {
12293 for row in selection.start.row..selection.end.row {
12294 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12295 new_selection_ranges.push(cursor..cursor);
12296 }
12297
12298 let is_multiline_selection = selection.start.row != selection.end.row;
12299 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12300 // so this action feels more ergonomic when paired with other selection operations
12301 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12302 if !should_skip_last {
12303 new_selection_ranges.push(selection.end..selection.end);
12304 }
12305 }
12306 }
12307 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12308 s.select_ranges(new_selection_ranges);
12309 });
12310 }
12311
12312 pub fn add_selection_above(
12313 &mut self,
12314 _: &AddSelectionAbove,
12315 window: &mut Window,
12316 cx: &mut Context<Self>,
12317 ) {
12318 self.add_selection(true, window, cx);
12319 }
12320
12321 pub fn add_selection_below(
12322 &mut self,
12323 _: &AddSelectionBelow,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.add_selection(false, window, cx);
12328 }
12329
12330 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12331 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12332
12333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12334 let mut selections = self.selections.all::<Point>(cx);
12335 let text_layout_details = self.text_layout_details(window);
12336 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12337 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12338 let range = oldest_selection.display_range(&display_map).sorted();
12339
12340 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12341 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12342 let positions = start_x.min(end_x)..start_x.max(end_x);
12343
12344 selections.clear();
12345 let mut stack = Vec::new();
12346 for row in range.start.row().0..=range.end.row().0 {
12347 if let Some(selection) = self.selections.build_columnar_selection(
12348 &display_map,
12349 DisplayRow(row),
12350 &positions,
12351 oldest_selection.reversed,
12352 &text_layout_details,
12353 ) {
12354 stack.push(selection.id);
12355 selections.push(selection);
12356 }
12357 }
12358
12359 if above {
12360 stack.reverse();
12361 }
12362
12363 AddSelectionsState { above, stack }
12364 });
12365
12366 let last_added_selection = *state.stack.last().unwrap();
12367 let mut new_selections = Vec::new();
12368 if above == state.above {
12369 let end_row = if above {
12370 DisplayRow(0)
12371 } else {
12372 display_map.max_point().row()
12373 };
12374
12375 'outer: for selection in selections {
12376 if selection.id == last_added_selection {
12377 let range = selection.display_range(&display_map).sorted();
12378 debug_assert_eq!(range.start.row(), range.end.row());
12379 let mut row = range.start.row();
12380 let positions =
12381 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12382 px(start)..px(end)
12383 } else {
12384 let start_x =
12385 display_map.x_for_display_point(range.start, &text_layout_details);
12386 let end_x =
12387 display_map.x_for_display_point(range.end, &text_layout_details);
12388 start_x.min(end_x)..start_x.max(end_x)
12389 };
12390
12391 while row != end_row {
12392 if above {
12393 row.0 -= 1;
12394 } else {
12395 row.0 += 1;
12396 }
12397
12398 if let Some(new_selection) = self.selections.build_columnar_selection(
12399 &display_map,
12400 row,
12401 &positions,
12402 selection.reversed,
12403 &text_layout_details,
12404 ) {
12405 state.stack.push(new_selection.id);
12406 if above {
12407 new_selections.push(new_selection);
12408 new_selections.push(selection);
12409 } else {
12410 new_selections.push(selection);
12411 new_selections.push(new_selection);
12412 }
12413
12414 continue 'outer;
12415 }
12416 }
12417 }
12418
12419 new_selections.push(selection);
12420 }
12421 } else {
12422 new_selections = selections;
12423 new_selections.retain(|s| s.id != last_added_selection);
12424 state.stack.pop();
12425 }
12426
12427 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12428 s.select(new_selections);
12429 });
12430 if state.stack.len() > 1 {
12431 self.add_selections_state = Some(state);
12432 }
12433 }
12434
12435 fn select_match_ranges(
12436 &mut self,
12437 range: Range<usize>,
12438 reversed: bool,
12439 replace_newest: bool,
12440 auto_scroll: Option<Autoscroll>,
12441 window: &mut Window,
12442 cx: &mut Context<Editor>,
12443 ) {
12444 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12445 self.change_selections(auto_scroll, window, cx, |s| {
12446 if replace_newest {
12447 s.delete(s.newest_anchor().id);
12448 }
12449 if reversed {
12450 s.insert_range(range.end..range.start);
12451 } else {
12452 s.insert_range(range);
12453 }
12454 });
12455 }
12456
12457 pub fn select_next_match_internal(
12458 &mut self,
12459 display_map: &DisplaySnapshot,
12460 replace_newest: bool,
12461 autoscroll: Option<Autoscroll>,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) -> Result<()> {
12465 let buffer = &display_map.buffer_snapshot;
12466 let mut selections = self.selections.all::<usize>(cx);
12467 if let Some(mut select_next_state) = self.select_next_state.take() {
12468 let query = &select_next_state.query;
12469 if !select_next_state.done {
12470 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12471 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12472 let mut next_selected_range = None;
12473
12474 let bytes_after_last_selection =
12475 buffer.bytes_in_range(last_selection.end..buffer.len());
12476 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12477 let query_matches = query
12478 .stream_find_iter(bytes_after_last_selection)
12479 .map(|result| (last_selection.end, result))
12480 .chain(
12481 query
12482 .stream_find_iter(bytes_before_first_selection)
12483 .map(|result| (0, result)),
12484 );
12485
12486 for (start_offset, query_match) in query_matches {
12487 let query_match = query_match.unwrap(); // can only fail due to I/O
12488 let offset_range =
12489 start_offset + query_match.start()..start_offset + query_match.end();
12490 let display_range = offset_range.start.to_display_point(display_map)
12491 ..offset_range.end.to_display_point(display_map);
12492
12493 if !select_next_state.wordwise
12494 || (!movement::is_inside_word(display_map, display_range.start)
12495 && !movement::is_inside_word(display_map, display_range.end))
12496 {
12497 // TODO: This is n^2, because we might check all the selections
12498 if !selections
12499 .iter()
12500 .any(|selection| selection.range().overlaps(&offset_range))
12501 {
12502 next_selected_range = Some(offset_range);
12503 break;
12504 }
12505 }
12506 }
12507
12508 if let Some(next_selected_range) = next_selected_range {
12509 self.select_match_ranges(
12510 next_selected_range,
12511 last_selection.reversed,
12512 replace_newest,
12513 autoscroll,
12514 window,
12515 cx,
12516 );
12517 } else {
12518 select_next_state.done = true;
12519 }
12520 }
12521
12522 self.select_next_state = Some(select_next_state);
12523 } else {
12524 let mut only_carets = true;
12525 let mut same_text_selected = true;
12526 let mut selected_text = None;
12527
12528 let mut selections_iter = selections.iter().peekable();
12529 while let Some(selection) = selections_iter.next() {
12530 if selection.start != selection.end {
12531 only_carets = false;
12532 }
12533
12534 if same_text_selected {
12535 if selected_text.is_none() {
12536 selected_text =
12537 Some(buffer.text_for_range(selection.range()).collect::<String>());
12538 }
12539
12540 if let Some(next_selection) = selections_iter.peek() {
12541 if next_selection.range().len() == selection.range().len() {
12542 let next_selected_text = buffer
12543 .text_for_range(next_selection.range())
12544 .collect::<String>();
12545 if Some(next_selected_text) != selected_text {
12546 same_text_selected = false;
12547 selected_text = None;
12548 }
12549 } else {
12550 same_text_selected = false;
12551 selected_text = None;
12552 }
12553 }
12554 }
12555 }
12556
12557 if only_carets {
12558 for selection in &mut selections {
12559 let word_range = movement::surrounding_word(
12560 display_map,
12561 selection.start.to_display_point(display_map),
12562 );
12563 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12564 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12565 selection.goal = SelectionGoal::None;
12566 selection.reversed = false;
12567 self.select_match_ranges(
12568 selection.start..selection.end,
12569 selection.reversed,
12570 replace_newest,
12571 autoscroll,
12572 window,
12573 cx,
12574 );
12575 }
12576
12577 if selections.len() == 1 {
12578 let selection = selections
12579 .last()
12580 .expect("ensured that there's only one selection");
12581 let query = buffer
12582 .text_for_range(selection.start..selection.end)
12583 .collect::<String>();
12584 let is_empty = query.is_empty();
12585 let select_state = SelectNextState {
12586 query: AhoCorasick::new(&[query])?,
12587 wordwise: true,
12588 done: is_empty,
12589 };
12590 self.select_next_state = Some(select_state);
12591 } else {
12592 self.select_next_state = None;
12593 }
12594 } else if let Some(selected_text) = selected_text {
12595 self.select_next_state = Some(SelectNextState {
12596 query: AhoCorasick::new(&[selected_text])?,
12597 wordwise: false,
12598 done: false,
12599 });
12600 self.select_next_match_internal(
12601 display_map,
12602 replace_newest,
12603 autoscroll,
12604 window,
12605 cx,
12606 )?;
12607 }
12608 }
12609 Ok(())
12610 }
12611
12612 pub fn select_all_matches(
12613 &mut self,
12614 _action: &SelectAllMatches,
12615 window: &mut Window,
12616 cx: &mut Context<Self>,
12617 ) -> Result<()> {
12618 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12619
12620 self.push_to_selection_history();
12621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12622
12623 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12624 let Some(select_next_state) = self.select_next_state.as_mut() else {
12625 return Ok(());
12626 };
12627 if select_next_state.done {
12628 return Ok(());
12629 }
12630
12631 let mut new_selections = Vec::new();
12632
12633 let reversed = self.selections.oldest::<usize>(cx).reversed;
12634 let buffer = &display_map.buffer_snapshot;
12635 let query_matches = select_next_state
12636 .query
12637 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12638
12639 for query_match in query_matches.into_iter() {
12640 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12641 let offset_range = if reversed {
12642 query_match.end()..query_match.start()
12643 } else {
12644 query_match.start()..query_match.end()
12645 };
12646 let display_range = offset_range.start.to_display_point(&display_map)
12647 ..offset_range.end.to_display_point(&display_map);
12648
12649 if !select_next_state.wordwise
12650 || (!movement::is_inside_word(&display_map, display_range.start)
12651 && !movement::is_inside_word(&display_map, display_range.end))
12652 {
12653 new_selections.push(offset_range.start..offset_range.end);
12654 }
12655 }
12656
12657 select_next_state.done = true;
12658 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12659 self.change_selections(None, window, cx, |selections| {
12660 selections.select_ranges(new_selections)
12661 });
12662
12663 Ok(())
12664 }
12665
12666 pub fn select_next(
12667 &mut self,
12668 action: &SelectNext,
12669 window: &mut Window,
12670 cx: &mut Context<Self>,
12671 ) -> Result<()> {
12672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12673 self.push_to_selection_history();
12674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12675 self.select_next_match_internal(
12676 &display_map,
12677 action.replace_newest,
12678 Some(Autoscroll::newest()),
12679 window,
12680 cx,
12681 )?;
12682 Ok(())
12683 }
12684
12685 pub fn select_previous(
12686 &mut self,
12687 action: &SelectPrevious,
12688 window: &mut Window,
12689 cx: &mut Context<Self>,
12690 ) -> Result<()> {
12691 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12692 self.push_to_selection_history();
12693 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12694 let buffer = &display_map.buffer_snapshot;
12695 let mut selections = self.selections.all::<usize>(cx);
12696 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12697 let query = &select_prev_state.query;
12698 if !select_prev_state.done {
12699 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12700 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12701 let mut next_selected_range = None;
12702 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12703 let bytes_before_last_selection =
12704 buffer.reversed_bytes_in_range(0..last_selection.start);
12705 let bytes_after_first_selection =
12706 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12707 let query_matches = query
12708 .stream_find_iter(bytes_before_last_selection)
12709 .map(|result| (last_selection.start, result))
12710 .chain(
12711 query
12712 .stream_find_iter(bytes_after_first_selection)
12713 .map(|result| (buffer.len(), result)),
12714 );
12715 for (end_offset, query_match) in query_matches {
12716 let query_match = query_match.unwrap(); // can only fail due to I/O
12717 let offset_range =
12718 end_offset - query_match.end()..end_offset - query_match.start();
12719 let display_range = offset_range.start.to_display_point(&display_map)
12720 ..offset_range.end.to_display_point(&display_map);
12721
12722 if !select_prev_state.wordwise
12723 || (!movement::is_inside_word(&display_map, display_range.start)
12724 && !movement::is_inside_word(&display_map, display_range.end))
12725 {
12726 next_selected_range = Some(offset_range);
12727 break;
12728 }
12729 }
12730
12731 if let Some(next_selected_range) = next_selected_range {
12732 self.select_match_ranges(
12733 next_selected_range,
12734 last_selection.reversed,
12735 action.replace_newest,
12736 Some(Autoscroll::newest()),
12737 window,
12738 cx,
12739 );
12740 } else {
12741 select_prev_state.done = true;
12742 }
12743 }
12744
12745 self.select_prev_state = Some(select_prev_state);
12746 } else {
12747 let mut only_carets = true;
12748 let mut same_text_selected = true;
12749 let mut selected_text = None;
12750
12751 let mut selections_iter = selections.iter().peekable();
12752 while let Some(selection) = selections_iter.next() {
12753 if selection.start != selection.end {
12754 only_carets = false;
12755 }
12756
12757 if same_text_selected {
12758 if selected_text.is_none() {
12759 selected_text =
12760 Some(buffer.text_for_range(selection.range()).collect::<String>());
12761 }
12762
12763 if let Some(next_selection) = selections_iter.peek() {
12764 if next_selection.range().len() == selection.range().len() {
12765 let next_selected_text = buffer
12766 .text_for_range(next_selection.range())
12767 .collect::<String>();
12768 if Some(next_selected_text) != selected_text {
12769 same_text_selected = false;
12770 selected_text = None;
12771 }
12772 } else {
12773 same_text_selected = false;
12774 selected_text = None;
12775 }
12776 }
12777 }
12778 }
12779
12780 if only_carets {
12781 for selection in &mut selections {
12782 let word_range = movement::surrounding_word(
12783 &display_map,
12784 selection.start.to_display_point(&display_map),
12785 );
12786 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12787 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12788 selection.goal = SelectionGoal::None;
12789 selection.reversed = false;
12790 self.select_match_ranges(
12791 selection.start..selection.end,
12792 selection.reversed,
12793 action.replace_newest,
12794 Some(Autoscroll::newest()),
12795 window,
12796 cx,
12797 );
12798 }
12799 if selections.len() == 1 {
12800 let selection = selections
12801 .last()
12802 .expect("ensured that there's only one selection");
12803 let query = buffer
12804 .text_for_range(selection.start..selection.end)
12805 .collect::<String>();
12806 let is_empty = query.is_empty();
12807 let select_state = SelectNextState {
12808 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12809 wordwise: true,
12810 done: is_empty,
12811 };
12812 self.select_prev_state = Some(select_state);
12813 } else {
12814 self.select_prev_state = None;
12815 }
12816 } else if let Some(selected_text) = selected_text {
12817 self.select_prev_state = Some(SelectNextState {
12818 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12819 wordwise: false,
12820 done: false,
12821 });
12822 self.select_previous(action, window, cx)?;
12823 }
12824 }
12825 Ok(())
12826 }
12827
12828 pub fn find_next_match(
12829 &mut self,
12830 _: &FindNextMatch,
12831 window: &mut Window,
12832 cx: &mut Context<Self>,
12833 ) -> Result<()> {
12834 let selections = self.selections.disjoint_anchors();
12835 match selections.first() {
12836 Some(first) if selections.len() >= 2 => {
12837 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12838 s.select_ranges([first.range()]);
12839 });
12840 }
12841 _ => self.select_next(
12842 &SelectNext {
12843 replace_newest: true,
12844 },
12845 window,
12846 cx,
12847 )?,
12848 }
12849 Ok(())
12850 }
12851
12852 pub fn find_previous_match(
12853 &mut self,
12854 _: &FindPreviousMatch,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) -> Result<()> {
12858 let selections = self.selections.disjoint_anchors();
12859 match selections.last() {
12860 Some(last) if selections.len() >= 2 => {
12861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12862 s.select_ranges([last.range()]);
12863 });
12864 }
12865 _ => self.select_previous(
12866 &SelectPrevious {
12867 replace_newest: true,
12868 },
12869 window,
12870 cx,
12871 )?,
12872 }
12873 Ok(())
12874 }
12875
12876 pub fn toggle_comments(
12877 &mut self,
12878 action: &ToggleComments,
12879 window: &mut Window,
12880 cx: &mut Context<Self>,
12881 ) {
12882 if self.read_only(cx) {
12883 return;
12884 }
12885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12886 let text_layout_details = &self.text_layout_details(window);
12887 self.transact(window, cx, |this, window, cx| {
12888 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12889 let mut edits = Vec::new();
12890 let mut selection_edit_ranges = Vec::new();
12891 let mut last_toggled_row = None;
12892 let snapshot = this.buffer.read(cx).read(cx);
12893 let empty_str: Arc<str> = Arc::default();
12894 let mut suffixes_inserted = Vec::new();
12895 let ignore_indent = action.ignore_indent;
12896
12897 fn comment_prefix_range(
12898 snapshot: &MultiBufferSnapshot,
12899 row: MultiBufferRow,
12900 comment_prefix: &str,
12901 comment_prefix_whitespace: &str,
12902 ignore_indent: bool,
12903 ) -> Range<Point> {
12904 let indent_size = if ignore_indent {
12905 0
12906 } else {
12907 snapshot.indent_size_for_line(row).len
12908 };
12909
12910 let start = Point::new(row.0, indent_size);
12911
12912 let mut line_bytes = snapshot
12913 .bytes_in_range(start..snapshot.max_point())
12914 .flatten()
12915 .copied();
12916
12917 // If this line currently begins with the line comment prefix, then record
12918 // the range containing the prefix.
12919 if line_bytes
12920 .by_ref()
12921 .take(comment_prefix.len())
12922 .eq(comment_prefix.bytes())
12923 {
12924 // Include any whitespace that matches the comment prefix.
12925 let matching_whitespace_len = line_bytes
12926 .zip(comment_prefix_whitespace.bytes())
12927 .take_while(|(a, b)| a == b)
12928 .count() as u32;
12929 let end = Point::new(
12930 start.row,
12931 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12932 );
12933 start..end
12934 } else {
12935 start..start
12936 }
12937 }
12938
12939 fn comment_suffix_range(
12940 snapshot: &MultiBufferSnapshot,
12941 row: MultiBufferRow,
12942 comment_suffix: &str,
12943 comment_suffix_has_leading_space: bool,
12944 ) -> Range<Point> {
12945 let end = Point::new(row.0, snapshot.line_len(row));
12946 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12947
12948 let mut line_end_bytes = snapshot
12949 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12950 .flatten()
12951 .copied();
12952
12953 let leading_space_len = if suffix_start_column > 0
12954 && line_end_bytes.next() == Some(b' ')
12955 && comment_suffix_has_leading_space
12956 {
12957 1
12958 } else {
12959 0
12960 };
12961
12962 // If this line currently begins with the line comment prefix, then record
12963 // the range containing the prefix.
12964 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12965 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12966 start..end
12967 } else {
12968 end..end
12969 }
12970 }
12971
12972 // TODO: Handle selections that cross excerpts
12973 for selection in &mut selections {
12974 let start_column = snapshot
12975 .indent_size_for_line(MultiBufferRow(selection.start.row))
12976 .len;
12977 let language = if let Some(language) =
12978 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12979 {
12980 language
12981 } else {
12982 continue;
12983 };
12984
12985 selection_edit_ranges.clear();
12986
12987 // If multiple selections contain a given row, avoid processing that
12988 // row more than once.
12989 let mut start_row = MultiBufferRow(selection.start.row);
12990 if last_toggled_row == Some(start_row) {
12991 start_row = start_row.next_row();
12992 }
12993 let end_row =
12994 if selection.end.row > selection.start.row && selection.end.column == 0 {
12995 MultiBufferRow(selection.end.row - 1)
12996 } else {
12997 MultiBufferRow(selection.end.row)
12998 };
12999 last_toggled_row = Some(end_row);
13000
13001 if start_row > end_row {
13002 continue;
13003 }
13004
13005 // If the language has line comments, toggle those.
13006 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13007
13008 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13009 if ignore_indent {
13010 full_comment_prefixes = full_comment_prefixes
13011 .into_iter()
13012 .map(|s| Arc::from(s.trim_end()))
13013 .collect();
13014 }
13015
13016 if !full_comment_prefixes.is_empty() {
13017 let first_prefix = full_comment_prefixes
13018 .first()
13019 .expect("prefixes is non-empty");
13020 let prefix_trimmed_lengths = full_comment_prefixes
13021 .iter()
13022 .map(|p| p.trim_end_matches(' ').len())
13023 .collect::<SmallVec<[usize; 4]>>();
13024
13025 let mut all_selection_lines_are_comments = true;
13026
13027 for row in start_row.0..=end_row.0 {
13028 let row = MultiBufferRow(row);
13029 if start_row < end_row && snapshot.is_line_blank(row) {
13030 continue;
13031 }
13032
13033 let prefix_range = full_comment_prefixes
13034 .iter()
13035 .zip(prefix_trimmed_lengths.iter().copied())
13036 .map(|(prefix, trimmed_prefix_len)| {
13037 comment_prefix_range(
13038 snapshot.deref(),
13039 row,
13040 &prefix[..trimmed_prefix_len],
13041 &prefix[trimmed_prefix_len..],
13042 ignore_indent,
13043 )
13044 })
13045 .max_by_key(|range| range.end.column - range.start.column)
13046 .expect("prefixes is non-empty");
13047
13048 if prefix_range.is_empty() {
13049 all_selection_lines_are_comments = false;
13050 }
13051
13052 selection_edit_ranges.push(prefix_range);
13053 }
13054
13055 if all_selection_lines_are_comments {
13056 edits.extend(
13057 selection_edit_ranges
13058 .iter()
13059 .cloned()
13060 .map(|range| (range, empty_str.clone())),
13061 );
13062 } else {
13063 let min_column = selection_edit_ranges
13064 .iter()
13065 .map(|range| range.start.column)
13066 .min()
13067 .unwrap_or(0);
13068 edits.extend(selection_edit_ranges.iter().map(|range| {
13069 let position = Point::new(range.start.row, min_column);
13070 (position..position, first_prefix.clone())
13071 }));
13072 }
13073 } else if let Some((full_comment_prefix, comment_suffix)) =
13074 language.block_comment_delimiters()
13075 {
13076 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13077 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13078 let prefix_range = comment_prefix_range(
13079 snapshot.deref(),
13080 start_row,
13081 comment_prefix,
13082 comment_prefix_whitespace,
13083 ignore_indent,
13084 );
13085 let suffix_range = comment_suffix_range(
13086 snapshot.deref(),
13087 end_row,
13088 comment_suffix.trim_start_matches(' '),
13089 comment_suffix.starts_with(' '),
13090 );
13091
13092 if prefix_range.is_empty() || suffix_range.is_empty() {
13093 edits.push((
13094 prefix_range.start..prefix_range.start,
13095 full_comment_prefix.clone(),
13096 ));
13097 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13098 suffixes_inserted.push((end_row, comment_suffix.len()));
13099 } else {
13100 edits.push((prefix_range, empty_str.clone()));
13101 edits.push((suffix_range, empty_str.clone()));
13102 }
13103 } else {
13104 continue;
13105 }
13106 }
13107
13108 drop(snapshot);
13109 this.buffer.update(cx, |buffer, cx| {
13110 buffer.edit(edits, None, cx);
13111 });
13112
13113 // Adjust selections so that they end before any comment suffixes that
13114 // were inserted.
13115 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13116 let mut selections = this.selections.all::<Point>(cx);
13117 let snapshot = this.buffer.read(cx).read(cx);
13118 for selection in &mut selections {
13119 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13120 match row.cmp(&MultiBufferRow(selection.end.row)) {
13121 Ordering::Less => {
13122 suffixes_inserted.next();
13123 continue;
13124 }
13125 Ordering::Greater => break,
13126 Ordering::Equal => {
13127 if selection.end.column == snapshot.line_len(row) {
13128 if selection.is_empty() {
13129 selection.start.column -= suffix_len as u32;
13130 }
13131 selection.end.column -= suffix_len as u32;
13132 }
13133 break;
13134 }
13135 }
13136 }
13137 }
13138
13139 drop(snapshot);
13140 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13141 s.select(selections)
13142 });
13143
13144 let selections = this.selections.all::<Point>(cx);
13145 let selections_on_single_row = selections.windows(2).all(|selections| {
13146 selections[0].start.row == selections[1].start.row
13147 && selections[0].end.row == selections[1].end.row
13148 && selections[0].start.row == selections[0].end.row
13149 });
13150 let selections_selecting = selections
13151 .iter()
13152 .any(|selection| selection.start != selection.end);
13153 let advance_downwards = action.advance_downwards
13154 && selections_on_single_row
13155 && !selections_selecting
13156 && !matches!(this.mode, EditorMode::SingleLine { .. });
13157
13158 if advance_downwards {
13159 let snapshot = this.buffer.read(cx).snapshot(cx);
13160
13161 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13162 s.move_cursors_with(|display_snapshot, display_point, _| {
13163 let mut point = display_point.to_point(display_snapshot);
13164 point.row += 1;
13165 point = snapshot.clip_point(point, Bias::Left);
13166 let display_point = point.to_display_point(display_snapshot);
13167 let goal = SelectionGoal::HorizontalPosition(
13168 display_snapshot
13169 .x_for_display_point(display_point, text_layout_details)
13170 .into(),
13171 );
13172 (display_point, goal)
13173 })
13174 });
13175 }
13176 });
13177 }
13178
13179 pub fn select_enclosing_symbol(
13180 &mut self,
13181 _: &SelectEnclosingSymbol,
13182 window: &mut Window,
13183 cx: &mut Context<Self>,
13184 ) {
13185 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13186
13187 let buffer = self.buffer.read(cx).snapshot(cx);
13188 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13189
13190 fn update_selection(
13191 selection: &Selection<usize>,
13192 buffer_snap: &MultiBufferSnapshot,
13193 ) -> Option<Selection<usize>> {
13194 let cursor = selection.head();
13195 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13196 for symbol in symbols.iter().rev() {
13197 let start = symbol.range.start.to_offset(buffer_snap);
13198 let end = symbol.range.end.to_offset(buffer_snap);
13199 let new_range = start..end;
13200 if start < selection.start || end > selection.end {
13201 return Some(Selection {
13202 id: selection.id,
13203 start: new_range.start,
13204 end: new_range.end,
13205 goal: SelectionGoal::None,
13206 reversed: selection.reversed,
13207 });
13208 }
13209 }
13210 None
13211 }
13212
13213 let mut selected_larger_symbol = false;
13214 let new_selections = old_selections
13215 .iter()
13216 .map(|selection| match update_selection(selection, &buffer) {
13217 Some(new_selection) => {
13218 if new_selection.range() != selection.range() {
13219 selected_larger_symbol = true;
13220 }
13221 new_selection
13222 }
13223 None => selection.clone(),
13224 })
13225 .collect::<Vec<_>>();
13226
13227 if selected_larger_symbol {
13228 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13229 s.select(new_selections);
13230 });
13231 }
13232 }
13233
13234 pub fn select_larger_syntax_node(
13235 &mut self,
13236 _: &SelectLargerSyntaxNode,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 let Some(visible_row_count) = self.visible_row_count() else {
13241 return;
13242 };
13243 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13244 if old_selections.is_empty() {
13245 return;
13246 }
13247
13248 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13249
13250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13251 let buffer = self.buffer.read(cx).snapshot(cx);
13252
13253 let mut selected_larger_node = false;
13254 let mut new_selections = old_selections
13255 .iter()
13256 .map(|selection| {
13257 let old_range = selection.start..selection.end;
13258
13259 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13260 // manually select word at selection
13261 if ["string_content", "inline"].contains(&node.kind()) {
13262 let word_range = {
13263 let display_point = buffer
13264 .offset_to_point(old_range.start)
13265 .to_display_point(&display_map);
13266 let Range { start, end } =
13267 movement::surrounding_word(&display_map, display_point);
13268 start.to_point(&display_map).to_offset(&buffer)
13269 ..end.to_point(&display_map).to_offset(&buffer)
13270 };
13271 // ignore if word is already selected
13272 if !word_range.is_empty() && old_range != word_range {
13273 let last_word_range = {
13274 let display_point = buffer
13275 .offset_to_point(old_range.end)
13276 .to_display_point(&display_map);
13277 let Range { start, end } =
13278 movement::surrounding_word(&display_map, display_point);
13279 start.to_point(&display_map).to_offset(&buffer)
13280 ..end.to_point(&display_map).to_offset(&buffer)
13281 };
13282 // only select word if start and end point belongs to same word
13283 if word_range == last_word_range {
13284 selected_larger_node = true;
13285 return Selection {
13286 id: selection.id,
13287 start: word_range.start,
13288 end: word_range.end,
13289 goal: SelectionGoal::None,
13290 reversed: selection.reversed,
13291 };
13292 }
13293 }
13294 }
13295 }
13296
13297 let mut new_range = old_range.clone();
13298 while let Some((_node, containing_range)) =
13299 buffer.syntax_ancestor(new_range.clone())
13300 {
13301 new_range = match containing_range {
13302 MultiOrSingleBufferOffsetRange::Single(_) => break,
13303 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13304 };
13305 if !display_map.intersects_fold(new_range.start)
13306 && !display_map.intersects_fold(new_range.end)
13307 {
13308 break;
13309 }
13310 }
13311
13312 selected_larger_node |= new_range != old_range;
13313 Selection {
13314 id: selection.id,
13315 start: new_range.start,
13316 end: new_range.end,
13317 goal: SelectionGoal::None,
13318 reversed: selection.reversed,
13319 }
13320 })
13321 .collect::<Vec<_>>();
13322
13323 if !selected_larger_node {
13324 return; // don't put this call in the history
13325 }
13326
13327 // scroll based on transformation done to the last selection created by the user
13328 let (last_old, last_new) = old_selections
13329 .last()
13330 .zip(new_selections.last().cloned())
13331 .expect("old_selections isn't empty");
13332
13333 // revert selection
13334 let is_selection_reversed = {
13335 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13336 new_selections.last_mut().expect("checked above").reversed =
13337 should_newest_selection_be_reversed;
13338 should_newest_selection_be_reversed
13339 };
13340
13341 if selected_larger_node {
13342 self.select_syntax_node_history.disable_clearing = true;
13343 self.change_selections(None, window, cx, |s| {
13344 s.select(new_selections.clone());
13345 });
13346 self.select_syntax_node_history.disable_clearing = false;
13347 }
13348
13349 let start_row = last_new.start.to_display_point(&display_map).row().0;
13350 let end_row = last_new.end.to_display_point(&display_map).row().0;
13351 let selection_height = end_row - start_row + 1;
13352 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13353
13354 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13355 let scroll_behavior = if fits_on_the_screen {
13356 self.request_autoscroll(Autoscroll::fit(), cx);
13357 SelectSyntaxNodeScrollBehavior::FitSelection
13358 } else if is_selection_reversed {
13359 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13360 SelectSyntaxNodeScrollBehavior::CursorTop
13361 } else {
13362 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13363 SelectSyntaxNodeScrollBehavior::CursorBottom
13364 };
13365
13366 self.select_syntax_node_history.push((
13367 old_selections,
13368 scroll_behavior,
13369 is_selection_reversed,
13370 ));
13371 }
13372
13373 pub fn select_smaller_syntax_node(
13374 &mut self,
13375 _: &SelectSmallerSyntaxNode,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) {
13379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13380
13381 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13382 self.select_syntax_node_history.pop()
13383 {
13384 if let Some(selection) = selections.last_mut() {
13385 selection.reversed = is_selection_reversed;
13386 }
13387
13388 self.select_syntax_node_history.disable_clearing = true;
13389 self.change_selections(None, window, cx, |s| {
13390 s.select(selections.to_vec());
13391 });
13392 self.select_syntax_node_history.disable_clearing = false;
13393
13394 match scroll_behavior {
13395 SelectSyntaxNodeScrollBehavior::CursorTop => {
13396 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13397 }
13398 SelectSyntaxNodeScrollBehavior::FitSelection => {
13399 self.request_autoscroll(Autoscroll::fit(), cx);
13400 }
13401 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13402 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13403 }
13404 }
13405 }
13406 }
13407
13408 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13409 if !EditorSettings::get_global(cx).gutter.runnables {
13410 self.clear_tasks();
13411 return Task::ready(());
13412 }
13413 let project = self.project.as_ref().map(Entity::downgrade);
13414 let task_sources = self.lsp_task_sources(cx);
13415 cx.spawn_in(window, async move |editor, cx| {
13416 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13417 let Some(project) = project.and_then(|p| p.upgrade()) else {
13418 return;
13419 };
13420 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13421 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13422 }) else {
13423 return;
13424 };
13425
13426 let hide_runnables = project
13427 .update(cx, |project, cx| {
13428 // Do not display any test indicators in non-dev server remote projects.
13429 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13430 })
13431 .unwrap_or(true);
13432 if hide_runnables {
13433 return;
13434 }
13435 let new_rows =
13436 cx.background_spawn({
13437 let snapshot = display_snapshot.clone();
13438 async move {
13439 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13440 }
13441 })
13442 .await;
13443 let Ok(lsp_tasks) =
13444 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13445 else {
13446 return;
13447 };
13448 let lsp_tasks = lsp_tasks.await;
13449
13450 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13451 lsp_tasks
13452 .into_iter()
13453 .flat_map(|(kind, tasks)| {
13454 tasks.into_iter().filter_map(move |(location, task)| {
13455 Some((kind.clone(), location?, task))
13456 })
13457 })
13458 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13459 let buffer = location.target.buffer;
13460 let buffer_snapshot = buffer.read(cx).snapshot();
13461 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13462 |(excerpt_id, snapshot, _)| {
13463 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13464 display_snapshot
13465 .buffer_snapshot
13466 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13467 } else {
13468 None
13469 }
13470 },
13471 );
13472 if let Some(offset) = offset {
13473 let task_buffer_range =
13474 location.target.range.to_point(&buffer_snapshot);
13475 let context_buffer_range =
13476 task_buffer_range.to_offset(&buffer_snapshot);
13477 let context_range = BufferOffset(context_buffer_range.start)
13478 ..BufferOffset(context_buffer_range.end);
13479
13480 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13481 .or_insert_with(|| RunnableTasks {
13482 templates: Vec::new(),
13483 offset,
13484 column: task_buffer_range.start.column,
13485 extra_variables: HashMap::default(),
13486 context_range,
13487 })
13488 .templates
13489 .push((kind, task.original_task().clone()));
13490 }
13491
13492 acc
13493 })
13494 }) else {
13495 return;
13496 };
13497
13498 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13499 editor
13500 .update(cx, |editor, _| {
13501 editor.clear_tasks();
13502 for (key, mut value) in rows {
13503 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13504 value.templates.extend(lsp_tasks.templates);
13505 }
13506
13507 editor.insert_tasks(key, value);
13508 }
13509 for (key, value) in lsp_tasks_by_rows {
13510 editor.insert_tasks(key, value);
13511 }
13512 })
13513 .ok();
13514 })
13515 }
13516 fn fetch_runnable_ranges(
13517 snapshot: &DisplaySnapshot,
13518 range: Range<Anchor>,
13519 ) -> Vec<language::RunnableRange> {
13520 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13521 }
13522
13523 fn runnable_rows(
13524 project: Entity<Project>,
13525 snapshot: DisplaySnapshot,
13526 runnable_ranges: Vec<RunnableRange>,
13527 mut cx: AsyncWindowContext,
13528 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13529 runnable_ranges
13530 .into_iter()
13531 .filter_map(|mut runnable| {
13532 let tasks = cx
13533 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13534 .ok()?;
13535 if tasks.is_empty() {
13536 return None;
13537 }
13538
13539 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13540
13541 let row = snapshot
13542 .buffer_snapshot
13543 .buffer_line_for_row(MultiBufferRow(point.row))?
13544 .1
13545 .start
13546 .row;
13547
13548 let context_range =
13549 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13550 Some((
13551 (runnable.buffer_id, row),
13552 RunnableTasks {
13553 templates: tasks,
13554 offset: snapshot
13555 .buffer_snapshot
13556 .anchor_before(runnable.run_range.start),
13557 context_range,
13558 column: point.column,
13559 extra_variables: runnable.extra_captures,
13560 },
13561 ))
13562 })
13563 .collect()
13564 }
13565
13566 fn templates_with_tags(
13567 project: &Entity<Project>,
13568 runnable: &mut Runnable,
13569 cx: &mut App,
13570 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13571 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13572 let (worktree_id, file) = project
13573 .buffer_for_id(runnable.buffer, cx)
13574 .and_then(|buffer| buffer.read(cx).file())
13575 .map(|file| (file.worktree_id(cx), file.clone()))
13576 .unzip();
13577
13578 (
13579 project.task_store().read(cx).task_inventory().cloned(),
13580 worktree_id,
13581 file,
13582 )
13583 });
13584
13585 let mut templates_with_tags = mem::take(&mut runnable.tags)
13586 .into_iter()
13587 .flat_map(|RunnableTag(tag)| {
13588 inventory
13589 .as_ref()
13590 .into_iter()
13591 .flat_map(|inventory| {
13592 inventory.read(cx).list_tasks(
13593 file.clone(),
13594 Some(runnable.language.clone()),
13595 worktree_id,
13596 cx,
13597 )
13598 })
13599 .filter(move |(_, template)| {
13600 template.tags.iter().any(|source_tag| source_tag == &tag)
13601 })
13602 })
13603 .sorted_by_key(|(kind, _)| kind.to_owned())
13604 .collect::<Vec<_>>();
13605 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13606 // Strongest source wins; if we have worktree tag binding, prefer that to
13607 // global and language bindings;
13608 // if we have a global binding, prefer that to language binding.
13609 let first_mismatch = templates_with_tags
13610 .iter()
13611 .position(|(tag_source, _)| tag_source != leading_tag_source);
13612 if let Some(index) = first_mismatch {
13613 templates_with_tags.truncate(index);
13614 }
13615 }
13616
13617 templates_with_tags
13618 }
13619
13620 pub fn move_to_enclosing_bracket(
13621 &mut self,
13622 _: &MoveToEnclosingBracket,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13627 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13628 s.move_offsets_with(|snapshot, selection| {
13629 let Some(enclosing_bracket_ranges) =
13630 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13631 else {
13632 return;
13633 };
13634
13635 let mut best_length = usize::MAX;
13636 let mut best_inside = false;
13637 let mut best_in_bracket_range = false;
13638 let mut best_destination = None;
13639 for (open, close) in enclosing_bracket_ranges {
13640 let close = close.to_inclusive();
13641 let length = close.end() - open.start;
13642 let inside = selection.start >= open.end && selection.end <= *close.start();
13643 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13644 || close.contains(&selection.head());
13645
13646 // If best is next to a bracket and current isn't, skip
13647 if !in_bracket_range && best_in_bracket_range {
13648 continue;
13649 }
13650
13651 // Prefer smaller lengths unless best is inside and current isn't
13652 if length > best_length && (best_inside || !inside) {
13653 continue;
13654 }
13655
13656 best_length = length;
13657 best_inside = inside;
13658 best_in_bracket_range = in_bracket_range;
13659 best_destination = Some(
13660 if close.contains(&selection.start) && close.contains(&selection.end) {
13661 if inside { open.end } else { open.start }
13662 } else if inside {
13663 *close.start()
13664 } else {
13665 *close.end()
13666 },
13667 );
13668 }
13669
13670 if let Some(destination) = best_destination {
13671 selection.collapse_to(destination, SelectionGoal::None);
13672 }
13673 })
13674 });
13675 }
13676
13677 pub fn undo_selection(
13678 &mut self,
13679 _: &UndoSelection,
13680 window: &mut Window,
13681 cx: &mut Context<Self>,
13682 ) {
13683 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13684 self.end_selection(window, cx);
13685 self.selection_history.mode = SelectionHistoryMode::Undoing;
13686 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13687 self.change_selections(None, window, cx, |s| {
13688 s.select_anchors(entry.selections.to_vec())
13689 });
13690 self.select_next_state = entry.select_next_state;
13691 self.select_prev_state = entry.select_prev_state;
13692 self.add_selections_state = entry.add_selections_state;
13693 self.request_autoscroll(Autoscroll::newest(), cx);
13694 }
13695 self.selection_history.mode = SelectionHistoryMode::Normal;
13696 }
13697
13698 pub fn redo_selection(
13699 &mut self,
13700 _: &RedoSelection,
13701 window: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13705 self.end_selection(window, cx);
13706 self.selection_history.mode = SelectionHistoryMode::Redoing;
13707 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13708 self.change_selections(None, window, cx, |s| {
13709 s.select_anchors(entry.selections.to_vec())
13710 });
13711 self.select_next_state = entry.select_next_state;
13712 self.select_prev_state = entry.select_prev_state;
13713 self.add_selections_state = entry.add_selections_state;
13714 self.request_autoscroll(Autoscroll::newest(), cx);
13715 }
13716 self.selection_history.mode = SelectionHistoryMode::Normal;
13717 }
13718
13719 pub fn expand_excerpts(
13720 &mut self,
13721 action: &ExpandExcerpts,
13722 _: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) {
13725 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13726 }
13727
13728 pub fn expand_excerpts_down(
13729 &mut self,
13730 action: &ExpandExcerptsDown,
13731 _: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) {
13734 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13735 }
13736
13737 pub fn expand_excerpts_up(
13738 &mut self,
13739 action: &ExpandExcerptsUp,
13740 _: &mut Window,
13741 cx: &mut Context<Self>,
13742 ) {
13743 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13744 }
13745
13746 pub fn expand_excerpts_for_direction(
13747 &mut self,
13748 lines: u32,
13749 direction: ExpandExcerptDirection,
13750
13751 cx: &mut Context<Self>,
13752 ) {
13753 let selections = self.selections.disjoint_anchors();
13754
13755 let lines = if lines == 0 {
13756 EditorSettings::get_global(cx).expand_excerpt_lines
13757 } else {
13758 lines
13759 };
13760
13761 self.buffer.update(cx, |buffer, cx| {
13762 let snapshot = buffer.snapshot(cx);
13763 let mut excerpt_ids = selections
13764 .iter()
13765 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13766 .collect::<Vec<_>>();
13767 excerpt_ids.sort();
13768 excerpt_ids.dedup();
13769 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13770 })
13771 }
13772
13773 pub fn expand_excerpt(
13774 &mut self,
13775 excerpt: ExcerptId,
13776 direction: ExpandExcerptDirection,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 let current_scroll_position = self.scroll_position(cx);
13781 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13782 let mut should_scroll_up = false;
13783
13784 if direction == ExpandExcerptDirection::Down {
13785 let multi_buffer = self.buffer.read(cx);
13786 let snapshot = multi_buffer.snapshot(cx);
13787 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13788 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13789 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13790 let buffer_snapshot = buffer.read(cx).snapshot();
13791 let excerpt_end_row =
13792 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13793 let last_row = buffer_snapshot.max_point().row;
13794 let lines_below = last_row.saturating_sub(excerpt_end_row);
13795 should_scroll_up = lines_below >= lines_to_expand;
13796 }
13797 }
13798 }
13799 }
13800
13801 self.buffer.update(cx, |buffer, cx| {
13802 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13803 });
13804
13805 if should_scroll_up {
13806 let new_scroll_position =
13807 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13808 self.set_scroll_position(new_scroll_position, window, cx);
13809 }
13810 }
13811
13812 pub fn go_to_singleton_buffer_point(
13813 &mut self,
13814 point: Point,
13815 window: &mut Window,
13816 cx: &mut Context<Self>,
13817 ) {
13818 self.go_to_singleton_buffer_range(point..point, window, cx);
13819 }
13820
13821 pub fn go_to_singleton_buffer_range(
13822 &mut self,
13823 range: Range<Point>,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) {
13827 let multibuffer = self.buffer().read(cx);
13828 let Some(buffer) = multibuffer.as_singleton() else {
13829 return;
13830 };
13831 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13832 return;
13833 };
13834 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13835 return;
13836 };
13837 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13838 s.select_anchor_ranges([start..end])
13839 });
13840 }
13841
13842 pub fn go_to_diagnostic(
13843 &mut self,
13844 _: &GoToDiagnostic,
13845 window: &mut Window,
13846 cx: &mut Context<Self>,
13847 ) {
13848 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13849 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13850 }
13851
13852 pub fn go_to_prev_diagnostic(
13853 &mut self,
13854 _: &GoToPreviousDiagnostic,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) {
13858 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13859 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13860 }
13861
13862 pub fn go_to_diagnostic_impl(
13863 &mut self,
13864 direction: Direction,
13865 window: &mut Window,
13866 cx: &mut Context<Self>,
13867 ) {
13868 let buffer = self.buffer.read(cx).snapshot(cx);
13869 let selection = self.selections.newest::<usize>(cx);
13870
13871 let mut active_group_id = None;
13872 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13873 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13874 active_group_id = Some(active_group.group_id);
13875 }
13876 }
13877
13878 fn filtered(
13879 snapshot: EditorSnapshot,
13880 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13881 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13882 diagnostics
13883 .filter(|entry| entry.range.start != entry.range.end)
13884 .filter(|entry| !entry.diagnostic.is_unnecessary)
13885 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13886 }
13887
13888 let snapshot = self.snapshot(window, cx);
13889 let before = filtered(
13890 snapshot.clone(),
13891 buffer
13892 .diagnostics_in_range(0..selection.start)
13893 .filter(|entry| entry.range.start <= selection.start),
13894 );
13895 let after = filtered(
13896 snapshot,
13897 buffer
13898 .diagnostics_in_range(selection.start..buffer.len())
13899 .filter(|entry| entry.range.start >= selection.start),
13900 );
13901
13902 let mut found: Option<DiagnosticEntry<usize>> = None;
13903 if direction == Direction::Prev {
13904 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13905 {
13906 for diagnostic in prev_diagnostics.into_iter().rev() {
13907 if diagnostic.range.start != selection.start
13908 || active_group_id
13909 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13910 {
13911 found = Some(diagnostic);
13912 break 'outer;
13913 }
13914 }
13915 }
13916 } else {
13917 for diagnostic in after.chain(before) {
13918 if diagnostic.range.start != selection.start
13919 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13920 {
13921 found = Some(diagnostic);
13922 break;
13923 }
13924 }
13925 }
13926 let Some(next_diagnostic) = found else {
13927 return;
13928 };
13929
13930 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13931 return;
13932 };
13933 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13934 s.select_ranges(vec![
13935 next_diagnostic.range.start..next_diagnostic.range.start,
13936 ])
13937 });
13938 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13939 self.refresh_inline_completion(false, true, window, cx);
13940 }
13941
13942 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13943 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13944 let snapshot = self.snapshot(window, cx);
13945 let selection = self.selections.newest::<Point>(cx);
13946 self.go_to_hunk_before_or_after_position(
13947 &snapshot,
13948 selection.head(),
13949 Direction::Next,
13950 window,
13951 cx,
13952 );
13953 }
13954
13955 pub fn go_to_hunk_before_or_after_position(
13956 &mut self,
13957 snapshot: &EditorSnapshot,
13958 position: Point,
13959 direction: Direction,
13960 window: &mut Window,
13961 cx: &mut Context<Editor>,
13962 ) {
13963 let row = if direction == Direction::Next {
13964 self.hunk_after_position(snapshot, position)
13965 .map(|hunk| hunk.row_range.start)
13966 } else {
13967 self.hunk_before_position(snapshot, position)
13968 };
13969
13970 if let Some(row) = row {
13971 let destination = Point::new(row.0, 0);
13972 let autoscroll = Autoscroll::center();
13973
13974 self.unfold_ranges(&[destination..destination], false, false, cx);
13975 self.change_selections(Some(autoscroll), window, cx, |s| {
13976 s.select_ranges([destination..destination]);
13977 });
13978 }
13979 }
13980
13981 fn hunk_after_position(
13982 &mut self,
13983 snapshot: &EditorSnapshot,
13984 position: Point,
13985 ) -> Option<MultiBufferDiffHunk> {
13986 snapshot
13987 .buffer_snapshot
13988 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13989 .find(|hunk| hunk.row_range.start.0 > position.row)
13990 .or_else(|| {
13991 snapshot
13992 .buffer_snapshot
13993 .diff_hunks_in_range(Point::zero()..position)
13994 .find(|hunk| hunk.row_range.end.0 < position.row)
13995 })
13996 }
13997
13998 fn go_to_prev_hunk(
13999 &mut self,
14000 _: &GoToPreviousHunk,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14005 let snapshot = self.snapshot(window, cx);
14006 let selection = self.selections.newest::<Point>(cx);
14007 self.go_to_hunk_before_or_after_position(
14008 &snapshot,
14009 selection.head(),
14010 Direction::Prev,
14011 window,
14012 cx,
14013 );
14014 }
14015
14016 fn hunk_before_position(
14017 &mut self,
14018 snapshot: &EditorSnapshot,
14019 position: Point,
14020 ) -> Option<MultiBufferRow> {
14021 snapshot
14022 .buffer_snapshot
14023 .diff_hunk_before(position)
14024 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14025 }
14026
14027 fn go_to_next_change(
14028 &mut self,
14029 _: &GoToNextChange,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if let Some(selections) = self
14034 .change_list
14035 .next_change(1, Direction::Next)
14036 .map(|s| s.to_vec())
14037 {
14038 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14039 let map = s.display_map();
14040 s.select_display_ranges(selections.iter().map(|a| {
14041 let point = a.to_display_point(&map);
14042 point..point
14043 }))
14044 })
14045 }
14046 }
14047
14048 fn go_to_previous_change(
14049 &mut self,
14050 _: &GoToPreviousChange,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 if let Some(selections) = self
14055 .change_list
14056 .next_change(1, Direction::Prev)
14057 .map(|s| s.to_vec())
14058 {
14059 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14060 let map = s.display_map();
14061 s.select_display_ranges(selections.iter().map(|a| {
14062 let point = a.to_display_point(&map);
14063 point..point
14064 }))
14065 })
14066 }
14067 }
14068
14069 fn go_to_line<T: 'static>(
14070 &mut self,
14071 position: Anchor,
14072 highlight_color: Option<Hsla>,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 let snapshot = self.snapshot(window, cx).display_snapshot;
14077 let position = position.to_point(&snapshot.buffer_snapshot);
14078 let start = snapshot
14079 .buffer_snapshot
14080 .clip_point(Point::new(position.row, 0), Bias::Left);
14081 let end = start + Point::new(1, 0);
14082 let start = snapshot.buffer_snapshot.anchor_before(start);
14083 let end = snapshot.buffer_snapshot.anchor_before(end);
14084
14085 self.highlight_rows::<T>(
14086 start..end,
14087 highlight_color
14088 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14089 Default::default(),
14090 cx,
14091 );
14092
14093 if self.buffer.read(cx).is_singleton() {
14094 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14095 }
14096 }
14097
14098 pub fn go_to_definition(
14099 &mut self,
14100 _: &GoToDefinition,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) -> Task<Result<Navigated>> {
14104 let definition =
14105 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14106 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14107 cx.spawn_in(window, async move |editor, cx| {
14108 if definition.await? == Navigated::Yes {
14109 return Ok(Navigated::Yes);
14110 }
14111 match fallback_strategy {
14112 GoToDefinitionFallback::None => Ok(Navigated::No),
14113 GoToDefinitionFallback::FindAllReferences => {
14114 match editor.update_in(cx, |editor, window, cx| {
14115 editor.find_all_references(&FindAllReferences, window, cx)
14116 })? {
14117 Some(references) => references.await,
14118 None => Ok(Navigated::No),
14119 }
14120 }
14121 }
14122 })
14123 }
14124
14125 pub fn go_to_declaration(
14126 &mut self,
14127 _: &GoToDeclaration,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) -> Task<Result<Navigated>> {
14131 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14132 }
14133
14134 pub fn go_to_declaration_split(
14135 &mut self,
14136 _: &GoToDeclaration,
14137 window: &mut Window,
14138 cx: &mut Context<Self>,
14139 ) -> Task<Result<Navigated>> {
14140 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14141 }
14142
14143 pub fn go_to_implementation(
14144 &mut self,
14145 _: &GoToImplementation,
14146 window: &mut Window,
14147 cx: &mut Context<Self>,
14148 ) -> Task<Result<Navigated>> {
14149 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14150 }
14151
14152 pub fn go_to_implementation_split(
14153 &mut self,
14154 _: &GoToImplementationSplit,
14155 window: &mut Window,
14156 cx: &mut Context<Self>,
14157 ) -> Task<Result<Navigated>> {
14158 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14159 }
14160
14161 pub fn go_to_type_definition(
14162 &mut self,
14163 _: &GoToTypeDefinition,
14164 window: &mut Window,
14165 cx: &mut Context<Self>,
14166 ) -> Task<Result<Navigated>> {
14167 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14168 }
14169
14170 pub fn go_to_definition_split(
14171 &mut self,
14172 _: &GoToDefinitionSplit,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) -> Task<Result<Navigated>> {
14176 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14177 }
14178
14179 pub fn go_to_type_definition_split(
14180 &mut self,
14181 _: &GoToTypeDefinitionSplit,
14182 window: &mut Window,
14183 cx: &mut Context<Self>,
14184 ) -> Task<Result<Navigated>> {
14185 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14186 }
14187
14188 fn go_to_definition_of_kind(
14189 &mut self,
14190 kind: GotoDefinitionKind,
14191 split: bool,
14192 window: &mut Window,
14193 cx: &mut Context<Self>,
14194 ) -> Task<Result<Navigated>> {
14195 let Some(provider) = self.semantics_provider.clone() else {
14196 return Task::ready(Ok(Navigated::No));
14197 };
14198 let head = self.selections.newest::<usize>(cx).head();
14199 let buffer = self.buffer.read(cx);
14200 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14201 text_anchor
14202 } else {
14203 return Task::ready(Ok(Navigated::No));
14204 };
14205
14206 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14207 return Task::ready(Ok(Navigated::No));
14208 };
14209
14210 cx.spawn_in(window, async move |editor, cx| {
14211 let definitions = definitions.await?;
14212 let navigated = editor
14213 .update_in(cx, |editor, window, cx| {
14214 editor.navigate_to_hover_links(
14215 Some(kind),
14216 definitions
14217 .into_iter()
14218 .filter(|location| {
14219 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14220 })
14221 .map(HoverLink::Text)
14222 .collect::<Vec<_>>(),
14223 split,
14224 window,
14225 cx,
14226 )
14227 })?
14228 .await?;
14229 anyhow::Ok(navigated)
14230 })
14231 }
14232
14233 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14234 let selection = self.selections.newest_anchor();
14235 let head = selection.head();
14236 let tail = selection.tail();
14237
14238 let Some((buffer, start_position)) =
14239 self.buffer.read(cx).text_anchor_for_position(head, cx)
14240 else {
14241 return;
14242 };
14243
14244 let end_position = if head != tail {
14245 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14246 return;
14247 };
14248 Some(pos)
14249 } else {
14250 None
14251 };
14252
14253 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14254 let url = if let Some(end_pos) = end_position {
14255 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14256 } else {
14257 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14258 };
14259
14260 if let Some(url) = url {
14261 editor.update(cx, |_, cx| {
14262 cx.open_url(&url);
14263 })
14264 } else {
14265 Ok(())
14266 }
14267 });
14268
14269 url_finder.detach();
14270 }
14271
14272 pub fn open_selected_filename(
14273 &mut self,
14274 _: &OpenSelectedFilename,
14275 window: &mut Window,
14276 cx: &mut Context<Self>,
14277 ) {
14278 let Some(workspace) = self.workspace() else {
14279 return;
14280 };
14281
14282 let position = self.selections.newest_anchor().head();
14283
14284 let Some((buffer, buffer_position)) =
14285 self.buffer.read(cx).text_anchor_for_position(position, cx)
14286 else {
14287 return;
14288 };
14289
14290 let project = self.project.clone();
14291
14292 cx.spawn_in(window, async move |_, cx| {
14293 let result = find_file(&buffer, project, buffer_position, cx).await;
14294
14295 if let Some((_, path)) = result {
14296 workspace
14297 .update_in(cx, |workspace, window, cx| {
14298 workspace.open_resolved_path(path, window, cx)
14299 })?
14300 .await?;
14301 }
14302 anyhow::Ok(())
14303 })
14304 .detach();
14305 }
14306
14307 pub(crate) fn navigate_to_hover_links(
14308 &mut self,
14309 kind: Option<GotoDefinitionKind>,
14310 mut definitions: Vec<HoverLink>,
14311 split: bool,
14312 window: &mut Window,
14313 cx: &mut Context<Editor>,
14314 ) -> Task<Result<Navigated>> {
14315 // If there is one definition, just open it directly
14316 if definitions.len() == 1 {
14317 let definition = definitions.pop().unwrap();
14318
14319 enum TargetTaskResult {
14320 Location(Option<Location>),
14321 AlreadyNavigated,
14322 }
14323
14324 let target_task = match definition {
14325 HoverLink::Text(link) => {
14326 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14327 }
14328 HoverLink::InlayHint(lsp_location, server_id) => {
14329 let computation =
14330 self.compute_target_location(lsp_location, server_id, window, cx);
14331 cx.background_spawn(async move {
14332 let location = computation.await?;
14333 Ok(TargetTaskResult::Location(location))
14334 })
14335 }
14336 HoverLink::Url(url) => {
14337 cx.open_url(&url);
14338 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14339 }
14340 HoverLink::File(path) => {
14341 if let Some(workspace) = self.workspace() {
14342 cx.spawn_in(window, async move |_, cx| {
14343 workspace
14344 .update_in(cx, |workspace, window, cx| {
14345 workspace.open_resolved_path(path, window, cx)
14346 })?
14347 .await
14348 .map(|_| TargetTaskResult::AlreadyNavigated)
14349 })
14350 } else {
14351 Task::ready(Ok(TargetTaskResult::Location(None)))
14352 }
14353 }
14354 };
14355 cx.spawn_in(window, async move |editor, cx| {
14356 let target = match target_task.await.context("target resolution task")? {
14357 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14358 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14359 TargetTaskResult::Location(Some(target)) => target,
14360 };
14361
14362 editor.update_in(cx, |editor, window, cx| {
14363 let Some(workspace) = editor.workspace() else {
14364 return Navigated::No;
14365 };
14366 let pane = workspace.read(cx).active_pane().clone();
14367
14368 let range = target.range.to_point(target.buffer.read(cx));
14369 let range = editor.range_for_match(&range);
14370 let range = collapse_multiline_range(range);
14371
14372 if !split
14373 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14374 {
14375 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14376 } else {
14377 window.defer(cx, move |window, cx| {
14378 let target_editor: Entity<Self> =
14379 workspace.update(cx, |workspace, cx| {
14380 let pane = if split {
14381 workspace.adjacent_pane(window, cx)
14382 } else {
14383 workspace.active_pane().clone()
14384 };
14385
14386 workspace.open_project_item(
14387 pane,
14388 target.buffer.clone(),
14389 true,
14390 true,
14391 window,
14392 cx,
14393 )
14394 });
14395 target_editor.update(cx, |target_editor, cx| {
14396 // When selecting a definition in a different buffer, disable the nav history
14397 // to avoid creating a history entry at the previous cursor location.
14398 pane.update(cx, |pane, _| pane.disable_history());
14399 target_editor.go_to_singleton_buffer_range(range, window, cx);
14400 pane.update(cx, |pane, _| pane.enable_history());
14401 });
14402 });
14403 }
14404 Navigated::Yes
14405 })
14406 })
14407 } else if !definitions.is_empty() {
14408 cx.spawn_in(window, async move |editor, cx| {
14409 let (title, location_tasks, workspace) = editor
14410 .update_in(cx, |editor, window, cx| {
14411 let tab_kind = match kind {
14412 Some(GotoDefinitionKind::Implementation) => "Implementations",
14413 _ => "Definitions",
14414 };
14415 let title = definitions
14416 .iter()
14417 .find_map(|definition| match definition {
14418 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14419 let buffer = origin.buffer.read(cx);
14420 format!(
14421 "{} for {}",
14422 tab_kind,
14423 buffer
14424 .text_for_range(origin.range.clone())
14425 .collect::<String>()
14426 )
14427 }),
14428 HoverLink::InlayHint(_, _) => None,
14429 HoverLink::Url(_) => None,
14430 HoverLink::File(_) => None,
14431 })
14432 .unwrap_or(tab_kind.to_string());
14433 let location_tasks = definitions
14434 .into_iter()
14435 .map(|definition| match definition {
14436 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14437 HoverLink::InlayHint(lsp_location, server_id) => editor
14438 .compute_target_location(lsp_location, server_id, window, cx),
14439 HoverLink::Url(_) => Task::ready(Ok(None)),
14440 HoverLink::File(_) => Task::ready(Ok(None)),
14441 })
14442 .collect::<Vec<_>>();
14443 (title, location_tasks, editor.workspace().clone())
14444 })
14445 .context("location tasks preparation")?;
14446
14447 let locations = future::join_all(location_tasks)
14448 .await
14449 .into_iter()
14450 .filter_map(|location| location.transpose())
14451 .collect::<Result<_>>()
14452 .context("location tasks")?;
14453
14454 let Some(workspace) = workspace else {
14455 return Ok(Navigated::No);
14456 };
14457 let opened = workspace
14458 .update_in(cx, |workspace, window, cx| {
14459 Self::open_locations_in_multibuffer(
14460 workspace,
14461 locations,
14462 title,
14463 split,
14464 MultibufferSelectionMode::First,
14465 window,
14466 cx,
14467 )
14468 })
14469 .ok();
14470
14471 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14472 })
14473 } else {
14474 Task::ready(Ok(Navigated::No))
14475 }
14476 }
14477
14478 fn compute_target_location(
14479 &self,
14480 lsp_location: lsp::Location,
14481 server_id: LanguageServerId,
14482 window: &mut Window,
14483 cx: &mut Context<Self>,
14484 ) -> Task<anyhow::Result<Option<Location>>> {
14485 let Some(project) = self.project.clone() else {
14486 return Task::ready(Ok(None));
14487 };
14488
14489 cx.spawn_in(window, async move |editor, cx| {
14490 let location_task = editor.update(cx, |_, cx| {
14491 project.update(cx, |project, cx| {
14492 let language_server_name = project
14493 .language_server_statuses(cx)
14494 .find(|(id, _)| server_id == *id)
14495 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14496 language_server_name.map(|language_server_name| {
14497 project.open_local_buffer_via_lsp(
14498 lsp_location.uri.clone(),
14499 server_id,
14500 language_server_name,
14501 cx,
14502 )
14503 })
14504 })
14505 })?;
14506 let location = match location_task {
14507 Some(task) => Some({
14508 let target_buffer_handle = task.await.context("open local buffer")?;
14509 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14510 let target_start = target_buffer
14511 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14512 let target_end = target_buffer
14513 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14514 target_buffer.anchor_after(target_start)
14515 ..target_buffer.anchor_before(target_end)
14516 })?;
14517 Location {
14518 buffer: target_buffer_handle,
14519 range,
14520 }
14521 }),
14522 None => None,
14523 };
14524 Ok(location)
14525 })
14526 }
14527
14528 pub fn find_all_references(
14529 &mut self,
14530 _: &FindAllReferences,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) -> Option<Task<Result<Navigated>>> {
14534 let selection = self.selections.newest::<usize>(cx);
14535 let multi_buffer = self.buffer.read(cx);
14536 let head = selection.head();
14537
14538 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14539 let head_anchor = multi_buffer_snapshot.anchor_at(
14540 head,
14541 if head < selection.tail() {
14542 Bias::Right
14543 } else {
14544 Bias::Left
14545 },
14546 );
14547
14548 match self
14549 .find_all_references_task_sources
14550 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14551 {
14552 Ok(_) => {
14553 log::info!(
14554 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14555 );
14556 return None;
14557 }
14558 Err(i) => {
14559 self.find_all_references_task_sources.insert(i, head_anchor);
14560 }
14561 }
14562
14563 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14564 let workspace = self.workspace()?;
14565 let project = workspace.read(cx).project().clone();
14566 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14567 Some(cx.spawn_in(window, async move |editor, cx| {
14568 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14569 if let Ok(i) = editor
14570 .find_all_references_task_sources
14571 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14572 {
14573 editor.find_all_references_task_sources.remove(i);
14574 }
14575 });
14576
14577 let locations = references.await?;
14578 if locations.is_empty() {
14579 return anyhow::Ok(Navigated::No);
14580 }
14581
14582 workspace.update_in(cx, |workspace, window, cx| {
14583 let title = locations
14584 .first()
14585 .as_ref()
14586 .map(|location| {
14587 let buffer = location.buffer.read(cx);
14588 format!(
14589 "References to `{}`",
14590 buffer
14591 .text_for_range(location.range.clone())
14592 .collect::<String>()
14593 )
14594 })
14595 .unwrap();
14596 Self::open_locations_in_multibuffer(
14597 workspace,
14598 locations,
14599 title,
14600 false,
14601 MultibufferSelectionMode::First,
14602 window,
14603 cx,
14604 );
14605 Navigated::Yes
14606 })
14607 }))
14608 }
14609
14610 /// Opens a multibuffer with the given project locations in it
14611 pub fn open_locations_in_multibuffer(
14612 workspace: &mut Workspace,
14613 mut locations: Vec<Location>,
14614 title: String,
14615 split: bool,
14616 multibuffer_selection_mode: MultibufferSelectionMode,
14617 window: &mut Window,
14618 cx: &mut Context<Workspace>,
14619 ) {
14620 // If there are multiple definitions, open them in a multibuffer
14621 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14622 let mut locations = locations.into_iter().peekable();
14623 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14624 let capability = workspace.project().read(cx).capability();
14625
14626 let excerpt_buffer = cx.new(|cx| {
14627 let mut multibuffer = MultiBuffer::new(capability);
14628 while let Some(location) = locations.next() {
14629 let buffer = location.buffer.read(cx);
14630 let mut ranges_for_buffer = Vec::new();
14631 let range = location.range.to_point(buffer);
14632 ranges_for_buffer.push(range.clone());
14633
14634 while let Some(next_location) = locations.peek() {
14635 if next_location.buffer == location.buffer {
14636 ranges_for_buffer.push(next_location.range.to_point(buffer));
14637 locations.next();
14638 } else {
14639 break;
14640 }
14641 }
14642
14643 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14644 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14645 PathKey::for_buffer(&location.buffer, cx),
14646 location.buffer.clone(),
14647 ranges_for_buffer,
14648 DEFAULT_MULTIBUFFER_CONTEXT,
14649 cx,
14650 );
14651 ranges.extend(new_ranges)
14652 }
14653
14654 multibuffer.with_title(title)
14655 });
14656
14657 let editor = cx.new(|cx| {
14658 Editor::for_multibuffer(
14659 excerpt_buffer,
14660 Some(workspace.project().clone()),
14661 window,
14662 cx,
14663 )
14664 });
14665 editor.update(cx, |editor, cx| {
14666 match multibuffer_selection_mode {
14667 MultibufferSelectionMode::First => {
14668 if let Some(first_range) = ranges.first() {
14669 editor.change_selections(None, window, cx, |selections| {
14670 selections.clear_disjoint();
14671 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14672 });
14673 }
14674 editor.highlight_background::<Self>(
14675 &ranges,
14676 |theme| theme.editor_highlighted_line_background,
14677 cx,
14678 );
14679 }
14680 MultibufferSelectionMode::All => {
14681 editor.change_selections(None, window, cx, |selections| {
14682 selections.clear_disjoint();
14683 selections.select_anchor_ranges(ranges);
14684 });
14685 }
14686 }
14687 editor.register_buffers_with_language_servers(cx);
14688 });
14689
14690 let item = Box::new(editor);
14691 let item_id = item.item_id();
14692
14693 if split {
14694 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14695 } else {
14696 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14697 let (preview_item_id, preview_item_idx) =
14698 workspace.active_pane().update(cx, |pane, _| {
14699 (pane.preview_item_id(), pane.preview_item_idx())
14700 });
14701
14702 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14703
14704 if let Some(preview_item_id) = preview_item_id {
14705 workspace.active_pane().update(cx, |pane, cx| {
14706 pane.remove_item(preview_item_id, false, false, window, cx);
14707 });
14708 }
14709 } else {
14710 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14711 }
14712 }
14713 workspace.active_pane().update(cx, |pane, cx| {
14714 pane.set_preview_item_id(Some(item_id), cx);
14715 });
14716 }
14717
14718 pub fn rename(
14719 &mut self,
14720 _: &Rename,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) -> Option<Task<Result<()>>> {
14724 use language::ToOffset as _;
14725
14726 let provider = self.semantics_provider.clone()?;
14727 let selection = self.selections.newest_anchor().clone();
14728 let (cursor_buffer, cursor_buffer_position) = self
14729 .buffer
14730 .read(cx)
14731 .text_anchor_for_position(selection.head(), cx)?;
14732 let (tail_buffer, cursor_buffer_position_end) = self
14733 .buffer
14734 .read(cx)
14735 .text_anchor_for_position(selection.tail(), cx)?;
14736 if tail_buffer != cursor_buffer {
14737 return None;
14738 }
14739
14740 let snapshot = cursor_buffer.read(cx).snapshot();
14741 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14742 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14743 let prepare_rename = provider
14744 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14745 .unwrap_or_else(|| Task::ready(Ok(None)));
14746 drop(snapshot);
14747
14748 Some(cx.spawn_in(window, async move |this, cx| {
14749 let rename_range = if let Some(range) = prepare_rename.await? {
14750 Some(range)
14751 } else {
14752 this.update(cx, |this, cx| {
14753 let buffer = this.buffer.read(cx).snapshot(cx);
14754 let mut buffer_highlights = this
14755 .document_highlights_for_position(selection.head(), &buffer)
14756 .filter(|highlight| {
14757 highlight.start.excerpt_id == selection.head().excerpt_id
14758 && highlight.end.excerpt_id == selection.head().excerpt_id
14759 });
14760 buffer_highlights
14761 .next()
14762 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14763 })?
14764 };
14765 if let Some(rename_range) = rename_range {
14766 this.update_in(cx, |this, window, cx| {
14767 let snapshot = cursor_buffer.read(cx).snapshot();
14768 let rename_buffer_range = rename_range.to_offset(&snapshot);
14769 let cursor_offset_in_rename_range =
14770 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14771 let cursor_offset_in_rename_range_end =
14772 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14773
14774 this.take_rename(false, window, cx);
14775 let buffer = this.buffer.read(cx).read(cx);
14776 let cursor_offset = selection.head().to_offset(&buffer);
14777 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14778 let rename_end = rename_start + rename_buffer_range.len();
14779 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14780 let mut old_highlight_id = None;
14781 let old_name: Arc<str> = buffer
14782 .chunks(rename_start..rename_end, true)
14783 .map(|chunk| {
14784 if old_highlight_id.is_none() {
14785 old_highlight_id = chunk.syntax_highlight_id;
14786 }
14787 chunk.text
14788 })
14789 .collect::<String>()
14790 .into();
14791
14792 drop(buffer);
14793
14794 // Position the selection in the rename editor so that it matches the current selection.
14795 this.show_local_selections = false;
14796 let rename_editor = cx.new(|cx| {
14797 let mut editor = Editor::single_line(window, cx);
14798 editor.buffer.update(cx, |buffer, cx| {
14799 buffer.edit([(0..0, old_name.clone())], None, cx)
14800 });
14801 let rename_selection_range = match cursor_offset_in_rename_range
14802 .cmp(&cursor_offset_in_rename_range_end)
14803 {
14804 Ordering::Equal => {
14805 editor.select_all(&SelectAll, window, cx);
14806 return editor;
14807 }
14808 Ordering::Less => {
14809 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14810 }
14811 Ordering::Greater => {
14812 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14813 }
14814 };
14815 if rename_selection_range.end > old_name.len() {
14816 editor.select_all(&SelectAll, window, cx);
14817 } else {
14818 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14819 s.select_ranges([rename_selection_range]);
14820 });
14821 }
14822 editor
14823 });
14824 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14825 if e == &EditorEvent::Focused {
14826 cx.emit(EditorEvent::FocusedIn)
14827 }
14828 })
14829 .detach();
14830
14831 let write_highlights =
14832 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14833 let read_highlights =
14834 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14835 let ranges = write_highlights
14836 .iter()
14837 .flat_map(|(_, ranges)| ranges.iter())
14838 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14839 .cloned()
14840 .collect();
14841
14842 this.highlight_text::<Rename>(
14843 ranges,
14844 HighlightStyle {
14845 fade_out: Some(0.6),
14846 ..Default::default()
14847 },
14848 cx,
14849 );
14850 let rename_focus_handle = rename_editor.focus_handle(cx);
14851 window.focus(&rename_focus_handle);
14852 let block_id = this.insert_blocks(
14853 [BlockProperties {
14854 style: BlockStyle::Flex,
14855 placement: BlockPlacement::Below(range.start),
14856 height: Some(1),
14857 render: Arc::new({
14858 let rename_editor = rename_editor.clone();
14859 move |cx: &mut BlockContext| {
14860 let mut text_style = cx.editor_style.text.clone();
14861 if let Some(highlight_style) = old_highlight_id
14862 .and_then(|h| h.style(&cx.editor_style.syntax))
14863 {
14864 text_style = text_style.highlight(highlight_style);
14865 }
14866 div()
14867 .block_mouse_down()
14868 .pl(cx.anchor_x)
14869 .child(EditorElement::new(
14870 &rename_editor,
14871 EditorStyle {
14872 background: cx.theme().system().transparent,
14873 local_player: cx.editor_style.local_player,
14874 text: text_style,
14875 scrollbar_width: cx.editor_style.scrollbar_width,
14876 syntax: cx.editor_style.syntax.clone(),
14877 status: cx.editor_style.status.clone(),
14878 inlay_hints_style: HighlightStyle {
14879 font_weight: Some(FontWeight::BOLD),
14880 ..make_inlay_hints_style(cx.app)
14881 },
14882 inline_completion_styles: make_suggestion_styles(
14883 cx.app,
14884 ),
14885 ..EditorStyle::default()
14886 },
14887 ))
14888 .into_any_element()
14889 }
14890 }),
14891 priority: 0,
14892 render_in_minimap: true,
14893 }],
14894 Some(Autoscroll::fit()),
14895 cx,
14896 )[0];
14897 this.pending_rename = Some(RenameState {
14898 range,
14899 old_name,
14900 editor: rename_editor,
14901 block_id,
14902 });
14903 })?;
14904 }
14905
14906 Ok(())
14907 }))
14908 }
14909
14910 pub fn confirm_rename(
14911 &mut self,
14912 _: &ConfirmRename,
14913 window: &mut Window,
14914 cx: &mut Context<Self>,
14915 ) -> Option<Task<Result<()>>> {
14916 let rename = self.take_rename(false, window, cx)?;
14917 let workspace = self.workspace()?.downgrade();
14918 let (buffer, start) = self
14919 .buffer
14920 .read(cx)
14921 .text_anchor_for_position(rename.range.start, cx)?;
14922 let (end_buffer, _) = self
14923 .buffer
14924 .read(cx)
14925 .text_anchor_for_position(rename.range.end, cx)?;
14926 if buffer != end_buffer {
14927 return None;
14928 }
14929
14930 let old_name = rename.old_name;
14931 let new_name = rename.editor.read(cx).text(cx);
14932
14933 let rename = self.semantics_provider.as_ref()?.perform_rename(
14934 &buffer,
14935 start,
14936 new_name.clone(),
14937 cx,
14938 )?;
14939
14940 Some(cx.spawn_in(window, async move |editor, cx| {
14941 let project_transaction = rename.await?;
14942 Self::open_project_transaction(
14943 &editor,
14944 workspace,
14945 project_transaction,
14946 format!("Rename: {} → {}", old_name, new_name),
14947 cx,
14948 )
14949 .await?;
14950
14951 editor.update(cx, |editor, cx| {
14952 editor.refresh_document_highlights(cx);
14953 })?;
14954 Ok(())
14955 }))
14956 }
14957
14958 fn take_rename(
14959 &mut self,
14960 moving_cursor: bool,
14961 window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) -> Option<RenameState> {
14964 let rename = self.pending_rename.take()?;
14965 if rename.editor.focus_handle(cx).is_focused(window) {
14966 window.focus(&self.focus_handle);
14967 }
14968
14969 self.remove_blocks(
14970 [rename.block_id].into_iter().collect(),
14971 Some(Autoscroll::fit()),
14972 cx,
14973 );
14974 self.clear_highlights::<Rename>(cx);
14975 self.show_local_selections = true;
14976
14977 if moving_cursor {
14978 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14979 editor.selections.newest::<usize>(cx).head()
14980 });
14981
14982 // Update the selection to match the position of the selection inside
14983 // the rename editor.
14984 let snapshot = self.buffer.read(cx).read(cx);
14985 let rename_range = rename.range.to_offset(&snapshot);
14986 let cursor_in_editor = snapshot
14987 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14988 .min(rename_range.end);
14989 drop(snapshot);
14990
14991 self.change_selections(None, window, cx, |s| {
14992 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14993 });
14994 } else {
14995 self.refresh_document_highlights(cx);
14996 }
14997
14998 Some(rename)
14999 }
15000
15001 pub fn pending_rename(&self) -> Option<&RenameState> {
15002 self.pending_rename.as_ref()
15003 }
15004
15005 fn format(
15006 &mut self,
15007 _: &Format,
15008 window: &mut Window,
15009 cx: &mut Context<Self>,
15010 ) -> Option<Task<Result<()>>> {
15011 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15012
15013 let project = match &self.project {
15014 Some(project) => project.clone(),
15015 None => return None,
15016 };
15017
15018 Some(self.perform_format(
15019 project,
15020 FormatTrigger::Manual,
15021 FormatTarget::Buffers,
15022 window,
15023 cx,
15024 ))
15025 }
15026
15027 fn format_selections(
15028 &mut self,
15029 _: &FormatSelections,
15030 window: &mut Window,
15031 cx: &mut Context<Self>,
15032 ) -> Option<Task<Result<()>>> {
15033 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15034
15035 let project = match &self.project {
15036 Some(project) => project.clone(),
15037 None => return None,
15038 };
15039
15040 let ranges = self
15041 .selections
15042 .all_adjusted(cx)
15043 .into_iter()
15044 .map(|selection| selection.range())
15045 .collect_vec();
15046
15047 Some(self.perform_format(
15048 project,
15049 FormatTrigger::Manual,
15050 FormatTarget::Ranges(ranges),
15051 window,
15052 cx,
15053 ))
15054 }
15055
15056 fn perform_format(
15057 &mut self,
15058 project: Entity<Project>,
15059 trigger: FormatTrigger,
15060 target: FormatTarget,
15061 window: &mut Window,
15062 cx: &mut Context<Self>,
15063 ) -> Task<Result<()>> {
15064 let buffer = self.buffer.clone();
15065 let (buffers, target) = match target {
15066 FormatTarget::Buffers => {
15067 let mut buffers = buffer.read(cx).all_buffers();
15068 if trigger == FormatTrigger::Save {
15069 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15070 }
15071 (buffers, LspFormatTarget::Buffers)
15072 }
15073 FormatTarget::Ranges(selection_ranges) => {
15074 let multi_buffer = buffer.read(cx);
15075 let snapshot = multi_buffer.read(cx);
15076 let mut buffers = HashSet::default();
15077 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15078 BTreeMap::new();
15079 for selection_range in selection_ranges {
15080 for (buffer, buffer_range, _) in
15081 snapshot.range_to_buffer_ranges(selection_range)
15082 {
15083 let buffer_id = buffer.remote_id();
15084 let start = buffer.anchor_before(buffer_range.start);
15085 let end = buffer.anchor_after(buffer_range.end);
15086 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15087 buffer_id_to_ranges
15088 .entry(buffer_id)
15089 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15090 .or_insert_with(|| vec![start..end]);
15091 }
15092 }
15093 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15094 }
15095 };
15096
15097 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15098 let selections_prev = transaction_id_prev
15099 .and_then(|transaction_id_prev| {
15100 // default to selections as they were after the last edit, if we have them,
15101 // instead of how they are now.
15102 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15103 // will take you back to where you made the last edit, instead of staying where you scrolled
15104 self.selection_history
15105 .transaction(transaction_id_prev)
15106 .map(|t| t.0.clone())
15107 })
15108 .unwrap_or_else(|| {
15109 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15110 self.selections.disjoint_anchors()
15111 });
15112
15113 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15114 let format = project.update(cx, |project, cx| {
15115 project.format(buffers, target, true, trigger, cx)
15116 });
15117
15118 cx.spawn_in(window, async move |editor, cx| {
15119 let transaction = futures::select_biased! {
15120 transaction = format.log_err().fuse() => transaction,
15121 () = timeout => {
15122 log::warn!("timed out waiting for formatting");
15123 None
15124 }
15125 };
15126
15127 buffer
15128 .update(cx, |buffer, cx| {
15129 if let Some(transaction) = transaction {
15130 if !buffer.is_singleton() {
15131 buffer.push_transaction(&transaction.0, cx);
15132 }
15133 }
15134 cx.notify();
15135 })
15136 .ok();
15137
15138 if let Some(transaction_id_now) =
15139 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15140 {
15141 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15142 if has_new_transaction {
15143 _ = editor.update(cx, |editor, _| {
15144 editor
15145 .selection_history
15146 .insert_transaction(transaction_id_now, selections_prev);
15147 });
15148 }
15149 }
15150
15151 Ok(())
15152 })
15153 }
15154
15155 fn organize_imports(
15156 &mut self,
15157 _: &OrganizeImports,
15158 window: &mut Window,
15159 cx: &mut Context<Self>,
15160 ) -> Option<Task<Result<()>>> {
15161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15162 let project = match &self.project {
15163 Some(project) => project.clone(),
15164 None => return None,
15165 };
15166 Some(self.perform_code_action_kind(
15167 project,
15168 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15169 window,
15170 cx,
15171 ))
15172 }
15173
15174 fn perform_code_action_kind(
15175 &mut self,
15176 project: Entity<Project>,
15177 kind: CodeActionKind,
15178 window: &mut Window,
15179 cx: &mut Context<Self>,
15180 ) -> Task<Result<()>> {
15181 let buffer = self.buffer.clone();
15182 let buffers = buffer.read(cx).all_buffers();
15183 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15184 let apply_action = project.update(cx, |project, cx| {
15185 project.apply_code_action_kind(buffers, kind, true, cx)
15186 });
15187 cx.spawn_in(window, async move |_, cx| {
15188 let transaction = futures::select_biased! {
15189 () = timeout => {
15190 log::warn!("timed out waiting for executing code action");
15191 None
15192 }
15193 transaction = apply_action.log_err().fuse() => transaction,
15194 };
15195 buffer
15196 .update(cx, |buffer, cx| {
15197 // check if we need this
15198 if let Some(transaction) = transaction {
15199 if !buffer.is_singleton() {
15200 buffer.push_transaction(&transaction.0, cx);
15201 }
15202 }
15203 cx.notify();
15204 })
15205 .ok();
15206 Ok(())
15207 })
15208 }
15209
15210 fn restart_language_server(
15211 &mut self,
15212 _: &RestartLanguageServer,
15213 _: &mut Window,
15214 cx: &mut Context<Self>,
15215 ) {
15216 if let Some(project) = self.project.clone() {
15217 self.buffer.update(cx, |multi_buffer, cx| {
15218 project.update(cx, |project, cx| {
15219 project.restart_language_servers_for_buffers(
15220 multi_buffer.all_buffers().into_iter().collect(),
15221 cx,
15222 );
15223 });
15224 })
15225 }
15226 }
15227
15228 fn stop_language_server(
15229 &mut self,
15230 _: &StopLanguageServer,
15231 _: &mut Window,
15232 cx: &mut Context<Self>,
15233 ) {
15234 if let Some(project) = self.project.clone() {
15235 self.buffer.update(cx, |multi_buffer, cx| {
15236 project.update(cx, |project, cx| {
15237 project.stop_language_servers_for_buffers(
15238 multi_buffer.all_buffers().into_iter().collect(),
15239 cx,
15240 );
15241 cx.emit(project::Event::RefreshInlayHints);
15242 });
15243 });
15244 }
15245 }
15246
15247 fn cancel_language_server_work(
15248 workspace: &mut Workspace,
15249 _: &actions::CancelLanguageServerWork,
15250 _: &mut Window,
15251 cx: &mut Context<Workspace>,
15252 ) {
15253 let project = workspace.project();
15254 let buffers = workspace
15255 .active_item(cx)
15256 .and_then(|item| item.act_as::<Editor>(cx))
15257 .map_or(HashSet::default(), |editor| {
15258 editor.read(cx).buffer.read(cx).all_buffers()
15259 });
15260 project.update(cx, |project, cx| {
15261 project.cancel_language_server_work_for_buffers(buffers, cx);
15262 });
15263 }
15264
15265 fn show_character_palette(
15266 &mut self,
15267 _: &ShowCharacterPalette,
15268 window: &mut Window,
15269 _: &mut Context<Self>,
15270 ) {
15271 window.show_character_palette();
15272 }
15273
15274 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15275 if self.mode.is_minimap() {
15276 return;
15277 }
15278
15279 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15280 let buffer = self.buffer.read(cx).snapshot(cx);
15281 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15282 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15283 let is_valid = buffer
15284 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15285 .any(|entry| {
15286 entry.diagnostic.is_primary
15287 && !entry.range.is_empty()
15288 && entry.range.start == primary_range_start
15289 && entry.diagnostic.message == active_diagnostics.active_message
15290 });
15291
15292 if !is_valid {
15293 self.dismiss_diagnostics(cx);
15294 }
15295 }
15296 }
15297
15298 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15299 match &self.active_diagnostics {
15300 ActiveDiagnostic::Group(group) => Some(group),
15301 _ => None,
15302 }
15303 }
15304
15305 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15306 self.dismiss_diagnostics(cx);
15307 self.active_diagnostics = ActiveDiagnostic::All;
15308 }
15309
15310 fn activate_diagnostics(
15311 &mut self,
15312 buffer_id: BufferId,
15313 diagnostic: DiagnosticEntry<usize>,
15314 window: &mut Window,
15315 cx: &mut Context<Self>,
15316 ) {
15317 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15318 return;
15319 }
15320 self.dismiss_diagnostics(cx);
15321 let snapshot = self.snapshot(window, cx);
15322 let buffer = self.buffer.read(cx).snapshot(cx);
15323 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15324 return;
15325 };
15326
15327 let diagnostic_group = buffer
15328 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15329 .collect::<Vec<_>>();
15330
15331 let blocks =
15332 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15333
15334 let blocks = self.display_map.update(cx, |display_map, cx| {
15335 display_map.insert_blocks(blocks, cx).into_iter().collect()
15336 });
15337 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15338 active_range: buffer.anchor_before(diagnostic.range.start)
15339 ..buffer.anchor_after(diagnostic.range.end),
15340 active_message: diagnostic.diagnostic.message.clone(),
15341 group_id: diagnostic.diagnostic.group_id,
15342 blocks,
15343 });
15344 cx.notify();
15345 }
15346
15347 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15348 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15349 return;
15350 };
15351
15352 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15353 if let ActiveDiagnostic::Group(group) = prev {
15354 self.display_map.update(cx, |display_map, cx| {
15355 display_map.remove_blocks(group.blocks, cx);
15356 });
15357 cx.notify();
15358 }
15359 }
15360
15361 /// Disable inline diagnostics rendering for this editor.
15362 pub fn disable_inline_diagnostics(&mut self) {
15363 self.inline_diagnostics_enabled = false;
15364 self.inline_diagnostics_update = Task::ready(());
15365 self.inline_diagnostics.clear();
15366 }
15367
15368 pub fn diagnostics_enabled(&self) -> bool {
15369 self.mode.is_full()
15370 }
15371
15372 pub fn inline_diagnostics_enabled(&self) -> bool {
15373 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15374 }
15375
15376 pub fn show_inline_diagnostics(&self) -> bool {
15377 self.show_inline_diagnostics
15378 }
15379
15380 pub fn toggle_inline_diagnostics(
15381 &mut self,
15382 _: &ToggleInlineDiagnostics,
15383 window: &mut Window,
15384 cx: &mut Context<Editor>,
15385 ) {
15386 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15387 self.refresh_inline_diagnostics(false, window, cx);
15388 }
15389
15390 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15391 self.diagnostics_max_severity = severity;
15392 self.display_map.update(cx, |display_map, _| {
15393 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15394 });
15395 }
15396
15397 pub fn toggle_diagnostics(
15398 &mut self,
15399 _: &ToggleDiagnostics,
15400 window: &mut Window,
15401 cx: &mut Context<Editor>,
15402 ) {
15403 if !self.diagnostics_enabled() {
15404 return;
15405 }
15406
15407 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15408 EditorSettings::get_global(cx)
15409 .diagnostics_max_severity
15410 .filter(|severity| severity != &DiagnosticSeverity::Off)
15411 .unwrap_or(DiagnosticSeverity::Hint)
15412 } else {
15413 DiagnosticSeverity::Off
15414 };
15415 self.set_max_diagnostics_severity(new_severity, cx);
15416 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15417 self.active_diagnostics = ActiveDiagnostic::None;
15418 self.inline_diagnostics_update = Task::ready(());
15419 self.inline_diagnostics.clear();
15420 } else {
15421 self.refresh_inline_diagnostics(false, window, cx);
15422 }
15423
15424 cx.notify();
15425 }
15426
15427 pub fn toggle_minimap(
15428 &mut self,
15429 _: &ToggleMinimap,
15430 window: &mut Window,
15431 cx: &mut Context<Editor>,
15432 ) {
15433 if self.supports_minimap(cx) {
15434 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15435 }
15436 }
15437
15438 fn refresh_inline_diagnostics(
15439 &mut self,
15440 debounce: bool,
15441 window: &mut Window,
15442 cx: &mut Context<Self>,
15443 ) {
15444 let max_severity = ProjectSettings::get_global(cx)
15445 .diagnostics
15446 .inline
15447 .max_severity
15448 .unwrap_or(self.diagnostics_max_severity);
15449
15450 if self.mode.is_minimap()
15451 || !self.inline_diagnostics_enabled()
15452 || !self.show_inline_diagnostics
15453 || max_severity == DiagnosticSeverity::Off
15454 {
15455 self.inline_diagnostics_update = Task::ready(());
15456 self.inline_diagnostics.clear();
15457 return;
15458 }
15459
15460 let debounce_ms = ProjectSettings::get_global(cx)
15461 .diagnostics
15462 .inline
15463 .update_debounce_ms;
15464 let debounce = if debounce && debounce_ms > 0 {
15465 Some(Duration::from_millis(debounce_ms))
15466 } else {
15467 None
15468 };
15469 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15470 let editor = editor.upgrade().unwrap();
15471
15472 if let Some(debounce) = debounce {
15473 cx.background_executor().timer(debounce).await;
15474 }
15475 let Some(snapshot) = editor
15476 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15477 .ok()
15478 else {
15479 return;
15480 };
15481
15482 let new_inline_diagnostics = cx
15483 .background_spawn(async move {
15484 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15485 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15486 let message = diagnostic_entry
15487 .diagnostic
15488 .message
15489 .split_once('\n')
15490 .map(|(line, _)| line)
15491 .map(SharedString::new)
15492 .unwrap_or_else(|| {
15493 SharedString::from(diagnostic_entry.diagnostic.message)
15494 });
15495 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15496 let (Ok(i) | Err(i)) = inline_diagnostics
15497 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15498 inline_diagnostics.insert(
15499 i,
15500 (
15501 start_anchor,
15502 InlineDiagnostic {
15503 message,
15504 group_id: diagnostic_entry.diagnostic.group_id,
15505 start: diagnostic_entry.range.start.to_point(&snapshot),
15506 is_primary: diagnostic_entry.diagnostic.is_primary,
15507 severity: diagnostic_entry.diagnostic.severity,
15508 },
15509 ),
15510 );
15511 }
15512 inline_diagnostics
15513 })
15514 .await;
15515
15516 editor
15517 .update(cx, |editor, cx| {
15518 editor.inline_diagnostics = new_inline_diagnostics;
15519 cx.notify();
15520 })
15521 .ok();
15522 });
15523 }
15524
15525 pub fn set_selections_from_remote(
15526 &mut self,
15527 selections: Vec<Selection<Anchor>>,
15528 pending_selection: Option<Selection<Anchor>>,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) {
15532 let old_cursor_position = self.selections.newest_anchor().head();
15533 self.selections.change_with(cx, |s| {
15534 s.select_anchors(selections);
15535 if let Some(pending_selection) = pending_selection {
15536 s.set_pending(pending_selection, SelectMode::Character);
15537 } else {
15538 s.clear_pending();
15539 }
15540 });
15541 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15542 }
15543
15544 fn push_to_selection_history(&mut self) {
15545 self.selection_history.push(SelectionHistoryEntry {
15546 selections: self.selections.disjoint_anchors(),
15547 select_next_state: self.select_next_state.clone(),
15548 select_prev_state: self.select_prev_state.clone(),
15549 add_selections_state: self.add_selections_state.clone(),
15550 });
15551 }
15552
15553 pub fn transact(
15554 &mut self,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15558 ) -> Option<TransactionId> {
15559 self.start_transaction_at(Instant::now(), window, cx);
15560 update(self, window, cx);
15561 self.end_transaction_at(Instant::now(), cx)
15562 }
15563
15564 pub fn start_transaction_at(
15565 &mut self,
15566 now: Instant,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 self.end_selection(window, cx);
15571 if let Some(tx_id) = self
15572 .buffer
15573 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15574 {
15575 self.selection_history
15576 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15577 cx.emit(EditorEvent::TransactionBegun {
15578 transaction_id: tx_id,
15579 })
15580 }
15581 }
15582
15583 pub fn end_transaction_at(
15584 &mut self,
15585 now: Instant,
15586 cx: &mut Context<Self>,
15587 ) -> Option<TransactionId> {
15588 if let Some(transaction_id) = self
15589 .buffer
15590 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15591 {
15592 if let Some((_, end_selections)) =
15593 self.selection_history.transaction_mut(transaction_id)
15594 {
15595 *end_selections = Some(self.selections.disjoint_anchors());
15596 } else {
15597 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15598 }
15599
15600 cx.emit(EditorEvent::Edited { transaction_id });
15601 Some(transaction_id)
15602 } else {
15603 None
15604 }
15605 }
15606
15607 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15608 if self.selection_mark_mode {
15609 self.change_selections(None, window, cx, |s| {
15610 s.move_with(|_, sel| {
15611 sel.collapse_to(sel.head(), SelectionGoal::None);
15612 });
15613 })
15614 }
15615 self.selection_mark_mode = true;
15616 cx.notify();
15617 }
15618
15619 pub fn swap_selection_ends(
15620 &mut self,
15621 _: &actions::SwapSelectionEnds,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 self.change_selections(None, window, cx, |s| {
15626 s.move_with(|_, sel| {
15627 if sel.start != sel.end {
15628 sel.reversed = !sel.reversed
15629 }
15630 });
15631 });
15632 self.request_autoscroll(Autoscroll::newest(), cx);
15633 cx.notify();
15634 }
15635
15636 pub fn toggle_fold(
15637 &mut self,
15638 _: &actions::ToggleFold,
15639 window: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) {
15642 if self.is_singleton(cx) {
15643 let selection = self.selections.newest::<Point>(cx);
15644
15645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15646 let range = if selection.is_empty() {
15647 let point = selection.head().to_display_point(&display_map);
15648 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15649 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15650 .to_point(&display_map);
15651 start..end
15652 } else {
15653 selection.range()
15654 };
15655 if display_map.folds_in_range(range).next().is_some() {
15656 self.unfold_lines(&Default::default(), window, cx)
15657 } else {
15658 self.fold(&Default::default(), window, cx)
15659 }
15660 } else {
15661 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15662 let buffer_ids: HashSet<_> = self
15663 .selections
15664 .disjoint_anchor_ranges()
15665 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15666 .collect();
15667
15668 let should_unfold = buffer_ids
15669 .iter()
15670 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15671
15672 for buffer_id in buffer_ids {
15673 if should_unfold {
15674 self.unfold_buffer(buffer_id, cx);
15675 } else {
15676 self.fold_buffer(buffer_id, cx);
15677 }
15678 }
15679 }
15680 }
15681
15682 pub fn toggle_fold_recursive(
15683 &mut self,
15684 _: &actions::ToggleFoldRecursive,
15685 window: &mut Window,
15686 cx: &mut Context<Self>,
15687 ) {
15688 let selection = self.selections.newest::<Point>(cx);
15689
15690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15691 let range = if selection.is_empty() {
15692 let point = selection.head().to_display_point(&display_map);
15693 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15694 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15695 .to_point(&display_map);
15696 start..end
15697 } else {
15698 selection.range()
15699 };
15700 if display_map.folds_in_range(range).next().is_some() {
15701 self.unfold_recursive(&Default::default(), window, cx)
15702 } else {
15703 self.fold_recursive(&Default::default(), window, cx)
15704 }
15705 }
15706
15707 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15708 if self.is_singleton(cx) {
15709 let mut to_fold = Vec::new();
15710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15711 let selections = self.selections.all_adjusted(cx);
15712
15713 for selection in selections {
15714 let range = selection.range().sorted();
15715 let buffer_start_row = range.start.row;
15716
15717 if range.start.row != range.end.row {
15718 let mut found = false;
15719 let mut row = range.start.row;
15720 while row <= range.end.row {
15721 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15722 {
15723 found = true;
15724 row = crease.range().end.row + 1;
15725 to_fold.push(crease);
15726 } else {
15727 row += 1
15728 }
15729 }
15730 if found {
15731 continue;
15732 }
15733 }
15734
15735 for row in (0..=range.start.row).rev() {
15736 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15737 if crease.range().end.row >= buffer_start_row {
15738 to_fold.push(crease);
15739 if row <= range.start.row {
15740 break;
15741 }
15742 }
15743 }
15744 }
15745 }
15746
15747 self.fold_creases(to_fold, true, window, cx);
15748 } else {
15749 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15750 let buffer_ids = self
15751 .selections
15752 .disjoint_anchor_ranges()
15753 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15754 .collect::<HashSet<_>>();
15755 for buffer_id in buffer_ids {
15756 self.fold_buffer(buffer_id, cx);
15757 }
15758 }
15759 }
15760
15761 fn fold_at_level(
15762 &mut self,
15763 fold_at: &FoldAtLevel,
15764 window: &mut Window,
15765 cx: &mut Context<Self>,
15766 ) {
15767 if !self.buffer.read(cx).is_singleton() {
15768 return;
15769 }
15770
15771 let fold_at_level = fold_at.0;
15772 let snapshot = self.buffer.read(cx).snapshot(cx);
15773 let mut to_fold = Vec::new();
15774 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15775
15776 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15777 while start_row < end_row {
15778 match self
15779 .snapshot(window, cx)
15780 .crease_for_buffer_row(MultiBufferRow(start_row))
15781 {
15782 Some(crease) => {
15783 let nested_start_row = crease.range().start.row + 1;
15784 let nested_end_row = crease.range().end.row;
15785
15786 if current_level < fold_at_level {
15787 stack.push((nested_start_row, nested_end_row, current_level + 1));
15788 } else if current_level == fold_at_level {
15789 to_fold.push(crease);
15790 }
15791
15792 start_row = nested_end_row + 1;
15793 }
15794 None => start_row += 1,
15795 }
15796 }
15797 }
15798
15799 self.fold_creases(to_fold, true, window, cx);
15800 }
15801
15802 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15803 if self.buffer.read(cx).is_singleton() {
15804 let mut fold_ranges = Vec::new();
15805 let snapshot = self.buffer.read(cx).snapshot(cx);
15806
15807 for row in 0..snapshot.max_row().0 {
15808 if let Some(foldable_range) = self
15809 .snapshot(window, cx)
15810 .crease_for_buffer_row(MultiBufferRow(row))
15811 {
15812 fold_ranges.push(foldable_range);
15813 }
15814 }
15815
15816 self.fold_creases(fold_ranges, true, window, cx);
15817 } else {
15818 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15819 editor
15820 .update_in(cx, |editor, _, cx| {
15821 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15822 editor.fold_buffer(buffer_id, cx);
15823 }
15824 })
15825 .ok();
15826 });
15827 }
15828 }
15829
15830 pub fn fold_function_bodies(
15831 &mut self,
15832 _: &actions::FoldFunctionBodies,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 let snapshot = self.buffer.read(cx).snapshot(cx);
15837
15838 let ranges = snapshot
15839 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15840 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15841 .collect::<Vec<_>>();
15842
15843 let creases = ranges
15844 .into_iter()
15845 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15846 .collect();
15847
15848 self.fold_creases(creases, true, window, cx);
15849 }
15850
15851 pub fn fold_recursive(
15852 &mut self,
15853 _: &actions::FoldRecursive,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 let mut to_fold = Vec::new();
15858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15859 let selections = self.selections.all_adjusted(cx);
15860
15861 for selection in selections {
15862 let range = selection.range().sorted();
15863 let buffer_start_row = range.start.row;
15864
15865 if range.start.row != range.end.row {
15866 let mut found = false;
15867 for row in range.start.row..=range.end.row {
15868 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15869 found = true;
15870 to_fold.push(crease);
15871 }
15872 }
15873 if found {
15874 continue;
15875 }
15876 }
15877
15878 for row in (0..=range.start.row).rev() {
15879 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15880 if crease.range().end.row >= buffer_start_row {
15881 to_fold.push(crease);
15882 } else {
15883 break;
15884 }
15885 }
15886 }
15887 }
15888
15889 self.fold_creases(to_fold, true, window, cx);
15890 }
15891
15892 pub fn fold_at(
15893 &mut self,
15894 buffer_row: MultiBufferRow,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15899
15900 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15901 let autoscroll = self
15902 .selections
15903 .all::<Point>(cx)
15904 .iter()
15905 .any(|selection| crease.range().overlaps(&selection.range()));
15906
15907 self.fold_creases(vec![crease], autoscroll, window, cx);
15908 }
15909 }
15910
15911 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15912 if self.is_singleton(cx) {
15913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15914 let buffer = &display_map.buffer_snapshot;
15915 let selections = self.selections.all::<Point>(cx);
15916 let ranges = selections
15917 .iter()
15918 .map(|s| {
15919 let range = s.display_range(&display_map).sorted();
15920 let mut start = range.start.to_point(&display_map);
15921 let mut end = range.end.to_point(&display_map);
15922 start.column = 0;
15923 end.column = buffer.line_len(MultiBufferRow(end.row));
15924 start..end
15925 })
15926 .collect::<Vec<_>>();
15927
15928 self.unfold_ranges(&ranges, true, true, cx);
15929 } else {
15930 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15931 let buffer_ids = self
15932 .selections
15933 .disjoint_anchor_ranges()
15934 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15935 .collect::<HashSet<_>>();
15936 for buffer_id in buffer_ids {
15937 self.unfold_buffer(buffer_id, cx);
15938 }
15939 }
15940 }
15941
15942 pub fn unfold_recursive(
15943 &mut self,
15944 _: &UnfoldRecursive,
15945 _window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15949 let selections = self.selections.all::<Point>(cx);
15950 let ranges = selections
15951 .iter()
15952 .map(|s| {
15953 let mut range = s.display_range(&display_map).sorted();
15954 *range.start.column_mut() = 0;
15955 *range.end.column_mut() = display_map.line_len(range.end.row());
15956 let start = range.start.to_point(&display_map);
15957 let end = range.end.to_point(&display_map);
15958 start..end
15959 })
15960 .collect::<Vec<_>>();
15961
15962 self.unfold_ranges(&ranges, true, true, cx);
15963 }
15964
15965 pub fn unfold_at(
15966 &mut self,
15967 buffer_row: MultiBufferRow,
15968 _window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15972
15973 let intersection_range = Point::new(buffer_row.0, 0)
15974 ..Point::new(
15975 buffer_row.0,
15976 display_map.buffer_snapshot.line_len(buffer_row),
15977 );
15978
15979 let autoscroll = self
15980 .selections
15981 .all::<Point>(cx)
15982 .iter()
15983 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15984
15985 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15986 }
15987
15988 pub fn unfold_all(
15989 &mut self,
15990 _: &actions::UnfoldAll,
15991 _window: &mut Window,
15992 cx: &mut Context<Self>,
15993 ) {
15994 if self.buffer.read(cx).is_singleton() {
15995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15996 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15997 } else {
15998 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15999 editor
16000 .update(cx, |editor, cx| {
16001 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16002 editor.unfold_buffer(buffer_id, cx);
16003 }
16004 })
16005 .ok();
16006 });
16007 }
16008 }
16009
16010 pub fn fold_selected_ranges(
16011 &mut self,
16012 _: &FoldSelectedRanges,
16013 window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 let selections = self.selections.all_adjusted(cx);
16017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16018 let ranges = selections
16019 .into_iter()
16020 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16021 .collect::<Vec<_>>();
16022 self.fold_creases(ranges, true, window, cx);
16023 }
16024
16025 pub fn fold_ranges<T: ToOffset + Clone>(
16026 &mut self,
16027 ranges: Vec<Range<T>>,
16028 auto_scroll: bool,
16029 window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) {
16032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16033 let ranges = ranges
16034 .into_iter()
16035 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16036 .collect::<Vec<_>>();
16037 self.fold_creases(ranges, auto_scroll, window, cx);
16038 }
16039
16040 pub fn fold_creases<T: ToOffset + Clone>(
16041 &mut self,
16042 creases: Vec<Crease<T>>,
16043 auto_scroll: bool,
16044 _window: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 if creases.is_empty() {
16048 return;
16049 }
16050
16051 let mut buffers_affected = HashSet::default();
16052 let multi_buffer = self.buffer().read(cx);
16053 for crease in &creases {
16054 if let Some((_, buffer, _)) =
16055 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16056 {
16057 buffers_affected.insert(buffer.read(cx).remote_id());
16058 };
16059 }
16060
16061 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16062
16063 if auto_scroll {
16064 self.request_autoscroll(Autoscroll::fit(), cx);
16065 }
16066
16067 cx.notify();
16068
16069 self.scrollbar_marker_state.dirty = true;
16070 self.folds_did_change(cx);
16071 }
16072
16073 /// Removes any folds whose ranges intersect any of the given ranges.
16074 pub fn unfold_ranges<T: ToOffset + Clone>(
16075 &mut self,
16076 ranges: &[Range<T>],
16077 inclusive: bool,
16078 auto_scroll: bool,
16079 cx: &mut Context<Self>,
16080 ) {
16081 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16082 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16083 });
16084 self.folds_did_change(cx);
16085 }
16086
16087 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16088 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16089 return;
16090 }
16091 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16092 self.display_map.update(cx, |display_map, cx| {
16093 display_map.fold_buffers([buffer_id], cx)
16094 });
16095 cx.emit(EditorEvent::BufferFoldToggled {
16096 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16097 folded: true,
16098 });
16099 cx.notify();
16100 }
16101
16102 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16103 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16104 return;
16105 }
16106 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16107 self.display_map.update(cx, |display_map, cx| {
16108 display_map.unfold_buffers([buffer_id], cx);
16109 });
16110 cx.emit(EditorEvent::BufferFoldToggled {
16111 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16112 folded: false,
16113 });
16114 cx.notify();
16115 }
16116
16117 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16118 self.display_map.read(cx).is_buffer_folded(buffer)
16119 }
16120
16121 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16122 self.display_map.read(cx).folded_buffers()
16123 }
16124
16125 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16126 self.display_map.update(cx, |display_map, cx| {
16127 display_map.disable_header_for_buffer(buffer_id, cx);
16128 });
16129 cx.notify();
16130 }
16131
16132 /// Removes any folds with the given ranges.
16133 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16134 &mut self,
16135 ranges: &[Range<T>],
16136 type_id: TypeId,
16137 auto_scroll: bool,
16138 cx: &mut Context<Self>,
16139 ) {
16140 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16141 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16142 });
16143 self.folds_did_change(cx);
16144 }
16145
16146 fn remove_folds_with<T: ToOffset + Clone>(
16147 &mut self,
16148 ranges: &[Range<T>],
16149 auto_scroll: bool,
16150 cx: &mut Context<Self>,
16151 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16152 ) {
16153 if ranges.is_empty() {
16154 return;
16155 }
16156
16157 let mut buffers_affected = HashSet::default();
16158 let multi_buffer = self.buffer().read(cx);
16159 for range in ranges {
16160 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16161 buffers_affected.insert(buffer.read(cx).remote_id());
16162 };
16163 }
16164
16165 self.display_map.update(cx, update);
16166
16167 if auto_scroll {
16168 self.request_autoscroll(Autoscroll::fit(), cx);
16169 }
16170
16171 cx.notify();
16172 self.scrollbar_marker_state.dirty = true;
16173 self.active_indent_guides_state.dirty = true;
16174 }
16175
16176 pub fn update_fold_widths(
16177 &mut self,
16178 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16179 cx: &mut Context<Self>,
16180 ) -> bool {
16181 self.display_map
16182 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16183 }
16184
16185 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16186 self.display_map.read(cx).fold_placeholder.clone()
16187 }
16188
16189 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16190 self.buffer.update(cx, |buffer, cx| {
16191 buffer.set_all_diff_hunks_expanded(cx);
16192 });
16193 }
16194
16195 pub fn expand_all_diff_hunks(
16196 &mut self,
16197 _: &ExpandAllDiffHunks,
16198 _window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) {
16201 self.buffer.update(cx, |buffer, cx| {
16202 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16203 });
16204 }
16205
16206 pub fn toggle_selected_diff_hunks(
16207 &mut self,
16208 _: &ToggleSelectedDiffHunks,
16209 _window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16213 self.toggle_diff_hunks_in_ranges(ranges, cx);
16214 }
16215
16216 pub fn diff_hunks_in_ranges<'a>(
16217 &'a self,
16218 ranges: &'a [Range<Anchor>],
16219 buffer: &'a MultiBufferSnapshot,
16220 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16221 ranges.iter().flat_map(move |range| {
16222 let end_excerpt_id = range.end.excerpt_id;
16223 let range = range.to_point(buffer);
16224 let mut peek_end = range.end;
16225 if range.end.row < buffer.max_row().0 {
16226 peek_end = Point::new(range.end.row + 1, 0);
16227 }
16228 buffer
16229 .diff_hunks_in_range(range.start..peek_end)
16230 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16231 })
16232 }
16233
16234 pub fn has_stageable_diff_hunks_in_ranges(
16235 &self,
16236 ranges: &[Range<Anchor>],
16237 snapshot: &MultiBufferSnapshot,
16238 ) -> bool {
16239 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16240 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16241 }
16242
16243 pub fn toggle_staged_selected_diff_hunks(
16244 &mut self,
16245 _: &::git::ToggleStaged,
16246 _: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 let snapshot = self.buffer.read(cx).snapshot(cx);
16250 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16251 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16252 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16253 }
16254
16255 pub fn set_render_diff_hunk_controls(
16256 &mut self,
16257 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16258 cx: &mut Context<Self>,
16259 ) {
16260 self.render_diff_hunk_controls = render_diff_hunk_controls;
16261 cx.notify();
16262 }
16263
16264 pub fn stage_and_next(
16265 &mut self,
16266 _: &::git::StageAndNext,
16267 window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) {
16270 self.do_stage_or_unstage_and_next(true, window, cx);
16271 }
16272
16273 pub fn unstage_and_next(
16274 &mut self,
16275 _: &::git::UnstageAndNext,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 self.do_stage_or_unstage_and_next(false, window, cx);
16280 }
16281
16282 pub fn stage_or_unstage_diff_hunks(
16283 &mut self,
16284 stage: bool,
16285 ranges: Vec<Range<Anchor>>,
16286 cx: &mut Context<Self>,
16287 ) {
16288 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16289 cx.spawn(async move |this, cx| {
16290 task.await?;
16291 this.update(cx, |this, cx| {
16292 let snapshot = this.buffer.read(cx).snapshot(cx);
16293 let chunk_by = this
16294 .diff_hunks_in_ranges(&ranges, &snapshot)
16295 .chunk_by(|hunk| hunk.buffer_id);
16296 for (buffer_id, hunks) in &chunk_by {
16297 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16298 }
16299 })
16300 })
16301 .detach_and_log_err(cx);
16302 }
16303
16304 fn save_buffers_for_ranges_if_needed(
16305 &mut self,
16306 ranges: &[Range<Anchor>],
16307 cx: &mut Context<Editor>,
16308 ) -> Task<Result<()>> {
16309 let multibuffer = self.buffer.read(cx);
16310 let snapshot = multibuffer.read(cx);
16311 let buffer_ids: HashSet<_> = ranges
16312 .iter()
16313 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16314 .collect();
16315 drop(snapshot);
16316
16317 let mut buffers = HashSet::default();
16318 for buffer_id in buffer_ids {
16319 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16320 let buffer = buffer_entity.read(cx);
16321 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16322 {
16323 buffers.insert(buffer_entity);
16324 }
16325 }
16326 }
16327
16328 if let Some(project) = &self.project {
16329 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16330 } else {
16331 Task::ready(Ok(()))
16332 }
16333 }
16334
16335 fn do_stage_or_unstage_and_next(
16336 &mut self,
16337 stage: bool,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) {
16341 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16342
16343 if ranges.iter().any(|range| range.start != range.end) {
16344 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16345 return;
16346 }
16347
16348 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16349 let snapshot = self.snapshot(window, cx);
16350 let position = self.selections.newest::<Point>(cx).head();
16351 let mut row = snapshot
16352 .buffer_snapshot
16353 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16354 .find(|hunk| hunk.row_range.start.0 > position.row)
16355 .map(|hunk| hunk.row_range.start);
16356
16357 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16358 // Outside of the project diff editor, wrap around to the beginning.
16359 if !all_diff_hunks_expanded {
16360 row = row.or_else(|| {
16361 snapshot
16362 .buffer_snapshot
16363 .diff_hunks_in_range(Point::zero()..position)
16364 .find(|hunk| hunk.row_range.end.0 < position.row)
16365 .map(|hunk| hunk.row_range.start)
16366 });
16367 }
16368
16369 if let Some(row) = row {
16370 let destination = Point::new(row.0, 0);
16371 let autoscroll = Autoscroll::center();
16372
16373 self.unfold_ranges(&[destination..destination], false, false, cx);
16374 self.change_selections(Some(autoscroll), window, cx, |s| {
16375 s.select_ranges([destination..destination]);
16376 });
16377 }
16378 }
16379
16380 fn do_stage_or_unstage(
16381 &self,
16382 stage: bool,
16383 buffer_id: BufferId,
16384 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16385 cx: &mut App,
16386 ) -> Option<()> {
16387 let project = self.project.as_ref()?;
16388 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16389 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16390 let buffer_snapshot = buffer.read(cx).snapshot();
16391 let file_exists = buffer_snapshot
16392 .file()
16393 .is_some_and(|file| file.disk_state().exists());
16394 diff.update(cx, |diff, cx| {
16395 diff.stage_or_unstage_hunks(
16396 stage,
16397 &hunks
16398 .map(|hunk| buffer_diff::DiffHunk {
16399 buffer_range: hunk.buffer_range,
16400 diff_base_byte_range: hunk.diff_base_byte_range,
16401 secondary_status: hunk.secondary_status,
16402 range: Point::zero()..Point::zero(), // unused
16403 })
16404 .collect::<Vec<_>>(),
16405 &buffer_snapshot,
16406 file_exists,
16407 cx,
16408 )
16409 });
16410 None
16411 }
16412
16413 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16414 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16415 self.buffer
16416 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16417 }
16418
16419 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16420 self.buffer.update(cx, |buffer, cx| {
16421 let ranges = vec![Anchor::min()..Anchor::max()];
16422 if !buffer.all_diff_hunks_expanded()
16423 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16424 {
16425 buffer.collapse_diff_hunks(ranges, cx);
16426 true
16427 } else {
16428 false
16429 }
16430 })
16431 }
16432
16433 fn toggle_diff_hunks_in_ranges(
16434 &mut self,
16435 ranges: Vec<Range<Anchor>>,
16436 cx: &mut Context<Editor>,
16437 ) {
16438 self.buffer.update(cx, |buffer, cx| {
16439 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16440 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16441 })
16442 }
16443
16444 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16445 self.buffer.update(cx, |buffer, cx| {
16446 let snapshot = buffer.snapshot(cx);
16447 let excerpt_id = range.end.excerpt_id;
16448 let point_range = range.to_point(&snapshot);
16449 let expand = !buffer.single_hunk_is_expanded(range, cx);
16450 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16451 })
16452 }
16453
16454 pub(crate) fn apply_all_diff_hunks(
16455 &mut self,
16456 _: &ApplyAllDiffHunks,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16461
16462 let buffers = self.buffer.read(cx).all_buffers();
16463 for branch_buffer in buffers {
16464 branch_buffer.update(cx, |branch_buffer, cx| {
16465 branch_buffer.merge_into_base(Vec::new(), cx);
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(crate) fn apply_selected_diff_hunks(
16475 &mut self,
16476 _: &ApplyDiffHunk,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) {
16480 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16481 let snapshot = self.snapshot(window, cx);
16482 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16483 let mut ranges_by_buffer = HashMap::default();
16484 self.transact(window, cx, |editor, _window, cx| {
16485 for hunk in hunks {
16486 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16487 ranges_by_buffer
16488 .entry(buffer.clone())
16489 .or_insert_with(Vec::new)
16490 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16491 }
16492 }
16493
16494 for (buffer, ranges) in ranges_by_buffer {
16495 buffer.update(cx, |buffer, cx| {
16496 buffer.merge_into_base(ranges, cx);
16497 });
16498 }
16499 });
16500
16501 if let Some(project) = self.project.clone() {
16502 self.save(true, project, window, cx).detach_and_log_err(cx);
16503 }
16504 }
16505
16506 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16507 if hovered != self.gutter_hovered {
16508 self.gutter_hovered = hovered;
16509 cx.notify();
16510 }
16511 }
16512
16513 pub fn insert_blocks(
16514 &mut self,
16515 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16516 autoscroll: Option<Autoscroll>,
16517 cx: &mut Context<Self>,
16518 ) -> Vec<CustomBlockId> {
16519 let blocks = self
16520 .display_map
16521 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16522 if let Some(autoscroll) = autoscroll {
16523 self.request_autoscroll(autoscroll, cx);
16524 }
16525 cx.notify();
16526 blocks
16527 }
16528
16529 pub fn resize_blocks(
16530 &mut self,
16531 heights: HashMap<CustomBlockId, u32>,
16532 autoscroll: Option<Autoscroll>,
16533 cx: &mut Context<Self>,
16534 ) {
16535 self.display_map
16536 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16537 if let Some(autoscroll) = autoscroll {
16538 self.request_autoscroll(autoscroll, cx);
16539 }
16540 cx.notify();
16541 }
16542
16543 pub fn replace_blocks(
16544 &mut self,
16545 renderers: HashMap<CustomBlockId, RenderBlock>,
16546 autoscroll: Option<Autoscroll>,
16547 cx: &mut Context<Self>,
16548 ) {
16549 self.display_map
16550 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16551 if let Some(autoscroll) = autoscroll {
16552 self.request_autoscroll(autoscroll, cx);
16553 }
16554 cx.notify();
16555 }
16556
16557 pub fn remove_blocks(
16558 &mut self,
16559 block_ids: HashSet<CustomBlockId>,
16560 autoscroll: Option<Autoscroll>,
16561 cx: &mut Context<Self>,
16562 ) {
16563 self.display_map.update(cx, |display_map, cx| {
16564 display_map.remove_blocks(block_ids, cx)
16565 });
16566 if let Some(autoscroll) = autoscroll {
16567 self.request_autoscroll(autoscroll, cx);
16568 }
16569 cx.notify();
16570 }
16571
16572 pub fn row_for_block(
16573 &self,
16574 block_id: CustomBlockId,
16575 cx: &mut Context<Self>,
16576 ) -> Option<DisplayRow> {
16577 self.display_map
16578 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16579 }
16580
16581 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16582 self.focused_block = Some(focused_block);
16583 }
16584
16585 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16586 self.focused_block.take()
16587 }
16588
16589 pub fn insert_creases(
16590 &mut self,
16591 creases: impl IntoIterator<Item = Crease<Anchor>>,
16592 cx: &mut Context<Self>,
16593 ) -> Vec<CreaseId> {
16594 self.display_map
16595 .update(cx, |map, cx| map.insert_creases(creases, cx))
16596 }
16597
16598 pub fn remove_creases(
16599 &mut self,
16600 ids: impl IntoIterator<Item = CreaseId>,
16601 cx: &mut Context<Self>,
16602 ) -> Vec<(CreaseId, Range<Anchor>)> {
16603 self.display_map
16604 .update(cx, |map, cx| map.remove_creases(ids, cx))
16605 }
16606
16607 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16608 self.display_map
16609 .update(cx, |map, cx| map.snapshot(cx))
16610 .longest_row()
16611 }
16612
16613 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16614 self.display_map
16615 .update(cx, |map, cx| map.snapshot(cx))
16616 .max_point()
16617 }
16618
16619 pub fn text(&self, cx: &App) -> String {
16620 self.buffer.read(cx).read(cx).text()
16621 }
16622
16623 pub fn is_empty(&self, cx: &App) -> bool {
16624 self.buffer.read(cx).read(cx).is_empty()
16625 }
16626
16627 pub fn text_option(&self, cx: &App) -> Option<String> {
16628 let text = self.text(cx);
16629 let text = text.trim();
16630
16631 if text.is_empty() {
16632 return None;
16633 }
16634
16635 Some(text.to_string())
16636 }
16637
16638 pub fn set_text(
16639 &mut self,
16640 text: impl Into<Arc<str>>,
16641 window: &mut Window,
16642 cx: &mut Context<Self>,
16643 ) {
16644 self.transact(window, cx, |this, _, cx| {
16645 this.buffer
16646 .read(cx)
16647 .as_singleton()
16648 .expect("you can only call set_text on editors for singleton buffers")
16649 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16650 });
16651 }
16652
16653 pub fn display_text(&self, cx: &mut App) -> String {
16654 self.display_map
16655 .update(cx, |map, cx| map.snapshot(cx))
16656 .text()
16657 }
16658
16659 fn create_minimap(
16660 &self,
16661 minimap_settings: MinimapSettings,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) -> Option<Entity<Self>> {
16665 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16666 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16667 }
16668
16669 fn initialize_new_minimap(
16670 &self,
16671 minimap_settings: MinimapSettings,
16672 window: &mut Window,
16673 cx: &mut Context<Self>,
16674 ) -> Entity<Self> {
16675 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16676
16677 let mut minimap = Editor::new_internal(
16678 EditorMode::Minimap {
16679 parent: cx.weak_entity(),
16680 },
16681 self.buffer.clone(),
16682 self.project.clone(),
16683 Some(self.display_map.clone()),
16684 window,
16685 cx,
16686 );
16687 minimap.scroll_manager.clone_state(&self.scroll_manager);
16688 minimap.set_text_style_refinement(TextStyleRefinement {
16689 font_size: Some(MINIMAP_FONT_SIZE),
16690 font_weight: Some(MINIMAP_FONT_WEIGHT),
16691 ..Default::default()
16692 });
16693 minimap.update_minimap_configuration(minimap_settings, cx);
16694 cx.new(|_| minimap)
16695 }
16696
16697 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16698 let current_line_highlight = minimap_settings
16699 .current_line_highlight
16700 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16701 self.set_current_line_highlight(Some(current_line_highlight));
16702 }
16703
16704 pub fn minimap(&self) -> Option<&Entity<Self>> {
16705 self.minimap
16706 .as_ref()
16707 .filter(|_| self.minimap_visibility.visible())
16708 }
16709
16710 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16711 let mut wrap_guides = smallvec::smallvec![];
16712
16713 if self.show_wrap_guides == Some(false) {
16714 return wrap_guides;
16715 }
16716
16717 let settings = self.buffer.read(cx).language_settings(cx);
16718 if settings.show_wrap_guides {
16719 match self.soft_wrap_mode(cx) {
16720 SoftWrap::Column(soft_wrap) => {
16721 wrap_guides.push((soft_wrap as usize, true));
16722 }
16723 SoftWrap::Bounded(soft_wrap) => {
16724 wrap_guides.push((soft_wrap as usize, true));
16725 }
16726 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16727 }
16728 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16729 }
16730
16731 wrap_guides
16732 }
16733
16734 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16735 let settings = self.buffer.read(cx).language_settings(cx);
16736 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16737 match mode {
16738 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16739 SoftWrap::None
16740 }
16741 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16742 language_settings::SoftWrap::PreferredLineLength => {
16743 SoftWrap::Column(settings.preferred_line_length)
16744 }
16745 language_settings::SoftWrap::Bounded => {
16746 SoftWrap::Bounded(settings.preferred_line_length)
16747 }
16748 }
16749 }
16750
16751 pub fn set_soft_wrap_mode(
16752 &mut self,
16753 mode: language_settings::SoftWrap,
16754
16755 cx: &mut Context<Self>,
16756 ) {
16757 self.soft_wrap_mode_override = Some(mode);
16758 cx.notify();
16759 }
16760
16761 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16762 self.hard_wrap = hard_wrap;
16763 cx.notify();
16764 }
16765
16766 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16767 self.text_style_refinement = Some(style);
16768 }
16769
16770 /// called by the Element so we know what style we were most recently rendered with.
16771 pub(crate) fn set_style(
16772 &mut self,
16773 style: EditorStyle,
16774 window: &mut Window,
16775 cx: &mut Context<Self>,
16776 ) {
16777 // We intentionally do not inform the display map about the minimap style
16778 // so that wrapping is not recalculated and stays consistent for the editor
16779 // and its linked minimap.
16780 if !self.mode.is_minimap() {
16781 let rem_size = window.rem_size();
16782 self.display_map.update(cx, |map, cx| {
16783 map.set_font(
16784 style.text.font(),
16785 style.text.font_size.to_pixels(rem_size),
16786 cx,
16787 )
16788 });
16789 }
16790 self.style = Some(style);
16791 }
16792
16793 pub fn style(&self) -> Option<&EditorStyle> {
16794 self.style.as_ref()
16795 }
16796
16797 // Called by the element. This method is not designed to be called outside of the editor
16798 // element's layout code because it does not notify when rewrapping is computed synchronously.
16799 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16800 self.display_map
16801 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16802 }
16803
16804 pub fn set_soft_wrap(&mut self) {
16805 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16806 }
16807
16808 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16809 if self.soft_wrap_mode_override.is_some() {
16810 self.soft_wrap_mode_override.take();
16811 } else {
16812 let soft_wrap = match self.soft_wrap_mode(cx) {
16813 SoftWrap::GitDiff => return,
16814 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16815 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16816 language_settings::SoftWrap::None
16817 }
16818 };
16819 self.soft_wrap_mode_override = Some(soft_wrap);
16820 }
16821 cx.notify();
16822 }
16823
16824 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16825 let Some(workspace) = self.workspace() else {
16826 return;
16827 };
16828 let fs = workspace.read(cx).app_state().fs.clone();
16829 let current_show = TabBarSettings::get_global(cx).show;
16830 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16831 setting.show = Some(!current_show);
16832 });
16833 }
16834
16835 pub fn toggle_indent_guides(
16836 &mut self,
16837 _: &ToggleIndentGuides,
16838 _: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) {
16841 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16842 self.buffer
16843 .read(cx)
16844 .language_settings(cx)
16845 .indent_guides
16846 .enabled
16847 });
16848 self.show_indent_guides = Some(!currently_enabled);
16849 cx.notify();
16850 }
16851
16852 fn should_show_indent_guides(&self) -> Option<bool> {
16853 self.show_indent_guides
16854 }
16855
16856 pub fn toggle_line_numbers(
16857 &mut self,
16858 _: &ToggleLineNumbers,
16859 _: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let mut editor_settings = EditorSettings::get_global(cx).clone();
16863 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16864 EditorSettings::override_global(editor_settings, cx);
16865 }
16866
16867 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16868 if let Some(show_line_numbers) = self.show_line_numbers {
16869 return show_line_numbers;
16870 }
16871 EditorSettings::get_global(cx).gutter.line_numbers
16872 }
16873
16874 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16875 self.use_relative_line_numbers
16876 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16877 }
16878
16879 pub fn toggle_relative_line_numbers(
16880 &mut self,
16881 _: &ToggleRelativeLineNumbers,
16882 _: &mut Window,
16883 cx: &mut Context<Self>,
16884 ) {
16885 let is_relative = self.should_use_relative_line_numbers(cx);
16886 self.set_relative_line_number(Some(!is_relative), cx)
16887 }
16888
16889 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16890 self.use_relative_line_numbers = is_relative;
16891 cx.notify();
16892 }
16893
16894 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16895 self.show_gutter = show_gutter;
16896 cx.notify();
16897 }
16898
16899 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16900 self.show_scrollbars = show_scrollbars;
16901 cx.notify();
16902 }
16903
16904 pub fn set_minimap_visibility(
16905 &mut self,
16906 minimap_visibility: MinimapVisibility,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 if self.minimap_visibility != minimap_visibility {
16911 if minimap_visibility.visible() && self.minimap.is_none() {
16912 let minimap_settings = EditorSettings::get_global(cx).minimap;
16913 self.minimap =
16914 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16915 }
16916 self.minimap_visibility = minimap_visibility;
16917 cx.notify();
16918 }
16919 }
16920
16921 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16922 self.set_show_scrollbars(false, cx);
16923 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16924 }
16925
16926 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16927 self.show_line_numbers = Some(show_line_numbers);
16928 cx.notify();
16929 }
16930
16931 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16932 self.disable_expand_excerpt_buttons = true;
16933 cx.notify();
16934 }
16935
16936 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16937 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16938 cx.notify();
16939 }
16940
16941 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16942 self.show_code_actions = Some(show_code_actions);
16943 cx.notify();
16944 }
16945
16946 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16947 self.show_runnables = Some(show_runnables);
16948 cx.notify();
16949 }
16950
16951 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16952 self.show_breakpoints = Some(show_breakpoints);
16953 cx.notify();
16954 }
16955
16956 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16957 if self.display_map.read(cx).masked != masked {
16958 self.display_map.update(cx, |map, _| map.masked = masked);
16959 }
16960 cx.notify()
16961 }
16962
16963 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16964 self.show_wrap_guides = Some(show_wrap_guides);
16965 cx.notify();
16966 }
16967
16968 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16969 self.show_indent_guides = Some(show_indent_guides);
16970 cx.notify();
16971 }
16972
16973 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16974 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16975 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16976 if let Some(dir) = file.abs_path(cx).parent() {
16977 return Some(dir.to_owned());
16978 }
16979 }
16980
16981 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16982 return Some(project_path.path.to_path_buf());
16983 }
16984 }
16985
16986 None
16987 }
16988
16989 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16990 self.active_excerpt(cx)?
16991 .1
16992 .read(cx)
16993 .file()
16994 .and_then(|f| f.as_local())
16995 }
16996
16997 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16998 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16999 let buffer = buffer.read(cx);
17000 if let Some(project_path) = buffer.project_path(cx) {
17001 let project = self.project.as_ref()?.read(cx);
17002 project.absolute_path(&project_path, cx)
17003 } else {
17004 buffer
17005 .file()
17006 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17007 }
17008 })
17009 }
17010
17011 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17012 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17013 let project_path = buffer.read(cx).project_path(cx)?;
17014 let project = self.project.as_ref()?.read(cx);
17015 let entry = project.entry_for_path(&project_path, cx)?;
17016 let path = entry.path.to_path_buf();
17017 Some(path)
17018 })
17019 }
17020
17021 pub fn reveal_in_finder(
17022 &mut self,
17023 _: &RevealInFileManager,
17024 _window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) {
17027 if let Some(target) = self.target_file(cx) {
17028 cx.reveal_path(&target.abs_path(cx));
17029 }
17030 }
17031
17032 pub fn copy_path(
17033 &mut self,
17034 _: &zed_actions::workspace::CopyPath,
17035 _window: &mut Window,
17036 cx: &mut Context<Self>,
17037 ) {
17038 if let Some(path) = self.target_file_abs_path(cx) {
17039 if let Some(path) = path.to_str() {
17040 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17041 }
17042 }
17043 }
17044
17045 pub fn copy_relative_path(
17046 &mut self,
17047 _: &zed_actions::workspace::CopyRelativePath,
17048 _window: &mut Window,
17049 cx: &mut Context<Self>,
17050 ) {
17051 if let Some(path) = self.target_file_path(cx) {
17052 if let Some(path) = path.to_str() {
17053 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17054 }
17055 }
17056 }
17057
17058 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17059 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17060 buffer.read(cx).project_path(cx)
17061 } else {
17062 None
17063 }
17064 }
17065
17066 // Returns true if the editor handled a go-to-line request
17067 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17068 maybe!({
17069 let breakpoint_store = self.breakpoint_store.as_ref()?;
17070
17071 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17072 else {
17073 self.clear_row_highlights::<ActiveDebugLine>();
17074 return None;
17075 };
17076
17077 let position = active_stack_frame.position;
17078 let buffer_id = position.buffer_id?;
17079 let snapshot = self
17080 .project
17081 .as_ref()?
17082 .read(cx)
17083 .buffer_for_id(buffer_id, cx)?
17084 .read(cx)
17085 .snapshot();
17086
17087 let mut handled = false;
17088 for (id, ExcerptRange { context, .. }) in
17089 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17090 {
17091 if context.start.cmp(&position, &snapshot).is_ge()
17092 || context.end.cmp(&position, &snapshot).is_lt()
17093 {
17094 continue;
17095 }
17096 let snapshot = self.buffer.read(cx).snapshot(cx);
17097 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17098
17099 handled = true;
17100 self.clear_row_highlights::<ActiveDebugLine>();
17101
17102 self.go_to_line::<ActiveDebugLine>(
17103 multibuffer_anchor,
17104 Some(cx.theme().colors().editor_debugger_active_line_background),
17105 window,
17106 cx,
17107 );
17108
17109 cx.notify();
17110 }
17111
17112 handled.then_some(())
17113 })
17114 .is_some()
17115 }
17116
17117 pub fn copy_file_name_without_extension(
17118 &mut self,
17119 _: &CopyFileNameWithoutExtension,
17120 _: &mut Window,
17121 cx: &mut Context<Self>,
17122 ) {
17123 if let Some(file) = self.target_file(cx) {
17124 if let Some(file_stem) = file.path().file_stem() {
17125 if let Some(name) = file_stem.to_str() {
17126 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17127 }
17128 }
17129 }
17130 }
17131
17132 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17133 if let Some(file) = self.target_file(cx) {
17134 if let Some(file_name) = file.path().file_name() {
17135 if let Some(name) = file_name.to_str() {
17136 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17137 }
17138 }
17139 }
17140 }
17141
17142 pub fn toggle_git_blame(
17143 &mut self,
17144 _: &::git::Blame,
17145 window: &mut Window,
17146 cx: &mut Context<Self>,
17147 ) {
17148 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17149
17150 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17151 self.start_git_blame(true, window, cx);
17152 }
17153
17154 cx.notify();
17155 }
17156
17157 pub fn toggle_git_blame_inline(
17158 &mut self,
17159 _: &ToggleGitBlameInline,
17160 window: &mut Window,
17161 cx: &mut Context<Self>,
17162 ) {
17163 self.toggle_git_blame_inline_internal(true, window, cx);
17164 cx.notify();
17165 }
17166
17167 pub fn open_git_blame_commit(
17168 &mut self,
17169 _: &OpenGitBlameCommit,
17170 window: &mut Window,
17171 cx: &mut Context<Self>,
17172 ) {
17173 self.open_git_blame_commit_internal(window, cx);
17174 }
17175
17176 fn open_git_blame_commit_internal(
17177 &mut self,
17178 window: &mut Window,
17179 cx: &mut Context<Self>,
17180 ) -> Option<()> {
17181 let blame = self.blame.as_ref()?;
17182 let snapshot = self.snapshot(window, cx);
17183 let cursor = self.selections.newest::<Point>(cx).head();
17184 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17185 let blame_entry = blame
17186 .update(cx, |blame, cx| {
17187 blame
17188 .blame_for_rows(
17189 &[RowInfo {
17190 buffer_id: Some(buffer.remote_id()),
17191 buffer_row: Some(point.row),
17192 ..Default::default()
17193 }],
17194 cx,
17195 )
17196 .next()
17197 })
17198 .flatten()?;
17199 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17200 let repo = blame.read(cx).repository(cx)?;
17201 let workspace = self.workspace()?.downgrade();
17202 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17203 None
17204 }
17205
17206 pub fn git_blame_inline_enabled(&self) -> bool {
17207 self.git_blame_inline_enabled
17208 }
17209
17210 pub fn toggle_selection_menu(
17211 &mut self,
17212 _: &ToggleSelectionMenu,
17213 _: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) {
17216 self.show_selection_menu = self
17217 .show_selection_menu
17218 .map(|show_selections_menu| !show_selections_menu)
17219 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17220
17221 cx.notify();
17222 }
17223
17224 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17225 self.show_selection_menu
17226 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17227 }
17228
17229 fn start_git_blame(
17230 &mut self,
17231 user_triggered: bool,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 if let Some(project) = self.project.as_ref() {
17236 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17237 return;
17238 };
17239
17240 if buffer.read(cx).file().is_none() {
17241 return;
17242 }
17243
17244 let focused = self.focus_handle(cx).contains_focused(window, cx);
17245
17246 let project = project.clone();
17247 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17248 self.blame_subscription =
17249 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17250 self.blame = Some(blame);
17251 }
17252 }
17253
17254 fn toggle_git_blame_inline_internal(
17255 &mut self,
17256 user_triggered: bool,
17257 window: &mut Window,
17258 cx: &mut Context<Self>,
17259 ) {
17260 if self.git_blame_inline_enabled {
17261 self.git_blame_inline_enabled = false;
17262 self.show_git_blame_inline = false;
17263 self.show_git_blame_inline_delay_task.take();
17264 } else {
17265 self.git_blame_inline_enabled = true;
17266 self.start_git_blame_inline(user_triggered, window, cx);
17267 }
17268
17269 cx.notify();
17270 }
17271
17272 fn start_git_blame_inline(
17273 &mut self,
17274 user_triggered: bool,
17275 window: &mut Window,
17276 cx: &mut Context<Self>,
17277 ) {
17278 self.start_git_blame(user_triggered, window, cx);
17279
17280 if ProjectSettings::get_global(cx)
17281 .git
17282 .inline_blame_delay()
17283 .is_some()
17284 {
17285 self.start_inline_blame_timer(window, cx);
17286 } else {
17287 self.show_git_blame_inline = true
17288 }
17289 }
17290
17291 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17292 self.blame.as_ref()
17293 }
17294
17295 pub fn show_git_blame_gutter(&self) -> bool {
17296 self.show_git_blame_gutter
17297 }
17298
17299 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17300 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17301 }
17302
17303 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17304 self.show_git_blame_inline
17305 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17306 && !self.newest_selection_head_on_empty_line(cx)
17307 && self.has_blame_entries(cx)
17308 }
17309
17310 fn has_blame_entries(&self, cx: &App) -> bool {
17311 self.blame()
17312 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17313 }
17314
17315 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17316 let cursor_anchor = self.selections.newest_anchor().head();
17317
17318 let snapshot = self.buffer.read(cx).snapshot(cx);
17319 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17320
17321 snapshot.line_len(buffer_row) == 0
17322 }
17323
17324 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17325 let buffer_and_selection = maybe!({
17326 let selection = self.selections.newest::<Point>(cx);
17327 let selection_range = selection.range();
17328
17329 let multi_buffer = self.buffer().read(cx);
17330 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17331 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17332
17333 let (buffer, range, _) = if selection.reversed {
17334 buffer_ranges.first()
17335 } else {
17336 buffer_ranges.last()
17337 }?;
17338
17339 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17340 ..text::ToPoint::to_point(&range.end, &buffer).row;
17341 Some((
17342 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17343 selection,
17344 ))
17345 });
17346
17347 let Some((buffer, selection)) = buffer_and_selection else {
17348 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17349 };
17350
17351 let Some(project) = self.project.as_ref() else {
17352 return Task::ready(Err(anyhow!("editor does not have project")));
17353 };
17354
17355 project.update(cx, |project, cx| {
17356 project.get_permalink_to_line(&buffer, selection, cx)
17357 })
17358 }
17359
17360 pub fn copy_permalink_to_line(
17361 &mut self,
17362 _: &CopyPermalinkToLine,
17363 window: &mut Window,
17364 cx: &mut Context<Self>,
17365 ) {
17366 let permalink_task = self.get_permalink_to_line(cx);
17367 let workspace = self.workspace();
17368
17369 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17370 Ok(permalink) => {
17371 cx.update(|_, cx| {
17372 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17373 })
17374 .ok();
17375 }
17376 Err(err) => {
17377 let message = format!("Failed to copy permalink: {err}");
17378
17379 anyhow::Result::<()>::Err(err).log_err();
17380
17381 if let Some(workspace) = workspace {
17382 workspace
17383 .update_in(cx, |workspace, _, cx| {
17384 struct CopyPermalinkToLine;
17385
17386 workspace.show_toast(
17387 Toast::new(
17388 NotificationId::unique::<CopyPermalinkToLine>(),
17389 message,
17390 ),
17391 cx,
17392 )
17393 })
17394 .ok();
17395 }
17396 }
17397 })
17398 .detach();
17399 }
17400
17401 pub fn copy_file_location(
17402 &mut self,
17403 _: &CopyFileLocation,
17404 _: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17408 if let Some(file) = self.target_file(cx) {
17409 if let Some(path) = file.path().to_str() {
17410 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17411 }
17412 }
17413 }
17414
17415 pub fn open_permalink_to_line(
17416 &mut self,
17417 _: &OpenPermalinkToLine,
17418 window: &mut Window,
17419 cx: &mut Context<Self>,
17420 ) {
17421 let permalink_task = self.get_permalink_to_line(cx);
17422 let workspace = self.workspace();
17423
17424 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17425 Ok(permalink) => {
17426 cx.update(|_, cx| {
17427 cx.open_url(permalink.as_ref());
17428 })
17429 .ok();
17430 }
17431 Err(err) => {
17432 let message = format!("Failed to open permalink: {err}");
17433
17434 anyhow::Result::<()>::Err(err).log_err();
17435
17436 if let Some(workspace) = workspace {
17437 workspace
17438 .update(cx, |workspace, cx| {
17439 struct OpenPermalinkToLine;
17440
17441 workspace.show_toast(
17442 Toast::new(
17443 NotificationId::unique::<OpenPermalinkToLine>(),
17444 message,
17445 ),
17446 cx,
17447 )
17448 })
17449 .ok();
17450 }
17451 }
17452 })
17453 .detach();
17454 }
17455
17456 pub fn insert_uuid_v4(
17457 &mut self,
17458 _: &InsertUuidV4,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 self.insert_uuid(UuidVersion::V4, window, cx);
17463 }
17464
17465 pub fn insert_uuid_v7(
17466 &mut self,
17467 _: &InsertUuidV7,
17468 window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) {
17471 self.insert_uuid(UuidVersion::V7, window, cx);
17472 }
17473
17474 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17476 self.transact(window, cx, |this, window, cx| {
17477 let edits = this
17478 .selections
17479 .all::<Point>(cx)
17480 .into_iter()
17481 .map(|selection| {
17482 let uuid = match version {
17483 UuidVersion::V4 => uuid::Uuid::new_v4(),
17484 UuidVersion::V7 => uuid::Uuid::now_v7(),
17485 };
17486
17487 (selection.range(), uuid.to_string())
17488 });
17489 this.edit(edits, cx);
17490 this.refresh_inline_completion(true, false, window, cx);
17491 });
17492 }
17493
17494 pub fn open_selections_in_multibuffer(
17495 &mut self,
17496 _: &OpenSelectionsInMultibuffer,
17497 window: &mut Window,
17498 cx: &mut Context<Self>,
17499 ) {
17500 let multibuffer = self.buffer.read(cx);
17501
17502 let Some(buffer) = multibuffer.as_singleton() else {
17503 return;
17504 };
17505
17506 let Some(workspace) = self.workspace() else {
17507 return;
17508 };
17509
17510 let locations = self
17511 .selections
17512 .disjoint_anchors()
17513 .iter()
17514 .map(|range| Location {
17515 buffer: buffer.clone(),
17516 range: range.start.text_anchor..range.end.text_anchor,
17517 })
17518 .collect::<Vec<_>>();
17519
17520 let title = multibuffer.title(cx).to_string();
17521
17522 cx.spawn_in(window, async move |_, cx| {
17523 workspace.update_in(cx, |workspace, window, cx| {
17524 Self::open_locations_in_multibuffer(
17525 workspace,
17526 locations,
17527 format!("Selections for '{title}'"),
17528 false,
17529 MultibufferSelectionMode::All,
17530 window,
17531 cx,
17532 );
17533 })
17534 })
17535 .detach();
17536 }
17537
17538 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17539 /// last highlight added will be used.
17540 ///
17541 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17542 pub fn highlight_rows<T: 'static>(
17543 &mut self,
17544 range: Range<Anchor>,
17545 color: Hsla,
17546 options: RowHighlightOptions,
17547 cx: &mut Context<Self>,
17548 ) {
17549 let snapshot = self.buffer().read(cx).snapshot(cx);
17550 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17551 let ix = row_highlights.binary_search_by(|highlight| {
17552 Ordering::Equal
17553 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17554 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17555 });
17556
17557 if let Err(mut ix) = ix {
17558 let index = post_inc(&mut self.highlight_order);
17559
17560 // If this range intersects with the preceding highlight, then merge it with
17561 // the preceding highlight. Otherwise insert a new highlight.
17562 let mut merged = false;
17563 if ix > 0 {
17564 let prev_highlight = &mut row_highlights[ix - 1];
17565 if prev_highlight
17566 .range
17567 .end
17568 .cmp(&range.start, &snapshot)
17569 .is_ge()
17570 {
17571 ix -= 1;
17572 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17573 prev_highlight.range.end = range.end;
17574 }
17575 merged = true;
17576 prev_highlight.index = index;
17577 prev_highlight.color = color;
17578 prev_highlight.options = options;
17579 }
17580 }
17581
17582 if !merged {
17583 row_highlights.insert(
17584 ix,
17585 RowHighlight {
17586 range: range.clone(),
17587 index,
17588 color,
17589 options,
17590 type_id: TypeId::of::<T>(),
17591 },
17592 );
17593 }
17594
17595 // If any of the following highlights intersect with this one, merge them.
17596 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17597 let highlight = &row_highlights[ix];
17598 if next_highlight
17599 .range
17600 .start
17601 .cmp(&highlight.range.end, &snapshot)
17602 .is_le()
17603 {
17604 if next_highlight
17605 .range
17606 .end
17607 .cmp(&highlight.range.end, &snapshot)
17608 .is_gt()
17609 {
17610 row_highlights[ix].range.end = next_highlight.range.end;
17611 }
17612 row_highlights.remove(ix + 1);
17613 } else {
17614 break;
17615 }
17616 }
17617 }
17618 }
17619
17620 /// Remove any highlighted row ranges of the given type that intersect the
17621 /// given ranges.
17622 pub fn remove_highlighted_rows<T: 'static>(
17623 &mut self,
17624 ranges_to_remove: Vec<Range<Anchor>>,
17625 cx: &mut Context<Self>,
17626 ) {
17627 let snapshot = self.buffer().read(cx).snapshot(cx);
17628 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17629 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17630 row_highlights.retain(|highlight| {
17631 while let Some(range_to_remove) = ranges_to_remove.peek() {
17632 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17633 Ordering::Less | Ordering::Equal => {
17634 ranges_to_remove.next();
17635 }
17636 Ordering::Greater => {
17637 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17638 Ordering::Less | Ordering::Equal => {
17639 return false;
17640 }
17641 Ordering::Greater => break,
17642 }
17643 }
17644 }
17645 }
17646
17647 true
17648 })
17649 }
17650
17651 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17652 pub fn clear_row_highlights<T: 'static>(&mut self) {
17653 self.highlighted_rows.remove(&TypeId::of::<T>());
17654 }
17655
17656 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17657 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17658 self.highlighted_rows
17659 .get(&TypeId::of::<T>())
17660 .map_or(&[] as &[_], |vec| vec.as_slice())
17661 .iter()
17662 .map(|highlight| (highlight.range.clone(), highlight.color))
17663 }
17664
17665 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17666 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17667 /// Allows to ignore certain kinds of highlights.
17668 pub fn highlighted_display_rows(
17669 &self,
17670 window: &mut Window,
17671 cx: &mut App,
17672 ) -> BTreeMap<DisplayRow, LineHighlight> {
17673 let snapshot = self.snapshot(window, cx);
17674 let mut used_highlight_orders = HashMap::default();
17675 self.highlighted_rows
17676 .iter()
17677 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17678 .fold(
17679 BTreeMap::<DisplayRow, LineHighlight>::new(),
17680 |mut unique_rows, highlight| {
17681 let start = highlight.range.start.to_display_point(&snapshot);
17682 let end = highlight.range.end.to_display_point(&snapshot);
17683 let start_row = start.row().0;
17684 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17685 && end.column() == 0
17686 {
17687 end.row().0.saturating_sub(1)
17688 } else {
17689 end.row().0
17690 };
17691 for row in start_row..=end_row {
17692 let used_index =
17693 used_highlight_orders.entry(row).or_insert(highlight.index);
17694 if highlight.index >= *used_index {
17695 *used_index = highlight.index;
17696 unique_rows.insert(
17697 DisplayRow(row),
17698 LineHighlight {
17699 include_gutter: highlight.options.include_gutter,
17700 border: None,
17701 background: highlight.color.into(),
17702 type_id: Some(highlight.type_id),
17703 },
17704 );
17705 }
17706 }
17707 unique_rows
17708 },
17709 )
17710 }
17711
17712 pub fn highlighted_display_row_for_autoscroll(
17713 &self,
17714 snapshot: &DisplaySnapshot,
17715 ) -> Option<DisplayRow> {
17716 self.highlighted_rows
17717 .values()
17718 .flat_map(|highlighted_rows| highlighted_rows.iter())
17719 .filter_map(|highlight| {
17720 if highlight.options.autoscroll {
17721 Some(highlight.range.start.to_display_point(snapshot).row())
17722 } else {
17723 None
17724 }
17725 })
17726 .min()
17727 }
17728
17729 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17730 self.highlight_background::<SearchWithinRange>(
17731 ranges,
17732 |colors| colors.editor_document_highlight_read_background,
17733 cx,
17734 )
17735 }
17736
17737 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17738 self.breadcrumb_header = Some(new_header);
17739 }
17740
17741 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17742 self.clear_background_highlights::<SearchWithinRange>(cx);
17743 }
17744
17745 pub fn highlight_background<T: 'static>(
17746 &mut self,
17747 ranges: &[Range<Anchor>],
17748 color_fetcher: fn(&ThemeColors) -> Hsla,
17749 cx: &mut Context<Self>,
17750 ) {
17751 self.background_highlights
17752 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17753 self.scrollbar_marker_state.dirty = true;
17754 cx.notify();
17755 }
17756
17757 pub fn clear_background_highlights<T: 'static>(
17758 &mut self,
17759 cx: &mut Context<Self>,
17760 ) -> Option<BackgroundHighlight> {
17761 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17762 if !text_highlights.1.is_empty() {
17763 self.scrollbar_marker_state.dirty = true;
17764 cx.notify();
17765 }
17766 Some(text_highlights)
17767 }
17768
17769 pub fn highlight_gutter<T: 'static>(
17770 &mut self,
17771 ranges: &[Range<Anchor>],
17772 color_fetcher: fn(&App) -> Hsla,
17773 cx: &mut Context<Self>,
17774 ) {
17775 self.gutter_highlights
17776 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17777 cx.notify();
17778 }
17779
17780 pub fn clear_gutter_highlights<T: 'static>(
17781 &mut self,
17782 cx: &mut Context<Self>,
17783 ) -> Option<GutterHighlight> {
17784 cx.notify();
17785 self.gutter_highlights.remove(&TypeId::of::<T>())
17786 }
17787
17788 #[cfg(feature = "test-support")]
17789 pub fn all_text_background_highlights(
17790 &self,
17791 window: &mut Window,
17792 cx: &mut Context<Self>,
17793 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17794 let snapshot = self.snapshot(window, cx);
17795 let buffer = &snapshot.buffer_snapshot;
17796 let start = buffer.anchor_before(0);
17797 let end = buffer.anchor_after(buffer.len());
17798 let theme = cx.theme().colors();
17799 self.background_highlights_in_range(start..end, &snapshot, theme)
17800 }
17801
17802 #[cfg(feature = "test-support")]
17803 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17804 let snapshot = self.buffer().read(cx).snapshot(cx);
17805
17806 let highlights = self
17807 .background_highlights
17808 .get(&TypeId::of::<items::BufferSearchHighlights>());
17809
17810 if let Some((_color, ranges)) = highlights {
17811 ranges
17812 .iter()
17813 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17814 .collect_vec()
17815 } else {
17816 vec![]
17817 }
17818 }
17819
17820 fn document_highlights_for_position<'a>(
17821 &'a self,
17822 position: Anchor,
17823 buffer: &'a MultiBufferSnapshot,
17824 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17825 let read_highlights = self
17826 .background_highlights
17827 .get(&TypeId::of::<DocumentHighlightRead>())
17828 .map(|h| &h.1);
17829 let write_highlights = self
17830 .background_highlights
17831 .get(&TypeId::of::<DocumentHighlightWrite>())
17832 .map(|h| &h.1);
17833 let left_position = position.bias_left(buffer);
17834 let right_position = position.bias_right(buffer);
17835 read_highlights
17836 .into_iter()
17837 .chain(write_highlights)
17838 .flat_map(move |ranges| {
17839 let start_ix = match ranges.binary_search_by(|probe| {
17840 let cmp = probe.end.cmp(&left_position, buffer);
17841 if cmp.is_ge() {
17842 Ordering::Greater
17843 } else {
17844 Ordering::Less
17845 }
17846 }) {
17847 Ok(i) | Err(i) => i,
17848 };
17849
17850 ranges[start_ix..]
17851 .iter()
17852 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17853 })
17854 }
17855
17856 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17857 self.background_highlights
17858 .get(&TypeId::of::<T>())
17859 .map_or(false, |(_, highlights)| !highlights.is_empty())
17860 }
17861
17862 pub fn background_highlights_in_range(
17863 &self,
17864 search_range: Range<Anchor>,
17865 display_snapshot: &DisplaySnapshot,
17866 theme: &ThemeColors,
17867 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17868 let mut results = Vec::new();
17869 for (color_fetcher, ranges) in self.background_highlights.values() {
17870 let color = color_fetcher(theme);
17871 let start_ix = match ranges.binary_search_by(|probe| {
17872 let cmp = probe
17873 .end
17874 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17875 if cmp.is_gt() {
17876 Ordering::Greater
17877 } else {
17878 Ordering::Less
17879 }
17880 }) {
17881 Ok(i) | Err(i) => i,
17882 };
17883 for range in &ranges[start_ix..] {
17884 if range
17885 .start
17886 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17887 .is_ge()
17888 {
17889 break;
17890 }
17891
17892 let start = range.start.to_display_point(display_snapshot);
17893 let end = range.end.to_display_point(display_snapshot);
17894 results.push((start..end, color))
17895 }
17896 }
17897 results
17898 }
17899
17900 pub fn background_highlight_row_ranges<T: 'static>(
17901 &self,
17902 search_range: Range<Anchor>,
17903 display_snapshot: &DisplaySnapshot,
17904 count: usize,
17905 ) -> Vec<RangeInclusive<DisplayPoint>> {
17906 let mut results = Vec::new();
17907 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17908 return vec![];
17909 };
17910
17911 let start_ix = match ranges.binary_search_by(|probe| {
17912 let cmp = probe
17913 .end
17914 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17915 if cmp.is_gt() {
17916 Ordering::Greater
17917 } else {
17918 Ordering::Less
17919 }
17920 }) {
17921 Ok(i) | Err(i) => i,
17922 };
17923 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17924 if let (Some(start_display), Some(end_display)) = (start, end) {
17925 results.push(
17926 start_display.to_display_point(display_snapshot)
17927 ..=end_display.to_display_point(display_snapshot),
17928 );
17929 }
17930 };
17931 let mut start_row: Option<Point> = None;
17932 let mut end_row: Option<Point> = None;
17933 if ranges.len() > count {
17934 return Vec::new();
17935 }
17936 for range in &ranges[start_ix..] {
17937 if range
17938 .start
17939 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17940 .is_ge()
17941 {
17942 break;
17943 }
17944 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17945 if let Some(current_row) = &end_row {
17946 if end.row == current_row.row {
17947 continue;
17948 }
17949 }
17950 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17951 if start_row.is_none() {
17952 assert_eq!(end_row, None);
17953 start_row = Some(start);
17954 end_row = Some(end);
17955 continue;
17956 }
17957 if let Some(current_end) = end_row.as_mut() {
17958 if start.row > current_end.row + 1 {
17959 push_region(start_row, end_row);
17960 start_row = Some(start);
17961 end_row = Some(end);
17962 } else {
17963 // Merge two hunks.
17964 *current_end = end;
17965 }
17966 } else {
17967 unreachable!();
17968 }
17969 }
17970 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17971 push_region(start_row, end_row);
17972 results
17973 }
17974
17975 pub fn gutter_highlights_in_range(
17976 &self,
17977 search_range: Range<Anchor>,
17978 display_snapshot: &DisplaySnapshot,
17979 cx: &App,
17980 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17981 let mut results = Vec::new();
17982 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17983 let color = color_fetcher(cx);
17984 let start_ix = match ranges.binary_search_by(|probe| {
17985 let cmp = probe
17986 .end
17987 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17988 if cmp.is_gt() {
17989 Ordering::Greater
17990 } else {
17991 Ordering::Less
17992 }
17993 }) {
17994 Ok(i) | Err(i) => i,
17995 };
17996 for range in &ranges[start_ix..] {
17997 if range
17998 .start
17999 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18000 .is_ge()
18001 {
18002 break;
18003 }
18004
18005 let start = range.start.to_display_point(display_snapshot);
18006 let end = range.end.to_display_point(display_snapshot);
18007 results.push((start..end, color))
18008 }
18009 }
18010 results
18011 }
18012
18013 /// Get the text ranges corresponding to the redaction query
18014 pub fn redacted_ranges(
18015 &self,
18016 search_range: Range<Anchor>,
18017 display_snapshot: &DisplaySnapshot,
18018 cx: &App,
18019 ) -> Vec<Range<DisplayPoint>> {
18020 display_snapshot
18021 .buffer_snapshot
18022 .redacted_ranges(search_range, |file| {
18023 if let Some(file) = file {
18024 file.is_private()
18025 && EditorSettings::get(
18026 Some(SettingsLocation {
18027 worktree_id: file.worktree_id(cx),
18028 path: file.path().as_ref(),
18029 }),
18030 cx,
18031 )
18032 .redact_private_values
18033 } else {
18034 false
18035 }
18036 })
18037 .map(|range| {
18038 range.start.to_display_point(display_snapshot)
18039 ..range.end.to_display_point(display_snapshot)
18040 })
18041 .collect()
18042 }
18043
18044 pub fn highlight_text<T: 'static>(
18045 &mut self,
18046 ranges: Vec<Range<Anchor>>,
18047 style: HighlightStyle,
18048 cx: &mut Context<Self>,
18049 ) {
18050 self.display_map.update(cx, |map, _| {
18051 map.highlight_text(TypeId::of::<T>(), ranges, style)
18052 });
18053 cx.notify();
18054 }
18055
18056 pub(crate) fn highlight_inlays<T: 'static>(
18057 &mut self,
18058 highlights: Vec<InlayHighlight>,
18059 style: HighlightStyle,
18060 cx: &mut Context<Self>,
18061 ) {
18062 self.display_map.update(cx, |map, _| {
18063 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18064 });
18065 cx.notify();
18066 }
18067
18068 pub fn text_highlights<'a, T: 'static>(
18069 &'a self,
18070 cx: &'a App,
18071 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18072 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18073 }
18074
18075 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18076 let cleared = self
18077 .display_map
18078 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18079 if cleared {
18080 cx.notify();
18081 }
18082 }
18083
18084 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18085 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18086 && self.focus_handle.is_focused(window)
18087 }
18088
18089 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18090 self.show_cursor_when_unfocused = is_enabled;
18091 cx.notify();
18092 }
18093
18094 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18095 cx.notify();
18096 }
18097
18098 fn on_debug_session_event(
18099 &mut self,
18100 _session: Entity<Session>,
18101 event: &SessionEvent,
18102 cx: &mut Context<Self>,
18103 ) {
18104 match event {
18105 SessionEvent::InvalidateInlineValue => {
18106 self.refresh_inline_values(cx);
18107 }
18108 _ => {}
18109 }
18110 }
18111
18112 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18113 let Some(project) = self.project.clone() else {
18114 return;
18115 };
18116
18117 if !self.inline_value_cache.enabled {
18118 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18119 self.splice_inlays(&inlays, Vec::new(), cx);
18120 return;
18121 }
18122
18123 let current_execution_position = self
18124 .highlighted_rows
18125 .get(&TypeId::of::<ActiveDebugLine>())
18126 .and_then(|lines| lines.last().map(|line| line.range.start));
18127
18128 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18129 let inline_values = editor
18130 .update(cx, |editor, cx| {
18131 let Some(current_execution_position) = current_execution_position else {
18132 return Some(Task::ready(Ok(Vec::new())));
18133 };
18134
18135 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18136 let snapshot = buffer.snapshot(cx);
18137
18138 let excerpt = snapshot.excerpt_containing(
18139 current_execution_position..current_execution_position,
18140 )?;
18141
18142 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18143 })?;
18144
18145 let range =
18146 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18147
18148 project.inline_values(buffer, range, cx)
18149 })
18150 .ok()
18151 .flatten()?
18152 .await
18153 .context("refreshing debugger inlays")
18154 .log_err()?;
18155
18156 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18157
18158 for (buffer_id, inline_value) in inline_values
18159 .into_iter()
18160 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18161 {
18162 buffer_inline_values
18163 .entry(buffer_id)
18164 .or_default()
18165 .push(inline_value);
18166 }
18167
18168 editor
18169 .update(cx, |editor, cx| {
18170 let snapshot = editor.buffer.read(cx).snapshot(cx);
18171 let mut new_inlays = Vec::default();
18172
18173 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18174 let buffer_id = buffer_snapshot.remote_id();
18175 buffer_inline_values
18176 .get(&buffer_id)
18177 .into_iter()
18178 .flatten()
18179 .for_each(|hint| {
18180 let inlay = Inlay::debugger_hint(
18181 post_inc(&mut editor.next_inlay_id),
18182 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18183 hint.text(),
18184 );
18185
18186 new_inlays.push(inlay);
18187 });
18188 }
18189
18190 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18191 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18192
18193 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18194 })
18195 .ok()?;
18196 Some(())
18197 });
18198 }
18199
18200 fn on_buffer_event(
18201 &mut self,
18202 multibuffer: &Entity<MultiBuffer>,
18203 event: &multi_buffer::Event,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) {
18207 match event {
18208 multi_buffer::Event::Edited {
18209 singleton_buffer_edited,
18210 edited_buffer: buffer_edited,
18211 } => {
18212 self.scrollbar_marker_state.dirty = true;
18213 self.active_indent_guides_state.dirty = true;
18214 self.refresh_active_diagnostics(cx);
18215 self.refresh_code_actions(window, cx);
18216 self.refresh_selected_text_highlights(true, window, cx);
18217 refresh_matching_bracket_highlights(self, window, cx);
18218 if self.has_active_inline_completion() {
18219 self.update_visible_inline_completion(window, cx);
18220 }
18221 if let Some(buffer) = buffer_edited {
18222 let buffer_id = buffer.read(cx).remote_id();
18223 if !self.registered_buffers.contains_key(&buffer_id) {
18224 if let Some(project) = self.project.as_ref() {
18225 project.update(cx, |project, cx| {
18226 self.registered_buffers.insert(
18227 buffer_id,
18228 project.register_buffer_with_language_servers(&buffer, cx),
18229 );
18230 })
18231 }
18232 }
18233 }
18234 cx.emit(EditorEvent::BufferEdited);
18235 cx.emit(SearchEvent::MatchesInvalidated);
18236 if *singleton_buffer_edited {
18237 if let Some(project) = &self.project {
18238 #[allow(clippy::mutable_key_type)]
18239 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18240 multibuffer
18241 .all_buffers()
18242 .into_iter()
18243 .filter_map(|buffer| {
18244 buffer.update(cx, |buffer, cx| {
18245 let language = buffer.language()?;
18246 let should_discard = project.update(cx, |project, cx| {
18247 project.is_local()
18248 && !project.has_language_servers_for(buffer, cx)
18249 });
18250 should_discard.not().then_some(language.clone())
18251 })
18252 })
18253 .collect::<HashSet<_>>()
18254 });
18255 if !languages_affected.is_empty() {
18256 self.refresh_inlay_hints(
18257 InlayHintRefreshReason::BufferEdited(languages_affected),
18258 cx,
18259 );
18260 }
18261 }
18262 }
18263
18264 let Some(project) = &self.project else { return };
18265 let (telemetry, is_via_ssh) = {
18266 let project = project.read(cx);
18267 let telemetry = project.client().telemetry().clone();
18268 let is_via_ssh = project.is_via_ssh();
18269 (telemetry, is_via_ssh)
18270 };
18271 refresh_linked_ranges(self, window, cx);
18272 telemetry.log_edit_event("editor", is_via_ssh);
18273 }
18274 multi_buffer::Event::ExcerptsAdded {
18275 buffer,
18276 predecessor,
18277 excerpts,
18278 } => {
18279 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18280 let buffer_id = buffer.read(cx).remote_id();
18281 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18282 if let Some(project) = &self.project {
18283 update_uncommitted_diff_for_buffer(
18284 cx.entity(),
18285 project,
18286 [buffer.clone()],
18287 self.buffer.clone(),
18288 cx,
18289 )
18290 .detach();
18291 }
18292 }
18293 cx.emit(EditorEvent::ExcerptsAdded {
18294 buffer: buffer.clone(),
18295 predecessor: *predecessor,
18296 excerpts: excerpts.clone(),
18297 });
18298 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18299 }
18300 multi_buffer::Event::ExcerptsRemoved {
18301 ids,
18302 removed_buffer_ids,
18303 } => {
18304 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18305 let buffer = self.buffer.read(cx);
18306 self.registered_buffers
18307 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18308 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18309 cx.emit(EditorEvent::ExcerptsRemoved {
18310 ids: ids.clone(),
18311 removed_buffer_ids: removed_buffer_ids.clone(),
18312 })
18313 }
18314 multi_buffer::Event::ExcerptsEdited {
18315 excerpt_ids,
18316 buffer_ids,
18317 } => {
18318 self.display_map.update(cx, |map, cx| {
18319 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18320 });
18321 cx.emit(EditorEvent::ExcerptsEdited {
18322 ids: excerpt_ids.clone(),
18323 })
18324 }
18325 multi_buffer::Event::ExcerptsExpanded { ids } => {
18326 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18327 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18328 }
18329 multi_buffer::Event::Reparsed(buffer_id) => {
18330 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18331 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18332
18333 cx.emit(EditorEvent::Reparsed(*buffer_id));
18334 }
18335 multi_buffer::Event::DiffHunksToggled => {
18336 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18337 }
18338 multi_buffer::Event::LanguageChanged(buffer_id) => {
18339 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18340 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18341 cx.emit(EditorEvent::Reparsed(*buffer_id));
18342 cx.notify();
18343 }
18344 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18345 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18346 multi_buffer::Event::FileHandleChanged
18347 | multi_buffer::Event::Reloaded
18348 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18349 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18350 multi_buffer::Event::DiagnosticsUpdated => {
18351 self.refresh_active_diagnostics(cx);
18352 self.refresh_inline_diagnostics(true, window, cx);
18353 self.scrollbar_marker_state.dirty = true;
18354 cx.notify();
18355 }
18356 _ => {}
18357 };
18358 }
18359
18360 pub fn start_temporary_diff_override(&mut self) {
18361 self.load_diff_task.take();
18362 self.temporary_diff_override = true;
18363 }
18364
18365 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18366 self.temporary_diff_override = false;
18367 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18368 self.buffer.update(cx, |buffer, cx| {
18369 buffer.set_all_diff_hunks_collapsed(cx);
18370 });
18371
18372 if let Some(project) = self.project.clone() {
18373 self.load_diff_task = Some(
18374 update_uncommitted_diff_for_buffer(
18375 cx.entity(),
18376 &project,
18377 self.buffer.read(cx).all_buffers(),
18378 self.buffer.clone(),
18379 cx,
18380 )
18381 .shared(),
18382 );
18383 }
18384 }
18385
18386 fn on_display_map_changed(
18387 &mut self,
18388 _: Entity<DisplayMap>,
18389 _: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 cx.notify();
18393 }
18394
18395 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18396 let new_severity = if self.diagnostics_enabled() {
18397 EditorSettings::get_global(cx)
18398 .diagnostics_max_severity
18399 .unwrap_or(DiagnosticSeverity::Hint)
18400 } else {
18401 DiagnosticSeverity::Off
18402 };
18403 self.set_max_diagnostics_severity(new_severity, cx);
18404 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18405 self.update_edit_prediction_settings(cx);
18406 self.refresh_inline_completion(true, false, window, cx);
18407 self.refresh_inlay_hints(
18408 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18409 self.selections.newest_anchor().head(),
18410 &self.buffer.read(cx).snapshot(cx),
18411 cx,
18412 )),
18413 cx,
18414 );
18415
18416 let old_cursor_shape = self.cursor_shape;
18417
18418 {
18419 let editor_settings = EditorSettings::get_global(cx);
18420 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18421 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18422 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18423 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18424 }
18425
18426 if old_cursor_shape != self.cursor_shape {
18427 cx.emit(EditorEvent::CursorShapeChanged);
18428 }
18429
18430 let project_settings = ProjectSettings::get_global(cx);
18431 self.serialize_dirty_buffers =
18432 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18433
18434 if self.mode.is_full() {
18435 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18436 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18437 if self.show_inline_diagnostics != show_inline_diagnostics {
18438 self.show_inline_diagnostics = show_inline_diagnostics;
18439 self.refresh_inline_diagnostics(false, window, cx);
18440 }
18441
18442 if self.git_blame_inline_enabled != inline_blame_enabled {
18443 self.toggle_git_blame_inline_internal(false, window, cx);
18444 }
18445
18446 let minimap_settings = EditorSettings::get_global(cx).minimap;
18447 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18448 self.set_minimap_visibility(
18449 self.minimap_visibility.toggle_visibility(),
18450 window,
18451 cx,
18452 );
18453 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18454 minimap_entity.update(cx, |minimap_editor, cx| {
18455 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18456 })
18457 }
18458 }
18459
18460 cx.notify();
18461 }
18462
18463 pub fn set_searchable(&mut self, searchable: bool) {
18464 self.searchable = searchable;
18465 }
18466
18467 pub fn searchable(&self) -> bool {
18468 self.searchable
18469 }
18470
18471 fn open_proposed_changes_editor(
18472 &mut self,
18473 _: &OpenProposedChangesEditor,
18474 window: &mut Window,
18475 cx: &mut Context<Self>,
18476 ) {
18477 let Some(workspace) = self.workspace() else {
18478 cx.propagate();
18479 return;
18480 };
18481
18482 let selections = self.selections.all::<usize>(cx);
18483 let multi_buffer = self.buffer.read(cx);
18484 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18485 let mut new_selections_by_buffer = HashMap::default();
18486 for selection in selections {
18487 for (buffer, range, _) in
18488 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18489 {
18490 let mut range = range.to_point(buffer);
18491 range.start.column = 0;
18492 range.end.column = buffer.line_len(range.end.row);
18493 new_selections_by_buffer
18494 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18495 .or_insert(Vec::new())
18496 .push(range)
18497 }
18498 }
18499
18500 let proposed_changes_buffers = new_selections_by_buffer
18501 .into_iter()
18502 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18503 .collect::<Vec<_>>();
18504 let proposed_changes_editor = cx.new(|cx| {
18505 ProposedChangesEditor::new(
18506 "Proposed changes",
18507 proposed_changes_buffers,
18508 self.project.clone(),
18509 window,
18510 cx,
18511 )
18512 });
18513
18514 window.defer(cx, move |window, cx| {
18515 workspace.update(cx, |workspace, cx| {
18516 workspace.active_pane().update(cx, |pane, cx| {
18517 pane.add_item(
18518 Box::new(proposed_changes_editor),
18519 true,
18520 true,
18521 None,
18522 window,
18523 cx,
18524 );
18525 });
18526 });
18527 });
18528 }
18529
18530 pub fn open_excerpts_in_split(
18531 &mut self,
18532 _: &OpenExcerptsSplit,
18533 window: &mut Window,
18534 cx: &mut Context<Self>,
18535 ) {
18536 self.open_excerpts_common(None, true, window, cx)
18537 }
18538
18539 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18540 self.open_excerpts_common(None, false, window, cx)
18541 }
18542
18543 fn open_excerpts_common(
18544 &mut self,
18545 jump_data: Option<JumpData>,
18546 split: bool,
18547 window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) {
18550 let Some(workspace) = self.workspace() else {
18551 cx.propagate();
18552 return;
18553 };
18554
18555 if self.buffer.read(cx).is_singleton() {
18556 cx.propagate();
18557 return;
18558 }
18559
18560 let mut new_selections_by_buffer = HashMap::default();
18561 match &jump_data {
18562 Some(JumpData::MultiBufferPoint {
18563 excerpt_id,
18564 position,
18565 anchor,
18566 line_offset_from_top,
18567 }) => {
18568 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18569 if let Some(buffer) = multi_buffer_snapshot
18570 .buffer_id_for_excerpt(*excerpt_id)
18571 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18572 {
18573 let buffer_snapshot = buffer.read(cx).snapshot();
18574 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18575 language::ToPoint::to_point(anchor, &buffer_snapshot)
18576 } else {
18577 buffer_snapshot.clip_point(*position, Bias::Left)
18578 };
18579 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18580 new_selections_by_buffer.insert(
18581 buffer,
18582 (
18583 vec![jump_to_offset..jump_to_offset],
18584 Some(*line_offset_from_top),
18585 ),
18586 );
18587 }
18588 }
18589 Some(JumpData::MultiBufferRow {
18590 row,
18591 line_offset_from_top,
18592 }) => {
18593 let point = MultiBufferPoint::new(row.0, 0);
18594 if let Some((buffer, buffer_point, _)) =
18595 self.buffer.read(cx).point_to_buffer_point(point, cx)
18596 {
18597 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18598 new_selections_by_buffer
18599 .entry(buffer)
18600 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18601 .0
18602 .push(buffer_offset..buffer_offset)
18603 }
18604 }
18605 None => {
18606 let selections = self.selections.all::<usize>(cx);
18607 let multi_buffer = self.buffer.read(cx);
18608 for selection in selections {
18609 for (snapshot, range, _, anchor) in multi_buffer
18610 .snapshot(cx)
18611 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18612 {
18613 if let Some(anchor) = anchor {
18614 // selection is in a deleted hunk
18615 let Some(buffer_id) = anchor.buffer_id else {
18616 continue;
18617 };
18618 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18619 continue;
18620 };
18621 let offset = text::ToOffset::to_offset(
18622 &anchor.text_anchor,
18623 &buffer_handle.read(cx).snapshot(),
18624 );
18625 let range = offset..offset;
18626 new_selections_by_buffer
18627 .entry(buffer_handle)
18628 .or_insert((Vec::new(), None))
18629 .0
18630 .push(range)
18631 } else {
18632 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18633 else {
18634 continue;
18635 };
18636 new_selections_by_buffer
18637 .entry(buffer_handle)
18638 .or_insert((Vec::new(), None))
18639 .0
18640 .push(range)
18641 }
18642 }
18643 }
18644 }
18645 }
18646
18647 new_selections_by_buffer
18648 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18649
18650 if new_selections_by_buffer.is_empty() {
18651 return;
18652 }
18653
18654 // We defer the pane interaction because we ourselves are a workspace item
18655 // and activating a new item causes the pane to call a method on us reentrantly,
18656 // which panics if we're on the stack.
18657 window.defer(cx, move |window, cx| {
18658 workspace.update(cx, |workspace, cx| {
18659 let pane = if split {
18660 workspace.adjacent_pane(window, cx)
18661 } else {
18662 workspace.active_pane().clone()
18663 };
18664
18665 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18666 let editor = buffer
18667 .read(cx)
18668 .file()
18669 .is_none()
18670 .then(|| {
18671 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18672 // so `workspace.open_project_item` will never find them, always opening a new editor.
18673 // Instead, we try to activate the existing editor in the pane first.
18674 let (editor, pane_item_index) =
18675 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18676 let editor = item.downcast::<Editor>()?;
18677 let singleton_buffer =
18678 editor.read(cx).buffer().read(cx).as_singleton()?;
18679 if singleton_buffer == buffer {
18680 Some((editor, i))
18681 } else {
18682 None
18683 }
18684 })?;
18685 pane.update(cx, |pane, cx| {
18686 pane.activate_item(pane_item_index, true, true, window, cx)
18687 });
18688 Some(editor)
18689 })
18690 .flatten()
18691 .unwrap_or_else(|| {
18692 workspace.open_project_item::<Self>(
18693 pane.clone(),
18694 buffer,
18695 true,
18696 true,
18697 window,
18698 cx,
18699 )
18700 });
18701
18702 editor.update(cx, |editor, cx| {
18703 let autoscroll = match scroll_offset {
18704 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18705 None => Autoscroll::newest(),
18706 };
18707 let nav_history = editor.nav_history.take();
18708 editor.change_selections(Some(autoscroll), window, cx, |s| {
18709 s.select_ranges(ranges);
18710 });
18711 editor.nav_history = nav_history;
18712 });
18713 }
18714 })
18715 });
18716 }
18717
18718 // For now, don't allow opening excerpts in buffers that aren't backed by
18719 // regular project files.
18720 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18721 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18722 }
18723
18724 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18725 let snapshot = self.buffer.read(cx).read(cx);
18726 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18727 Some(
18728 ranges
18729 .iter()
18730 .map(move |range| {
18731 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18732 })
18733 .collect(),
18734 )
18735 }
18736
18737 fn selection_replacement_ranges(
18738 &self,
18739 range: Range<OffsetUtf16>,
18740 cx: &mut App,
18741 ) -> Vec<Range<OffsetUtf16>> {
18742 let selections = self.selections.all::<OffsetUtf16>(cx);
18743 let newest_selection = selections
18744 .iter()
18745 .max_by_key(|selection| selection.id)
18746 .unwrap();
18747 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18748 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18749 let snapshot = self.buffer.read(cx).read(cx);
18750 selections
18751 .into_iter()
18752 .map(|mut selection| {
18753 selection.start.0 =
18754 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18755 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18756 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18757 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18758 })
18759 .collect()
18760 }
18761
18762 fn report_editor_event(
18763 &self,
18764 event_type: &'static str,
18765 file_extension: Option<String>,
18766 cx: &App,
18767 ) {
18768 if cfg!(any(test, feature = "test-support")) {
18769 return;
18770 }
18771
18772 let Some(project) = &self.project else { return };
18773
18774 // If None, we are in a file without an extension
18775 let file = self
18776 .buffer
18777 .read(cx)
18778 .as_singleton()
18779 .and_then(|b| b.read(cx).file());
18780 let file_extension = file_extension.or(file
18781 .as_ref()
18782 .and_then(|file| Path::new(file.file_name(cx)).extension())
18783 .and_then(|e| e.to_str())
18784 .map(|a| a.to_string()));
18785
18786 let vim_mode = vim_enabled(cx);
18787
18788 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18789 let copilot_enabled = edit_predictions_provider
18790 == language::language_settings::EditPredictionProvider::Copilot;
18791 let copilot_enabled_for_language = self
18792 .buffer
18793 .read(cx)
18794 .language_settings(cx)
18795 .show_edit_predictions;
18796
18797 let project = project.read(cx);
18798 telemetry::event!(
18799 event_type,
18800 file_extension,
18801 vim_mode,
18802 copilot_enabled,
18803 copilot_enabled_for_language,
18804 edit_predictions_provider,
18805 is_via_ssh = project.is_via_ssh(),
18806 );
18807 }
18808
18809 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18810 /// with each line being an array of {text, highlight} objects.
18811 fn copy_highlight_json(
18812 &mut self,
18813 _: &CopyHighlightJson,
18814 window: &mut Window,
18815 cx: &mut Context<Self>,
18816 ) {
18817 #[derive(Serialize)]
18818 struct Chunk<'a> {
18819 text: String,
18820 highlight: Option<&'a str>,
18821 }
18822
18823 let snapshot = self.buffer.read(cx).snapshot(cx);
18824 let range = self
18825 .selected_text_range(false, window, cx)
18826 .and_then(|selection| {
18827 if selection.range.is_empty() {
18828 None
18829 } else {
18830 Some(selection.range)
18831 }
18832 })
18833 .unwrap_or_else(|| 0..snapshot.len());
18834
18835 let chunks = snapshot.chunks(range, true);
18836 let mut lines = Vec::new();
18837 let mut line: VecDeque<Chunk> = VecDeque::new();
18838
18839 let Some(style) = self.style.as_ref() else {
18840 return;
18841 };
18842
18843 for chunk in chunks {
18844 let highlight = chunk
18845 .syntax_highlight_id
18846 .and_then(|id| id.name(&style.syntax));
18847 let mut chunk_lines = chunk.text.split('\n').peekable();
18848 while let Some(text) = chunk_lines.next() {
18849 let mut merged_with_last_token = false;
18850 if let Some(last_token) = line.back_mut() {
18851 if last_token.highlight == highlight {
18852 last_token.text.push_str(text);
18853 merged_with_last_token = true;
18854 }
18855 }
18856
18857 if !merged_with_last_token {
18858 line.push_back(Chunk {
18859 text: text.into(),
18860 highlight,
18861 });
18862 }
18863
18864 if chunk_lines.peek().is_some() {
18865 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18866 line.pop_front();
18867 }
18868 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18869 line.pop_back();
18870 }
18871
18872 lines.push(mem::take(&mut line));
18873 }
18874 }
18875 }
18876
18877 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18878 return;
18879 };
18880 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18881 }
18882
18883 pub fn open_context_menu(
18884 &mut self,
18885 _: &OpenContextMenu,
18886 window: &mut Window,
18887 cx: &mut Context<Self>,
18888 ) {
18889 self.request_autoscroll(Autoscroll::newest(), cx);
18890 let position = self.selections.newest_display(cx).start;
18891 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18892 }
18893
18894 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18895 &self.inlay_hint_cache
18896 }
18897
18898 pub fn replay_insert_event(
18899 &mut self,
18900 text: &str,
18901 relative_utf16_range: Option<Range<isize>>,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) {
18905 if !self.input_enabled {
18906 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18907 return;
18908 }
18909 if let Some(relative_utf16_range) = relative_utf16_range {
18910 let selections = self.selections.all::<OffsetUtf16>(cx);
18911 self.change_selections(None, window, cx, |s| {
18912 let new_ranges = selections.into_iter().map(|range| {
18913 let start = OffsetUtf16(
18914 range
18915 .head()
18916 .0
18917 .saturating_add_signed(relative_utf16_range.start),
18918 );
18919 let end = OffsetUtf16(
18920 range
18921 .head()
18922 .0
18923 .saturating_add_signed(relative_utf16_range.end),
18924 );
18925 start..end
18926 });
18927 s.select_ranges(new_ranges);
18928 });
18929 }
18930
18931 self.handle_input(text, window, cx);
18932 }
18933
18934 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18935 let Some(provider) = self.semantics_provider.as_ref() else {
18936 return false;
18937 };
18938
18939 let mut supports = false;
18940 self.buffer().update(cx, |this, cx| {
18941 this.for_each_buffer(|buffer| {
18942 supports |= provider.supports_inlay_hints(buffer, cx);
18943 });
18944 });
18945
18946 supports
18947 }
18948
18949 pub fn is_focused(&self, window: &Window) -> bool {
18950 self.focus_handle.is_focused(window)
18951 }
18952
18953 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18954 cx.emit(EditorEvent::Focused);
18955
18956 if let Some(descendant) = self
18957 .last_focused_descendant
18958 .take()
18959 .and_then(|descendant| descendant.upgrade())
18960 {
18961 window.focus(&descendant);
18962 } else {
18963 if let Some(blame) = self.blame.as_ref() {
18964 blame.update(cx, GitBlame::focus)
18965 }
18966
18967 self.blink_manager.update(cx, BlinkManager::enable);
18968 self.show_cursor_names(window, cx);
18969 self.buffer.update(cx, |buffer, cx| {
18970 buffer.finalize_last_transaction(cx);
18971 if self.leader_id.is_none() {
18972 buffer.set_active_selections(
18973 &self.selections.disjoint_anchors(),
18974 self.selections.line_mode,
18975 self.cursor_shape,
18976 cx,
18977 );
18978 }
18979 });
18980 }
18981 }
18982
18983 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18984 cx.emit(EditorEvent::FocusedIn)
18985 }
18986
18987 fn handle_focus_out(
18988 &mut self,
18989 event: FocusOutEvent,
18990 _window: &mut Window,
18991 cx: &mut Context<Self>,
18992 ) {
18993 if event.blurred != self.focus_handle {
18994 self.last_focused_descendant = Some(event.blurred);
18995 }
18996 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18997 }
18998
18999 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19000 self.blink_manager.update(cx, BlinkManager::disable);
19001 self.buffer
19002 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19003
19004 if let Some(blame) = self.blame.as_ref() {
19005 blame.update(cx, GitBlame::blur)
19006 }
19007 if !self.hover_state.focused(window, cx) {
19008 hide_hover(self, cx);
19009 }
19010 if !self
19011 .context_menu
19012 .borrow()
19013 .as_ref()
19014 .is_some_and(|context_menu| context_menu.focused(window, cx))
19015 {
19016 self.hide_context_menu(window, cx);
19017 }
19018 self.discard_inline_completion(false, cx);
19019 cx.emit(EditorEvent::Blurred);
19020 cx.notify();
19021 }
19022
19023 pub fn register_action<A: Action>(
19024 &mut self,
19025 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19026 ) -> Subscription {
19027 let id = self.next_editor_action_id.post_inc();
19028 let listener = Arc::new(listener);
19029 self.editor_actions.borrow_mut().insert(
19030 id,
19031 Box::new(move |window, _| {
19032 let listener = listener.clone();
19033 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19034 let action = action.downcast_ref().unwrap();
19035 if phase == DispatchPhase::Bubble {
19036 listener(action, window, cx)
19037 }
19038 })
19039 }),
19040 );
19041
19042 let editor_actions = self.editor_actions.clone();
19043 Subscription::new(move || {
19044 editor_actions.borrow_mut().remove(&id);
19045 })
19046 }
19047
19048 pub fn file_header_size(&self) -> u32 {
19049 FILE_HEADER_HEIGHT
19050 }
19051
19052 pub fn restore(
19053 &mut self,
19054 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19055 window: &mut Window,
19056 cx: &mut Context<Self>,
19057 ) {
19058 let workspace = self.workspace();
19059 let project = self.project.as_ref();
19060 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19061 let mut tasks = Vec::new();
19062 for (buffer_id, changes) in revert_changes {
19063 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19064 buffer.update(cx, |buffer, cx| {
19065 buffer.edit(
19066 changes
19067 .into_iter()
19068 .map(|(range, text)| (range, text.to_string())),
19069 None,
19070 cx,
19071 );
19072 });
19073
19074 if let Some(project) =
19075 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19076 {
19077 project.update(cx, |project, cx| {
19078 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19079 })
19080 }
19081 }
19082 }
19083 tasks
19084 });
19085 cx.spawn_in(window, async move |_, cx| {
19086 for (buffer, task) in save_tasks {
19087 let result = task.await;
19088 if result.is_err() {
19089 let Some(path) = buffer
19090 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19091 .ok()
19092 else {
19093 continue;
19094 };
19095 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19096 let Some(task) = cx
19097 .update_window_entity(&workspace, |workspace, window, cx| {
19098 workspace
19099 .open_path_preview(path, None, false, false, false, window, cx)
19100 })
19101 .ok()
19102 else {
19103 continue;
19104 };
19105 task.await.log_err();
19106 }
19107 }
19108 }
19109 })
19110 .detach();
19111 self.change_selections(None, window, cx, |selections| selections.refresh());
19112 }
19113
19114 pub fn to_pixel_point(
19115 &self,
19116 source: multi_buffer::Anchor,
19117 editor_snapshot: &EditorSnapshot,
19118 window: &mut Window,
19119 ) -> Option<gpui::Point<Pixels>> {
19120 let source_point = source.to_display_point(editor_snapshot);
19121 self.display_to_pixel_point(source_point, editor_snapshot, window)
19122 }
19123
19124 pub fn display_to_pixel_point(
19125 &self,
19126 source: DisplayPoint,
19127 editor_snapshot: &EditorSnapshot,
19128 window: &mut Window,
19129 ) -> Option<gpui::Point<Pixels>> {
19130 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19131 let text_layout_details = self.text_layout_details(window);
19132 let scroll_top = text_layout_details
19133 .scroll_anchor
19134 .scroll_position(editor_snapshot)
19135 .y;
19136
19137 if source.row().as_f32() < scroll_top.floor() {
19138 return None;
19139 }
19140 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19141 let source_y = line_height * (source.row().as_f32() - scroll_top);
19142 Some(gpui::Point::new(source_x, source_y))
19143 }
19144
19145 pub fn has_visible_completions_menu(&self) -> bool {
19146 !self.edit_prediction_preview_is_active()
19147 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19148 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19149 })
19150 }
19151
19152 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19153 if self.mode.is_minimap() {
19154 return;
19155 }
19156 self.addons
19157 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19158 }
19159
19160 pub fn unregister_addon<T: Addon>(&mut self) {
19161 self.addons.remove(&std::any::TypeId::of::<T>());
19162 }
19163
19164 pub fn addon<T: Addon>(&self) -> Option<&T> {
19165 let type_id = std::any::TypeId::of::<T>();
19166 self.addons
19167 .get(&type_id)
19168 .and_then(|item| item.to_any().downcast_ref::<T>())
19169 }
19170
19171 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19172 let type_id = std::any::TypeId::of::<T>();
19173 self.addons
19174 .get_mut(&type_id)
19175 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19176 }
19177
19178 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19179 let text_layout_details = self.text_layout_details(window);
19180 let style = &text_layout_details.editor_style;
19181 let font_id = window.text_system().resolve_font(&style.text.font());
19182 let font_size = style.text.font_size.to_pixels(window.rem_size());
19183 let line_height = style.text.line_height_in_pixels(window.rem_size());
19184 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19185
19186 gpui::Size::new(em_width, line_height)
19187 }
19188
19189 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19190 self.load_diff_task.clone()
19191 }
19192
19193 fn read_metadata_from_db(
19194 &mut self,
19195 item_id: u64,
19196 workspace_id: WorkspaceId,
19197 window: &mut Window,
19198 cx: &mut Context<Editor>,
19199 ) {
19200 if self.is_singleton(cx)
19201 && !self.mode.is_minimap()
19202 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19203 {
19204 let buffer_snapshot = OnceCell::new();
19205
19206 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19207 if !folds.is_empty() {
19208 let snapshot =
19209 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19210 self.fold_ranges(
19211 folds
19212 .into_iter()
19213 .map(|(start, end)| {
19214 snapshot.clip_offset(start, Bias::Left)
19215 ..snapshot.clip_offset(end, Bias::Right)
19216 })
19217 .collect(),
19218 false,
19219 window,
19220 cx,
19221 );
19222 }
19223 }
19224
19225 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19226 if !selections.is_empty() {
19227 let snapshot =
19228 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19229 self.change_selections(None, window, cx, |s| {
19230 s.select_ranges(selections.into_iter().map(|(start, end)| {
19231 snapshot.clip_offset(start, Bias::Left)
19232 ..snapshot.clip_offset(end, Bias::Right)
19233 }));
19234 });
19235 }
19236 };
19237 }
19238
19239 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19240 }
19241}
19242
19243fn vim_enabled(cx: &App) -> bool {
19244 cx.global::<SettingsStore>()
19245 .raw_user_settings()
19246 .get("vim_mode")
19247 == Some(&serde_json::Value::Bool(true))
19248}
19249
19250// Consider user intent and default settings
19251fn choose_completion_range(
19252 completion: &Completion,
19253 intent: CompletionIntent,
19254 buffer: &Entity<Buffer>,
19255 cx: &mut Context<Editor>,
19256) -> Range<usize> {
19257 fn should_replace(
19258 completion: &Completion,
19259 insert_range: &Range<text::Anchor>,
19260 intent: CompletionIntent,
19261 completion_mode_setting: LspInsertMode,
19262 buffer: &Buffer,
19263 ) -> bool {
19264 // specific actions take precedence over settings
19265 match intent {
19266 CompletionIntent::CompleteWithInsert => return false,
19267 CompletionIntent::CompleteWithReplace => return true,
19268 CompletionIntent::Complete | CompletionIntent::Compose => {}
19269 }
19270
19271 match completion_mode_setting {
19272 LspInsertMode::Insert => false,
19273 LspInsertMode::Replace => true,
19274 LspInsertMode::ReplaceSubsequence => {
19275 let mut text_to_replace = buffer.chars_for_range(
19276 buffer.anchor_before(completion.replace_range.start)
19277 ..buffer.anchor_after(completion.replace_range.end),
19278 );
19279 let mut completion_text = completion.new_text.chars();
19280
19281 // is `text_to_replace` a subsequence of `completion_text`
19282 text_to_replace
19283 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19284 }
19285 LspInsertMode::ReplaceSuffix => {
19286 let range_after_cursor = insert_range.end..completion.replace_range.end;
19287
19288 let text_after_cursor = buffer
19289 .text_for_range(
19290 buffer.anchor_before(range_after_cursor.start)
19291 ..buffer.anchor_after(range_after_cursor.end),
19292 )
19293 .collect::<String>();
19294 completion.new_text.ends_with(&text_after_cursor)
19295 }
19296 }
19297 }
19298
19299 let buffer = buffer.read(cx);
19300
19301 if let CompletionSource::Lsp {
19302 insert_range: Some(insert_range),
19303 ..
19304 } = &completion.source
19305 {
19306 let completion_mode_setting =
19307 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19308 .completions
19309 .lsp_insert_mode;
19310
19311 if !should_replace(
19312 completion,
19313 &insert_range,
19314 intent,
19315 completion_mode_setting,
19316 buffer,
19317 ) {
19318 return insert_range.to_offset(buffer);
19319 }
19320 }
19321
19322 completion.replace_range.to_offset(buffer)
19323}
19324
19325fn insert_extra_newline_brackets(
19326 buffer: &MultiBufferSnapshot,
19327 range: Range<usize>,
19328 language: &language::LanguageScope,
19329) -> bool {
19330 let leading_whitespace_len = buffer
19331 .reversed_chars_at(range.start)
19332 .take_while(|c| c.is_whitespace() && *c != '\n')
19333 .map(|c| c.len_utf8())
19334 .sum::<usize>();
19335 let trailing_whitespace_len = buffer
19336 .chars_at(range.end)
19337 .take_while(|c| c.is_whitespace() && *c != '\n')
19338 .map(|c| c.len_utf8())
19339 .sum::<usize>();
19340 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19341
19342 language.brackets().any(|(pair, enabled)| {
19343 let pair_start = pair.start.trim_end();
19344 let pair_end = pair.end.trim_start();
19345
19346 enabled
19347 && pair.newline
19348 && buffer.contains_str_at(range.end, pair_end)
19349 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19350 })
19351}
19352
19353fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19354 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19355 [(buffer, range, _)] => (*buffer, range.clone()),
19356 _ => return false,
19357 };
19358 let pair = {
19359 let mut result: Option<BracketMatch> = None;
19360
19361 for pair in buffer
19362 .all_bracket_ranges(range.clone())
19363 .filter(move |pair| {
19364 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19365 })
19366 {
19367 let len = pair.close_range.end - pair.open_range.start;
19368
19369 if let Some(existing) = &result {
19370 let existing_len = existing.close_range.end - existing.open_range.start;
19371 if len > existing_len {
19372 continue;
19373 }
19374 }
19375
19376 result = Some(pair);
19377 }
19378
19379 result
19380 };
19381 let Some(pair) = pair else {
19382 return false;
19383 };
19384 pair.newline_only
19385 && buffer
19386 .chars_for_range(pair.open_range.end..range.start)
19387 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19388 .all(|c| c.is_whitespace() && c != '\n')
19389}
19390
19391fn update_uncommitted_diff_for_buffer(
19392 editor: Entity<Editor>,
19393 project: &Entity<Project>,
19394 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19395 buffer: Entity<MultiBuffer>,
19396 cx: &mut App,
19397) -> Task<()> {
19398 let mut tasks = Vec::new();
19399 project.update(cx, |project, cx| {
19400 for buffer in buffers {
19401 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19402 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19403 }
19404 }
19405 });
19406 cx.spawn(async move |cx| {
19407 let diffs = future::join_all(tasks).await;
19408 if editor
19409 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19410 .unwrap_or(false)
19411 {
19412 return;
19413 }
19414
19415 buffer
19416 .update(cx, |buffer, cx| {
19417 for diff in diffs.into_iter().flatten() {
19418 buffer.add_diff(diff, cx);
19419 }
19420 })
19421 .ok();
19422 })
19423}
19424
19425fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19426 let tab_size = tab_size.get() as usize;
19427 let mut width = offset;
19428
19429 for ch in text.chars() {
19430 width += if ch == '\t' {
19431 tab_size - (width % tab_size)
19432 } else {
19433 1
19434 };
19435 }
19436
19437 width - offset
19438}
19439
19440#[cfg(test)]
19441mod tests {
19442 use super::*;
19443
19444 #[test]
19445 fn test_string_size_with_expanded_tabs() {
19446 let nz = |val| NonZeroU32::new(val).unwrap();
19447 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19448 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19449 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19450 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19451 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19452 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19453 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19454 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19455 }
19456}
19457
19458/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19459struct WordBreakingTokenizer<'a> {
19460 input: &'a str,
19461}
19462
19463impl<'a> WordBreakingTokenizer<'a> {
19464 fn new(input: &'a str) -> Self {
19465 Self { input }
19466 }
19467}
19468
19469fn is_char_ideographic(ch: char) -> bool {
19470 use unicode_script::Script::*;
19471 use unicode_script::UnicodeScript;
19472 matches!(ch.script(), Han | Tangut | Yi)
19473}
19474
19475fn is_grapheme_ideographic(text: &str) -> bool {
19476 text.chars().any(is_char_ideographic)
19477}
19478
19479fn is_grapheme_whitespace(text: &str) -> bool {
19480 text.chars().any(|x| x.is_whitespace())
19481}
19482
19483fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19484 text.chars().next().map_or(false, |ch| {
19485 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19486 })
19487}
19488
19489#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19490enum WordBreakToken<'a> {
19491 Word { token: &'a str, grapheme_len: usize },
19492 InlineWhitespace { token: &'a str, grapheme_len: usize },
19493 Newline,
19494}
19495
19496impl<'a> Iterator for WordBreakingTokenizer<'a> {
19497 /// Yields a span, the count of graphemes in the token, and whether it was
19498 /// whitespace. Note that it also breaks at word boundaries.
19499 type Item = WordBreakToken<'a>;
19500
19501 fn next(&mut self) -> Option<Self::Item> {
19502 use unicode_segmentation::UnicodeSegmentation;
19503 if self.input.is_empty() {
19504 return None;
19505 }
19506
19507 let mut iter = self.input.graphemes(true).peekable();
19508 let mut offset = 0;
19509 let mut grapheme_len = 0;
19510 if let Some(first_grapheme) = iter.next() {
19511 let is_newline = first_grapheme == "\n";
19512 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19513 offset += first_grapheme.len();
19514 grapheme_len += 1;
19515 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19516 if let Some(grapheme) = iter.peek().copied() {
19517 if should_stay_with_preceding_ideograph(grapheme) {
19518 offset += grapheme.len();
19519 grapheme_len += 1;
19520 }
19521 }
19522 } else {
19523 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19524 let mut next_word_bound = words.peek().copied();
19525 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19526 next_word_bound = words.next();
19527 }
19528 while let Some(grapheme) = iter.peek().copied() {
19529 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19530 break;
19531 };
19532 if is_grapheme_whitespace(grapheme) != is_whitespace
19533 || (grapheme == "\n") != is_newline
19534 {
19535 break;
19536 };
19537 offset += grapheme.len();
19538 grapheme_len += 1;
19539 iter.next();
19540 }
19541 }
19542 let token = &self.input[..offset];
19543 self.input = &self.input[offset..];
19544 if token == "\n" {
19545 Some(WordBreakToken::Newline)
19546 } else if is_whitespace {
19547 Some(WordBreakToken::InlineWhitespace {
19548 token,
19549 grapheme_len,
19550 })
19551 } else {
19552 Some(WordBreakToken::Word {
19553 token,
19554 grapheme_len,
19555 })
19556 }
19557 } else {
19558 None
19559 }
19560 }
19561}
19562
19563#[test]
19564fn test_word_breaking_tokenizer() {
19565 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19566 ("", &[]),
19567 (" ", &[whitespace(" ", 2)]),
19568 ("Ʒ", &[word("Ʒ", 1)]),
19569 ("Ǽ", &[word("Ǽ", 1)]),
19570 ("⋑", &[word("⋑", 1)]),
19571 ("⋑⋑", &[word("⋑⋑", 2)]),
19572 (
19573 "原理,进而",
19574 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19575 ),
19576 (
19577 "hello world",
19578 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19579 ),
19580 (
19581 "hello, world",
19582 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19583 ),
19584 (
19585 " hello world",
19586 &[
19587 whitespace(" ", 2),
19588 word("hello", 5),
19589 whitespace(" ", 1),
19590 word("world", 5),
19591 ],
19592 ),
19593 (
19594 "这是什么 \n 钢笔",
19595 &[
19596 word("这", 1),
19597 word("是", 1),
19598 word("什", 1),
19599 word("么", 1),
19600 whitespace(" ", 1),
19601 newline(),
19602 whitespace(" ", 1),
19603 word("钢", 1),
19604 word("笔", 1),
19605 ],
19606 ),
19607 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19608 ];
19609
19610 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19611 WordBreakToken::Word {
19612 token,
19613 grapheme_len,
19614 }
19615 }
19616
19617 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19618 WordBreakToken::InlineWhitespace {
19619 token,
19620 grapheme_len,
19621 }
19622 }
19623
19624 fn newline() -> WordBreakToken<'static> {
19625 WordBreakToken::Newline
19626 }
19627
19628 for (input, result) in tests {
19629 assert_eq!(
19630 WordBreakingTokenizer::new(input)
19631 .collect::<Vec<_>>()
19632 .as_slice(),
19633 *result,
19634 );
19635 }
19636}
19637
19638fn wrap_with_prefix(
19639 line_prefix: String,
19640 unwrapped_text: String,
19641 wrap_column: usize,
19642 tab_size: NonZeroU32,
19643 preserve_existing_whitespace: bool,
19644) -> String {
19645 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19646 let mut wrapped_text = String::new();
19647 let mut current_line = line_prefix.clone();
19648
19649 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19650 let mut current_line_len = line_prefix_len;
19651 let mut in_whitespace = false;
19652 for token in tokenizer {
19653 let have_preceding_whitespace = in_whitespace;
19654 match token {
19655 WordBreakToken::Word {
19656 token,
19657 grapheme_len,
19658 } => {
19659 in_whitespace = false;
19660 if current_line_len + grapheme_len > wrap_column
19661 && current_line_len != line_prefix_len
19662 {
19663 wrapped_text.push_str(current_line.trim_end());
19664 wrapped_text.push('\n');
19665 current_line.truncate(line_prefix.len());
19666 current_line_len = line_prefix_len;
19667 }
19668 current_line.push_str(token);
19669 current_line_len += grapheme_len;
19670 }
19671 WordBreakToken::InlineWhitespace {
19672 mut token,
19673 mut grapheme_len,
19674 } => {
19675 in_whitespace = true;
19676 if have_preceding_whitespace && !preserve_existing_whitespace {
19677 continue;
19678 }
19679 if !preserve_existing_whitespace {
19680 token = " ";
19681 grapheme_len = 1;
19682 }
19683 if current_line_len + grapheme_len > wrap_column {
19684 wrapped_text.push_str(current_line.trim_end());
19685 wrapped_text.push('\n');
19686 current_line.truncate(line_prefix.len());
19687 current_line_len = line_prefix_len;
19688 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19689 current_line.push_str(token);
19690 current_line_len += grapheme_len;
19691 }
19692 }
19693 WordBreakToken::Newline => {
19694 in_whitespace = true;
19695 if preserve_existing_whitespace {
19696 wrapped_text.push_str(current_line.trim_end());
19697 wrapped_text.push('\n');
19698 current_line.truncate(line_prefix.len());
19699 current_line_len = line_prefix_len;
19700 } else if have_preceding_whitespace {
19701 continue;
19702 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19703 {
19704 wrapped_text.push_str(current_line.trim_end());
19705 wrapped_text.push('\n');
19706 current_line.truncate(line_prefix.len());
19707 current_line_len = line_prefix_len;
19708 } else if current_line_len != line_prefix_len {
19709 current_line.push(' ');
19710 current_line_len += 1;
19711 }
19712 }
19713 }
19714 }
19715
19716 if !current_line.is_empty() {
19717 wrapped_text.push_str(¤t_line);
19718 }
19719 wrapped_text
19720}
19721
19722#[test]
19723fn test_wrap_with_prefix() {
19724 assert_eq!(
19725 wrap_with_prefix(
19726 "# ".to_string(),
19727 "abcdefg".to_string(),
19728 4,
19729 NonZeroU32::new(4).unwrap(),
19730 false,
19731 ),
19732 "# abcdefg"
19733 );
19734 assert_eq!(
19735 wrap_with_prefix(
19736 "".to_string(),
19737 "\thello world".to_string(),
19738 8,
19739 NonZeroU32::new(4).unwrap(),
19740 false,
19741 ),
19742 "hello\nworld"
19743 );
19744 assert_eq!(
19745 wrap_with_prefix(
19746 "// ".to_string(),
19747 "xx \nyy zz aa bb cc".to_string(),
19748 12,
19749 NonZeroU32::new(4).unwrap(),
19750 false,
19751 ),
19752 "// xx yy zz\n// aa bb cc"
19753 );
19754 assert_eq!(
19755 wrap_with_prefix(
19756 String::new(),
19757 "这是什么 \n 钢笔".to_string(),
19758 3,
19759 NonZeroU32::new(4).unwrap(),
19760 false,
19761 ),
19762 "这是什\n么 钢\n笔"
19763 );
19764}
19765
19766pub trait CollaborationHub {
19767 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19768 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19769 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19770}
19771
19772impl CollaborationHub for Entity<Project> {
19773 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19774 self.read(cx).collaborators()
19775 }
19776
19777 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19778 self.read(cx).user_store().read(cx).participant_indices()
19779 }
19780
19781 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19782 let this = self.read(cx);
19783 let user_ids = this.collaborators().values().map(|c| c.user_id);
19784 this.user_store().read_with(cx, |user_store, cx| {
19785 user_store.participant_names(user_ids, cx)
19786 })
19787 }
19788}
19789
19790pub trait SemanticsProvider {
19791 fn hover(
19792 &self,
19793 buffer: &Entity<Buffer>,
19794 position: text::Anchor,
19795 cx: &mut App,
19796 ) -> Option<Task<Vec<project::Hover>>>;
19797
19798 fn inline_values(
19799 &self,
19800 buffer_handle: Entity<Buffer>,
19801 range: Range<text::Anchor>,
19802 cx: &mut App,
19803 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19804
19805 fn inlay_hints(
19806 &self,
19807 buffer_handle: Entity<Buffer>,
19808 range: Range<text::Anchor>,
19809 cx: &mut App,
19810 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19811
19812 fn resolve_inlay_hint(
19813 &self,
19814 hint: InlayHint,
19815 buffer_handle: Entity<Buffer>,
19816 server_id: LanguageServerId,
19817 cx: &mut App,
19818 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19819
19820 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19821
19822 fn document_highlights(
19823 &self,
19824 buffer: &Entity<Buffer>,
19825 position: text::Anchor,
19826 cx: &mut App,
19827 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19828
19829 fn definitions(
19830 &self,
19831 buffer: &Entity<Buffer>,
19832 position: text::Anchor,
19833 kind: GotoDefinitionKind,
19834 cx: &mut App,
19835 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19836
19837 fn range_for_rename(
19838 &self,
19839 buffer: &Entity<Buffer>,
19840 position: text::Anchor,
19841 cx: &mut App,
19842 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19843
19844 fn perform_rename(
19845 &self,
19846 buffer: &Entity<Buffer>,
19847 position: text::Anchor,
19848 new_name: String,
19849 cx: &mut App,
19850 ) -> Option<Task<Result<ProjectTransaction>>>;
19851}
19852
19853pub trait CompletionProvider {
19854 fn completions(
19855 &self,
19856 excerpt_id: ExcerptId,
19857 buffer: &Entity<Buffer>,
19858 buffer_position: text::Anchor,
19859 trigger: CompletionContext,
19860 window: &mut Window,
19861 cx: &mut Context<Editor>,
19862 ) -> Task<Result<Option<Vec<Completion>>>>;
19863
19864 fn resolve_completions(
19865 &self,
19866 buffer: Entity<Buffer>,
19867 completion_indices: Vec<usize>,
19868 completions: Rc<RefCell<Box<[Completion]>>>,
19869 cx: &mut Context<Editor>,
19870 ) -> Task<Result<bool>>;
19871
19872 fn apply_additional_edits_for_completion(
19873 &self,
19874 _buffer: Entity<Buffer>,
19875 _completions: Rc<RefCell<Box<[Completion]>>>,
19876 _completion_index: usize,
19877 _push_to_history: bool,
19878 _cx: &mut Context<Editor>,
19879 ) -> Task<Result<Option<language::Transaction>>> {
19880 Task::ready(Ok(None))
19881 }
19882
19883 fn is_completion_trigger(
19884 &self,
19885 buffer: &Entity<Buffer>,
19886 position: language::Anchor,
19887 text: &str,
19888 trigger_in_words: bool,
19889 cx: &mut Context<Editor>,
19890 ) -> bool;
19891
19892 fn sort_completions(&self) -> bool {
19893 true
19894 }
19895
19896 fn filter_completions(&self) -> bool {
19897 true
19898 }
19899}
19900
19901pub trait CodeActionProvider {
19902 fn id(&self) -> Arc<str>;
19903
19904 fn code_actions(
19905 &self,
19906 buffer: &Entity<Buffer>,
19907 range: Range<text::Anchor>,
19908 window: &mut Window,
19909 cx: &mut App,
19910 ) -> Task<Result<Vec<CodeAction>>>;
19911
19912 fn apply_code_action(
19913 &self,
19914 buffer_handle: Entity<Buffer>,
19915 action: CodeAction,
19916 excerpt_id: ExcerptId,
19917 push_to_history: bool,
19918 window: &mut Window,
19919 cx: &mut App,
19920 ) -> Task<Result<ProjectTransaction>>;
19921}
19922
19923impl CodeActionProvider for Entity<Project> {
19924 fn id(&self) -> Arc<str> {
19925 "project".into()
19926 }
19927
19928 fn code_actions(
19929 &self,
19930 buffer: &Entity<Buffer>,
19931 range: Range<text::Anchor>,
19932 _window: &mut Window,
19933 cx: &mut App,
19934 ) -> Task<Result<Vec<CodeAction>>> {
19935 self.update(cx, |project, cx| {
19936 let code_lens = project.code_lens(buffer, range.clone(), cx);
19937 let code_actions = project.code_actions(buffer, range, None, cx);
19938 cx.background_spawn(async move {
19939 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19940 Ok(code_lens
19941 .context("code lens fetch")?
19942 .into_iter()
19943 .chain(code_actions.context("code action fetch")?)
19944 .collect())
19945 })
19946 })
19947 }
19948
19949 fn apply_code_action(
19950 &self,
19951 buffer_handle: Entity<Buffer>,
19952 action: CodeAction,
19953 _excerpt_id: ExcerptId,
19954 push_to_history: bool,
19955 _window: &mut Window,
19956 cx: &mut App,
19957 ) -> Task<Result<ProjectTransaction>> {
19958 self.update(cx, |project, cx| {
19959 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19960 })
19961 }
19962}
19963
19964fn snippet_completions(
19965 project: &Project,
19966 buffer: &Entity<Buffer>,
19967 buffer_position: text::Anchor,
19968 cx: &mut App,
19969) -> Task<Result<Vec<Completion>>> {
19970 let languages = buffer.read(cx).languages_at(buffer_position);
19971 let snippet_store = project.snippets().read(cx);
19972
19973 let scopes: Vec<_> = languages
19974 .iter()
19975 .filter_map(|language| {
19976 let language_name = language.lsp_id();
19977 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19978
19979 if snippets.is_empty() {
19980 None
19981 } else {
19982 Some((language.default_scope(), snippets))
19983 }
19984 })
19985 .collect();
19986
19987 if scopes.is_empty() {
19988 return Task::ready(Ok(vec![]));
19989 }
19990
19991 let snapshot = buffer.read(cx).text_snapshot();
19992 let chars: String = snapshot
19993 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19994 .collect();
19995 let executor = cx.background_executor().clone();
19996
19997 cx.background_spawn(async move {
19998 let mut all_results: Vec<Completion> = Vec::new();
19999 for (scope, snippets) in scopes.into_iter() {
20000 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20001 let mut last_word = chars
20002 .chars()
20003 .take_while(|c| classifier.is_word(*c))
20004 .collect::<String>();
20005 last_word = last_word.chars().rev().collect();
20006
20007 if last_word.is_empty() {
20008 return Ok(vec![]);
20009 }
20010
20011 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20012 let to_lsp = |point: &text::Anchor| {
20013 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20014 point_to_lsp(end)
20015 };
20016 let lsp_end = to_lsp(&buffer_position);
20017
20018 let candidates = snippets
20019 .iter()
20020 .enumerate()
20021 .flat_map(|(ix, snippet)| {
20022 snippet
20023 .prefix
20024 .iter()
20025 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20026 })
20027 .collect::<Vec<StringMatchCandidate>>();
20028
20029 let mut matches = fuzzy::match_strings(
20030 &candidates,
20031 &last_word,
20032 last_word.chars().any(|c| c.is_uppercase()),
20033 100,
20034 &Default::default(),
20035 executor.clone(),
20036 )
20037 .await;
20038
20039 // Remove all candidates where the query's start does not match the start of any word in the candidate
20040 if let Some(query_start) = last_word.chars().next() {
20041 matches.retain(|string_match| {
20042 split_words(&string_match.string).any(|word| {
20043 // Check that the first codepoint of the word as lowercase matches the first
20044 // codepoint of the query as lowercase
20045 word.chars()
20046 .flat_map(|codepoint| codepoint.to_lowercase())
20047 .zip(query_start.to_lowercase())
20048 .all(|(word_cp, query_cp)| word_cp == query_cp)
20049 })
20050 });
20051 }
20052
20053 let matched_strings = matches
20054 .into_iter()
20055 .map(|m| m.string)
20056 .collect::<HashSet<_>>();
20057
20058 let mut result: Vec<Completion> = snippets
20059 .iter()
20060 .filter_map(|snippet| {
20061 let matching_prefix = snippet
20062 .prefix
20063 .iter()
20064 .find(|prefix| matched_strings.contains(*prefix))?;
20065 let start = as_offset - last_word.len();
20066 let start = snapshot.anchor_before(start);
20067 let range = start..buffer_position;
20068 let lsp_start = to_lsp(&start);
20069 let lsp_range = lsp::Range {
20070 start: lsp_start,
20071 end: lsp_end,
20072 };
20073 Some(Completion {
20074 replace_range: range,
20075 new_text: snippet.body.clone(),
20076 source: CompletionSource::Lsp {
20077 insert_range: None,
20078 server_id: LanguageServerId(usize::MAX),
20079 resolved: true,
20080 lsp_completion: Box::new(lsp::CompletionItem {
20081 label: snippet.prefix.first().unwrap().clone(),
20082 kind: Some(CompletionItemKind::SNIPPET),
20083 label_details: snippet.description.as_ref().map(|description| {
20084 lsp::CompletionItemLabelDetails {
20085 detail: Some(description.clone()),
20086 description: None,
20087 }
20088 }),
20089 insert_text_format: Some(InsertTextFormat::SNIPPET),
20090 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20091 lsp::InsertReplaceEdit {
20092 new_text: snippet.body.clone(),
20093 insert: lsp_range,
20094 replace: lsp_range,
20095 },
20096 )),
20097 filter_text: Some(snippet.body.clone()),
20098 sort_text: Some(char::MAX.to_string()),
20099 ..lsp::CompletionItem::default()
20100 }),
20101 lsp_defaults: None,
20102 },
20103 label: CodeLabel {
20104 text: matching_prefix.clone(),
20105 runs: Vec::new(),
20106 filter_range: 0..matching_prefix.len(),
20107 },
20108 icon_path: None,
20109 documentation: Some(
20110 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20111 single_line: snippet.name.clone().into(),
20112 plain_text: snippet
20113 .description
20114 .clone()
20115 .map(|description| description.into()),
20116 },
20117 ),
20118 insert_text_mode: None,
20119 confirm: None,
20120 })
20121 })
20122 .collect();
20123
20124 all_results.append(&mut result);
20125 }
20126
20127 Ok(all_results)
20128 })
20129}
20130
20131impl CompletionProvider for Entity<Project> {
20132 fn completions(
20133 &self,
20134 _excerpt_id: ExcerptId,
20135 buffer: &Entity<Buffer>,
20136 buffer_position: text::Anchor,
20137 options: CompletionContext,
20138 _window: &mut Window,
20139 cx: &mut Context<Editor>,
20140 ) -> Task<Result<Option<Vec<Completion>>>> {
20141 self.update(cx, |project, cx| {
20142 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20143 let project_completions = project.completions(buffer, buffer_position, options, cx);
20144 cx.background_spawn(async move {
20145 let snippets_completions = snippets.await?;
20146 match project_completions.await? {
20147 Some(mut completions) => {
20148 completions.extend(snippets_completions);
20149 Ok(Some(completions))
20150 }
20151 None => {
20152 if snippets_completions.is_empty() {
20153 Ok(None)
20154 } else {
20155 Ok(Some(snippets_completions))
20156 }
20157 }
20158 }
20159 })
20160 })
20161 }
20162
20163 fn resolve_completions(
20164 &self,
20165 buffer: Entity<Buffer>,
20166 completion_indices: Vec<usize>,
20167 completions: Rc<RefCell<Box<[Completion]>>>,
20168 cx: &mut Context<Editor>,
20169 ) -> Task<Result<bool>> {
20170 self.update(cx, |project, cx| {
20171 project.lsp_store().update(cx, |lsp_store, cx| {
20172 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20173 })
20174 })
20175 }
20176
20177 fn apply_additional_edits_for_completion(
20178 &self,
20179 buffer: Entity<Buffer>,
20180 completions: Rc<RefCell<Box<[Completion]>>>,
20181 completion_index: usize,
20182 push_to_history: bool,
20183 cx: &mut Context<Editor>,
20184 ) -> Task<Result<Option<language::Transaction>>> {
20185 self.update(cx, |project, cx| {
20186 project.lsp_store().update(cx, |lsp_store, cx| {
20187 lsp_store.apply_additional_edits_for_completion(
20188 buffer,
20189 completions,
20190 completion_index,
20191 push_to_history,
20192 cx,
20193 )
20194 })
20195 })
20196 }
20197
20198 fn is_completion_trigger(
20199 &self,
20200 buffer: &Entity<Buffer>,
20201 position: language::Anchor,
20202 text: &str,
20203 trigger_in_words: bool,
20204 cx: &mut Context<Editor>,
20205 ) -> bool {
20206 let mut chars = text.chars();
20207 let char = if let Some(char) = chars.next() {
20208 char
20209 } else {
20210 return false;
20211 };
20212 if chars.next().is_some() {
20213 return false;
20214 }
20215
20216 let buffer = buffer.read(cx);
20217 let snapshot = buffer.snapshot();
20218 if !snapshot.settings_at(position, cx).show_completions_on_input {
20219 return false;
20220 }
20221 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20222 if trigger_in_words && classifier.is_word(char) {
20223 return true;
20224 }
20225
20226 buffer.completion_triggers().contains(text)
20227 }
20228}
20229
20230impl SemanticsProvider for Entity<Project> {
20231 fn hover(
20232 &self,
20233 buffer: &Entity<Buffer>,
20234 position: text::Anchor,
20235 cx: &mut App,
20236 ) -> Option<Task<Vec<project::Hover>>> {
20237 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20238 }
20239
20240 fn document_highlights(
20241 &self,
20242 buffer: &Entity<Buffer>,
20243 position: text::Anchor,
20244 cx: &mut App,
20245 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20246 Some(self.update(cx, |project, cx| {
20247 project.document_highlights(buffer, position, cx)
20248 }))
20249 }
20250
20251 fn definitions(
20252 &self,
20253 buffer: &Entity<Buffer>,
20254 position: text::Anchor,
20255 kind: GotoDefinitionKind,
20256 cx: &mut App,
20257 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20258 Some(self.update(cx, |project, cx| match kind {
20259 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20260 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20261 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20262 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20263 }))
20264 }
20265
20266 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20267 // TODO: make this work for remote projects
20268 self.update(cx, |project, cx| {
20269 if project
20270 .active_debug_session(cx)
20271 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20272 {
20273 return true;
20274 }
20275
20276 buffer.update(cx, |buffer, cx| {
20277 project.any_language_server_supports_inlay_hints(buffer, cx)
20278 })
20279 })
20280 }
20281
20282 fn inline_values(
20283 &self,
20284 buffer_handle: Entity<Buffer>,
20285
20286 range: Range<text::Anchor>,
20287 cx: &mut App,
20288 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20289 self.update(cx, |project, cx| {
20290 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20291
20292 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20293 })
20294 }
20295
20296 fn inlay_hints(
20297 &self,
20298 buffer_handle: Entity<Buffer>,
20299 range: Range<text::Anchor>,
20300 cx: &mut App,
20301 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20302 Some(self.update(cx, |project, cx| {
20303 project.inlay_hints(buffer_handle, range, cx)
20304 }))
20305 }
20306
20307 fn resolve_inlay_hint(
20308 &self,
20309 hint: InlayHint,
20310 buffer_handle: Entity<Buffer>,
20311 server_id: LanguageServerId,
20312 cx: &mut App,
20313 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20314 Some(self.update(cx, |project, cx| {
20315 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20316 }))
20317 }
20318
20319 fn range_for_rename(
20320 &self,
20321 buffer: &Entity<Buffer>,
20322 position: text::Anchor,
20323 cx: &mut App,
20324 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20325 Some(self.update(cx, |project, cx| {
20326 let buffer = buffer.clone();
20327 let task = project.prepare_rename(buffer.clone(), position, cx);
20328 cx.spawn(async move |_, cx| {
20329 Ok(match task.await? {
20330 PrepareRenameResponse::Success(range) => Some(range),
20331 PrepareRenameResponse::InvalidPosition => None,
20332 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20333 // Fallback on using TreeSitter info to determine identifier range
20334 buffer.update(cx, |buffer, _| {
20335 let snapshot = buffer.snapshot();
20336 let (range, kind) = snapshot.surrounding_word(position);
20337 if kind != Some(CharKind::Word) {
20338 return None;
20339 }
20340 Some(
20341 snapshot.anchor_before(range.start)
20342 ..snapshot.anchor_after(range.end),
20343 )
20344 })?
20345 }
20346 })
20347 })
20348 }))
20349 }
20350
20351 fn perform_rename(
20352 &self,
20353 buffer: &Entity<Buffer>,
20354 position: text::Anchor,
20355 new_name: String,
20356 cx: &mut App,
20357 ) -> Option<Task<Result<ProjectTransaction>>> {
20358 Some(self.update(cx, |project, cx| {
20359 project.perform_rename(buffer.clone(), position, new_name, cx)
20360 }))
20361 }
20362}
20363
20364fn inlay_hint_settings(
20365 location: Anchor,
20366 snapshot: &MultiBufferSnapshot,
20367 cx: &mut Context<Editor>,
20368) -> InlayHintSettings {
20369 let file = snapshot.file_at(location);
20370 let language = snapshot.language_at(location).map(|l| l.name());
20371 language_settings(language, file, cx).inlay_hints
20372}
20373
20374fn consume_contiguous_rows(
20375 contiguous_row_selections: &mut Vec<Selection<Point>>,
20376 selection: &Selection<Point>,
20377 display_map: &DisplaySnapshot,
20378 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20379) -> (MultiBufferRow, MultiBufferRow) {
20380 contiguous_row_selections.push(selection.clone());
20381 let start_row = MultiBufferRow(selection.start.row);
20382 let mut end_row = ending_row(selection, display_map);
20383
20384 while let Some(next_selection) = selections.peek() {
20385 if next_selection.start.row <= end_row.0 {
20386 end_row = ending_row(next_selection, display_map);
20387 contiguous_row_selections.push(selections.next().unwrap().clone());
20388 } else {
20389 break;
20390 }
20391 }
20392 (start_row, end_row)
20393}
20394
20395fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20396 if next_selection.end.column > 0 || next_selection.is_empty() {
20397 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20398 } else {
20399 MultiBufferRow(next_selection.end.row)
20400 }
20401}
20402
20403impl EditorSnapshot {
20404 pub fn remote_selections_in_range<'a>(
20405 &'a self,
20406 range: &'a Range<Anchor>,
20407 collaboration_hub: &dyn CollaborationHub,
20408 cx: &'a App,
20409 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20410 let participant_names = collaboration_hub.user_names(cx);
20411 let participant_indices = collaboration_hub.user_participant_indices(cx);
20412 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20413 let collaborators_by_replica_id = collaborators_by_peer_id
20414 .values()
20415 .map(|collaborator| (collaborator.replica_id, collaborator))
20416 .collect::<HashMap<_, _>>();
20417 self.buffer_snapshot
20418 .selections_in_range(range, false)
20419 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20420 if replica_id == AGENT_REPLICA_ID {
20421 Some(RemoteSelection {
20422 replica_id,
20423 selection,
20424 cursor_shape,
20425 line_mode,
20426 collaborator_id: CollaboratorId::Agent,
20427 user_name: Some("Agent".into()),
20428 color: cx.theme().players().agent(),
20429 })
20430 } else {
20431 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20432 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20433 let user_name = participant_names.get(&collaborator.user_id).cloned();
20434 Some(RemoteSelection {
20435 replica_id,
20436 selection,
20437 cursor_shape,
20438 line_mode,
20439 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20440 user_name,
20441 color: if let Some(index) = participant_index {
20442 cx.theme().players().color_for_participant(index.0)
20443 } else {
20444 cx.theme().players().absent()
20445 },
20446 })
20447 }
20448 })
20449 }
20450
20451 pub fn hunks_for_ranges(
20452 &self,
20453 ranges: impl IntoIterator<Item = Range<Point>>,
20454 ) -> Vec<MultiBufferDiffHunk> {
20455 let mut hunks = Vec::new();
20456 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20457 HashMap::default();
20458 for query_range in ranges {
20459 let query_rows =
20460 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20461 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20462 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20463 ) {
20464 // Include deleted hunks that are adjacent to the query range, because
20465 // otherwise they would be missed.
20466 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20467 if hunk.status().is_deleted() {
20468 intersects_range |= hunk.row_range.start == query_rows.end;
20469 intersects_range |= hunk.row_range.end == query_rows.start;
20470 }
20471 if intersects_range {
20472 if !processed_buffer_rows
20473 .entry(hunk.buffer_id)
20474 .or_default()
20475 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20476 {
20477 continue;
20478 }
20479 hunks.push(hunk);
20480 }
20481 }
20482 }
20483
20484 hunks
20485 }
20486
20487 fn display_diff_hunks_for_rows<'a>(
20488 &'a self,
20489 display_rows: Range<DisplayRow>,
20490 folded_buffers: &'a HashSet<BufferId>,
20491 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20492 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20493 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20494
20495 self.buffer_snapshot
20496 .diff_hunks_in_range(buffer_start..buffer_end)
20497 .filter_map(|hunk| {
20498 if folded_buffers.contains(&hunk.buffer_id) {
20499 return None;
20500 }
20501
20502 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20503 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20504
20505 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20506 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20507
20508 let display_hunk = if hunk_display_start.column() != 0 {
20509 DisplayDiffHunk::Folded {
20510 display_row: hunk_display_start.row(),
20511 }
20512 } else {
20513 let mut end_row = hunk_display_end.row();
20514 if hunk_display_end.column() > 0 {
20515 end_row.0 += 1;
20516 }
20517 let is_created_file = hunk.is_created_file();
20518 DisplayDiffHunk::Unfolded {
20519 status: hunk.status(),
20520 diff_base_byte_range: hunk.diff_base_byte_range,
20521 display_row_range: hunk_display_start.row()..end_row,
20522 multi_buffer_range: Anchor::range_in_buffer(
20523 hunk.excerpt_id,
20524 hunk.buffer_id,
20525 hunk.buffer_range,
20526 ),
20527 is_created_file,
20528 }
20529 };
20530
20531 Some(display_hunk)
20532 })
20533 }
20534
20535 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20536 self.display_snapshot.buffer_snapshot.language_at(position)
20537 }
20538
20539 pub fn is_focused(&self) -> bool {
20540 self.is_focused
20541 }
20542
20543 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20544 self.placeholder_text.as_ref()
20545 }
20546
20547 pub fn scroll_position(&self) -> gpui::Point<f32> {
20548 self.scroll_anchor.scroll_position(&self.display_snapshot)
20549 }
20550
20551 fn gutter_dimensions(
20552 &self,
20553 font_id: FontId,
20554 font_size: Pixels,
20555 max_line_number_width: Pixels,
20556 cx: &App,
20557 ) -> Option<GutterDimensions> {
20558 if !self.show_gutter {
20559 return None;
20560 }
20561
20562 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20563 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20564
20565 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20566 matches!(
20567 ProjectSettings::get_global(cx).git.git_gutter,
20568 Some(GitGutterSetting::TrackedFiles)
20569 )
20570 });
20571 let gutter_settings = EditorSettings::get_global(cx).gutter;
20572 let show_line_numbers = self
20573 .show_line_numbers
20574 .unwrap_or(gutter_settings.line_numbers);
20575 let line_gutter_width = if show_line_numbers {
20576 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20577 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20578 max_line_number_width.max(min_width_for_number_on_gutter)
20579 } else {
20580 0.0.into()
20581 };
20582
20583 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20584 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20585
20586 let git_blame_entries_width =
20587 self.git_blame_gutter_max_author_length
20588 .map(|max_author_length| {
20589 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20590 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20591
20592 /// The number of characters to dedicate to gaps and margins.
20593 const SPACING_WIDTH: usize = 4;
20594
20595 let max_char_count = max_author_length.min(renderer.max_author_length())
20596 + ::git::SHORT_SHA_LENGTH
20597 + MAX_RELATIVE_TIMESTAMP.len()
20598 + SPACING_WIDTH;
20599
20600 em_advance * max_char_count
20601 });
20602
20603 let is_singleton = self.buffer_snapshot.is_singleton();
20604
20605 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20606 left_padding += if !is_singleton {
20607 em_width * 4.0
20608 } else if show_runnables || show_breakpoints {
20609 em_width * 3.0
20610 } else if show_git_gutter && show_line_numbers {
20611 em_width * 2.0
20612 } else if show_git_gutter || show_line_numbers {
20613 em_width
20614 } else {
20615 px(0.)
20616 };
20617
20618 let shows_folds = is_singleton && gutter_settings.folds;
20619
20620 let right_padding = if shows_folds && show_line_numbers {
20621 em_width * 4.0
20622 } else if shows_folds || (!is_singleton && show_line_numbers) {
20623 em_width * 3.0
20624 } else if show_line_numbers {
20625 em_width
20626 } else {
20627 px(0.)
20628 };
20629
20630 Some(GutterDimensions {
20631 left_padding,
20632 right_padding,
20633 width: line_gutter_width + left_padding + right_padding,
20634 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20635 git_blame_entries_width,
20636 })
20637 }
20638
20639 pub fn render_crease_toggle(
20640 &self,
20641 buffer_row: MultiBufferRow,
20642 row_contains_cursor: bool,
20643 editor: Entity<Editor>,
20644 window: &mut Window,
20645 cx: &mut App,
20646 ) -> Option<AnyElement> {
20647 let folded = self.is_line_folded(buffer_row);
20648 let mut is_foldable = false;
20649
20650 if let Some(crease) = self
20651 .crease_snapshot
20652 .query_row(buffer_row, &self.buffer_snapshot)
20653 {
20654 is_foldable = true;
20655 match crease {
20656 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20657 if let Some(render_toggle) = render_toggle {
20658 let toggle_callback =
20659 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20660 if folded {
20661 editor.update(cx, |editor, cx| {
20662 editor.fold_at(buffer_row, window, cx)
20663 });
20664 } else {
20665 editor.update(cx, |editor, cx| {
20666 editor.unfold_at(buffer_row, window, cx)
20667 });
20668 }
20669 });
20670 return Some((render_toggle)(
20671 buffer_row,
20672 folded,
20673 toggle_callback,
20674 window,
20675 cx,
20676 ));
20677 }
20678 }
20679 }
20680 }
20681
20682 is_foldable |= self.starts_indent(buffer_row);
20683
20684 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20685 Some(
20686 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20687 .toggle_state(folded)
20688 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20689 if folded {
20690 this.unfold_at(buffer_row, window, cx);
20691 } else {
20692 this.fold_at(buffer_row, window, cx);
20693 }
20694 }))
20695 .into_any_element(),
20696 )
20697 } else {
20698 None
20699 }
20700 }
20701
20702 pub fn render_crease_trailer(
20703 &self,
20704 buffer_row: MultiBufferRow,
20705 window: &mut Window,
20706 cx: &mut App,
20707 ) -> Option<AnyElement> {
20708 let folded = self.is_line_folded(buffer_row);
20709 if let Crease::Inline { render_trailer, .. } = self
20710 .crease_snapshot
20711 .query_row(buffer_row, &self.buffer_snapshot)?
20712 {
20713 let render_trailer = render_trailer.as_ref()?;
20714 Some(render_trailer(buffer_row, folded, window, cx))
20715 } else {
20716 None
20717 }
20718 }
20719}
20720
20721impl Deref for EditorSnapshot {
20722 type Target = DisplaySnapshot;
20723
20724 fn deref(&self) -> &Self::Target {
20725 &self.display_snapshot
20726 }
20727}
20728
20729#[derive(Clone, Debug, PartialEq, Eq)]
20730pub enum EditorEvent {
20731 InputIgnored {
20732 text: Arc<str>,
20733 },
20734 InputHandled {
20735 utf16_range_to_replace: Option<Range<isize>>,
20736 text: Arc<str>,
20737 },
20738 ExcerptsAdded {
20739 buffer: Entity<Buffer>,
20740 predecessor: ExcerptId,
20741 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20742 },
20743 ExcerptsRemoved {
20744 ids: Vec<ExcerptId>,
20745 removed_buffer_ids: Vec<BufferId>,
20746 },
20747 BufferFoldToggled {
20748 ids: Vec<ExcerptId>,
20749 folded: bool,
20750 },
20751 ExcerptsEdited {
20752 ids: Vec<ExcerptId>,
20753 },
20754 ExcerptsExpanded {
20755 ids: Vec<ExcerptId>,
20756 },
20757 BufferEdited,
20758 Edited {
20759 transaction_id: clock::Lamport,
20760 },
20761 Reparsed(BufferId),
20762 Focused,
20763 FocusedIn,
20764 Blurred,
20765 DirtyChanged,
20766 Saved,
20767 TitleChanged,
20768 DiffBaseChanged,
20769 SelectionsChanged {
20770 local: bool,
20771 },
20772 ScrollPositionChanged {
20773 local: bool,
20774 autoscroll: bool,
20775 },
20776 Closed,
20777 TransactionUndone {
20778 transaction_id: clock::Lamport,
20779 },
20780 TransactionBegun {
20781 transaction_id: clock::Lamport,
20782 },
20783 Reloaded,
20784 CursorShapeChanged,
20785 PushedToNavHistory {
20786 anchor: Anchor,
20787 is_deactivate: bool,
20788 },
20789}
20790
20791impl EventEmitter<EditorEvent> for Editor {}
20792
20793impl Focusable for Editor {
20794 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20795 self.focus_handle.clone()
20796 }
20797}
20798
20799impl Render for Editor {
20800 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20801 let settings = ThemeSettings::get_global(cx);
20802
20803 let mut text_style = match self.mode {
20804 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20805 color: cx.theme().colors().editor_foreground,
20806 font_family: settings.ui_font.family.clone(),
20807 font_features: settings.ui_font.features.clone(),
20808 font_fallbacks: settings.ui_font.fallbacks.clone(),
20809 font_size: rems(0.875).into(),
20810 font_weight: settings.ui_font.weight,
20811 line_height: relative(settings.buffer_line_height.value()),
20812 ..Default::default()
20813 },
20814 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20815 color: cx.theme().colors().editor_foreground,
20816 font_family: settings.buffer_font.family.clone(),
20817 font_features: settings.buffer_font.features.clone(),
20818 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20819 font_size: settings.buffer_font_size(cx).into(),
20820 font_weight: settings.buffer_font.weight,
20821 line_height: relative(settings.buffer_line_height.value()),
20822 ..Default::default()
20823 },
20824 };
20825 if let Some(text_style_refinement) = &self.text_style_refinement {
20826 text_style.refine(text_style_refinement)
20827 }
20828
20829 let background = match self.mode {
20830 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20831 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20832 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20833 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20834 };
20835
20836 EditorElement::new(
20837 &cx.entity(),
20838 EditorStyle {
20839 background,
20840 local_player: cx.theme().players().local(),
20841 text: text_style,
20842 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20843 syntax: cx.theme().syntax().clone(),
20844 status: cx.theme().status().clone(),
20845 inlay_hints_style: make_inlay_hints_style(cx),
20846 inline_completion_styles: make_suggestion_styles(cx),
20847 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20848 show_underlines: !self.mode.is_minimap(),
20849 },
20850 )
20851 }
20852}
20853
20854impl EntityInputHandler for Editor {
20855 fn text_for_range(
20856 &mut self,
20857 range_utf16: Range<usize>,
20858 adjusted_range: &mut Option<Range<usize>>,
20859 _: &mut Window,
20860 cx: &mut Context<Self>,
20861 ) -> Option<String> {
20862 let snapshot = self.buffer.read(cx).read(cx);
20863 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20864 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20865 if (start.0..end.0) != range_utf16 {
20866 adjusted_range.replace(start.0..end.0);
20867 }
20868 Some(snapshot.text_for_range(start..end).collect())
20869 }
20870
20871 fn selected_text_range(
20872 &mut self,
20873 ignore_disabled_input: bool,
20874 _: &mut Window,
20875 cx: &mut Context<Self>,
20876 ) -> Option<UTF16Selection> {
20877 // Prevent the IME menu from appearing when holding down an alphabetic key
20878 // while input is disabled.
20879 if !ignore_disabled_input && !self.input_enabled {
20880 return None;
20881 }
20882
20883 let selection = self.selections.newest::<OffsetUtf16>(cx);
20884 let range = selection.range();
20885
20886 Some(UTF16Selection {
20887 range: range.start.0..range.end.0,
20888 reversed: selection.reversed,
20889 })
20890 }
20891
20892 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20893 let snapshot = self.buffer.read(cx).read(cx);
20894 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20895 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20896 }
20897
20898 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20899 self.clear_highlights::<InputComposition>(cx);
20900 self.ime_transaction.take();
20901 }
20902
20903 fn replace_text_in_range(
20904 &mut self,
20905 range_utf16: Option<Range<usize>>,
20906 text: &str,
20907 window: &mut Window,
20908 cx: &mut Context<Self>,
20909 ) {
20910 if !self.input_enabled {
20911 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20912 return;
20913 }
20914
20915 self.transact(window, cx, |this, window, cx| {
20916 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20917 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20918 Some(this.selection_replacement_ranges(range_utf16, cx))
20919 } else {
20920 this.marked_text_ranges(cx)
20921 };
20922
20923 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20924 let newest_selection_id = this.selections.newest_anchor().id;
20925 this.selections
20926 .all::<OffsetUtf16>(cx)
20927 .iter()
20928 .zip(ranges_to_replace.iter())
20929 .find_map(|(selection, range)| {
20930 if selection.id == newest_selection_id {
20931 Some(
20932 (range.start.0 as isize - selection.head().0 as isize)
20933 ..(range.end.0 as isize - selection.head().0 as isize),
20934 )
20935 } else {
20936 None
20937 }
20938 })
20939 });
20940
20941 cx.emit(EditorEvent::InputHandled {
20942 utf16_range_to_replace: range_to_replace,
20943 text: text.into(),
20944 });
20945
20946 if let Some(new_selected_ranges) = new_selected_ranges {
20947 this.change_selections(None, window, cx, |selections| {
20948 selections.select_ranges(new_selected_ranges)
20949 });
20950 this.backspace(&Default::default(), window, cx);
20951 }
20952
20953 this.handle_input(text, window, cx);
20954 });
20955
20956 if let Some(transaction) = self.ime_transaction {
20957 self.buffer.update(cx, |buffer, cx| {
20958 buffer.group_until_transaction(transaction, cx);
20959 });
20960 }
20961
20962 self.unmark_text(window, cx);
20963 }
20964
20965 fn replace_and_mark_text_in_range(
20966 &mut self,
20967 range_utf16: Option<Range<usize>>,
20968 text: &str,
20969 new_selected_range_utf16: Option<Range<usize>>,
20970 window: &mut Window,
20971 cx: &mut Context<Self>,
20972 ) {
20973 if !self.input_enabled {
20974 return;
20975 }
20976
20977 let transaction = self.transact(window, cx, |this, window, cx| {
20978 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20979 let snapshot = this.buffer.read(cx).read(cx);
20980 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20981 for marked_range in &mut marked_ranges {
20982 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20983 marked_range.start.0 += relative_range_utf16.start;
20984 marked_range.start =
20985 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20986 marked_range.end =
20987 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20988 }
20989 }
20990 Some(marked_ranges)
20991 } else if let Some(range_utf16) = range_utf16 {
20992 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20993 Some(this.selection_replacement_ranges(range_utf16, cx))
20994 } else {
20995 None
20996 };
20997
20998 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20999 let newest_selection_id = this.selections.newest_anchor().id;
21000 this.selections
21001 .all::<OffsetUtf16>(cx)
21002 .iter()
21003 .zip(ranges_to_replace.iter())
21004 .find_map(|(selection, range)| {
21005 if selection.id == newest_selection_id {
21006 Some(
21007 (range.start.0 as isize - selection.head().0 as isize)
21008 ..(range.end.0 as isize - selection.head().0 as isize),
21009 )
21010 } else {
21011 None
21012 }
21013 })
21014 });
21015
21016 cx.emit(EditorEvent::InputHandled {
21017 utf16_range_to_replace: range_to_replace,
21018 text: text.into(),
21019 });
21020
21021 if let Some(ranges) = ranges_to_replace {
21022 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21023 }
21024
21025 let marked_ranges = {
21026 let snapshot = this.buffer.read(cx).read(cx);
21027 this.selections
21028 .disjoint_anchors()
21029 .iter()
21030 .map(|selection| {
21031 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21032 })
21033 .collect::<Vec<_>>()
21034 };
21035
21036 if text.is_empty() {
21037 this.unmark_text(window, cx);
21038 } else {
21039 this.highlight_text::<InputComposition>(
21040 marked_ranges.clone(),
21041 HighlightStyle {
21042 underline: Some(UnderlineStyle {
21043 thickness: px(1.),
21044 color: None,
21045 wavy: false,
21046 }),
21047 ..Default::default()
21048 },
21049 cx,
21050 );
21051 }
21052
21053 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21054 let use_autoclose = this.use_autoclose;
21055 let use_auto_surround = this.use_auto_surround;
21056 this.set_use_autoclose(false);
21057 this.set_use_auto_surround(false);
21058 this.handle_input(text, window, cx);
21059 this.set_use_autoclose(use_autoclose);
21060 this.set_use_auto_surround(use_auto_surround);
21061
21062 if let Some(new_selected_range) = new_selected_range_utf16 {
21063 let snapshot = this.buffer.read(cx).read(cx);
21064 let new_selected_ranges = marked_ranges
21065 .into_iter()
21066 .map(|marked_range| {
21067 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21068 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21069 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21070 snapshot.clip_offset_utf16(new_start, Bias::Left)
21071 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21072 })
21073 .collect::<Vec<_>>();
21074
21075 drop(snapshot);
21076 this.change_selections(None, window, cx, |selections| {
21077 selections.select_ranges(new_selected_ranges)
21078 });
21079 }
21080 });
21081
21082 self.ime_transaction = self.ime_transaction.or(transaction);
21083 if let Some(transaction) = self.ime_transaction {
21084 self.buffer.update(cx, |buffer, cx| {
21085 buffer.group_until_transaction(transaction, cx);
21086 });
21087 }
21088
21089 if self.text_highlights::<InputComposition>(cx).is_none() {
21090 self.ime_transaction.take();
21091 }
21092 }
21093
21094 fn bounds_for_range(
21095 &mut self,
21096 range_utf16: Range<usize>,
21097 element_bounds: gpui::Bounds<Pixels>,
21098 window: &mut Window,
21099 cx: &mut Context<Self>,
21100 ) -> Option<gpui::Bounds<Pixels>> {
21101 let text_layout_details = self.text_layout_details(window);
21102 let gpui::Size {
21103 width: em_width,
21104 height: line_height,
21105 } = self.character_size(window);
21106
21107 let snapshot = self.snapshot(window, cx);
21108 let scroll_position = snapshot.scroll_position();
21109 let scroll_left = scroll_position.x * em_width;
21110
21111 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21112 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21113 + self.gutter_dimensions.width
21114 + self.gutter_dimensions.margin;
21115 let y = line_height * (start.row().as_f32() - scroll_position.y);
21116
21117 Some(Bounds {
21118 origin: element_bounds.origin + point(x, y),
21119 size: size(em_width, line_height),
21120 })
21121 }
21122
21123 fn character_index_for_point(
21124 &mut self,
21125 point: gpui::Point<Pixels>,
21126 _window: &mut Window,
21127 _cx: &mut Context<Self>,
21128 ) -> Option<usize> {
21129 let position_map = self.last_position_map.as_ref()?;
21130 if !position_map.text_hitbox.contains(&point) {
21131 return None;
21132 }
21133 let display_point = position_map.point_for_position(point).previous_valid;
21134 let anchor = position_map
21135 .snapshot
21136 .display_point_to_anchor(display_point, Bias::Left);
21137 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21138 Some(utf16_offset.0)
21139 }
21140}
21141
21142trait SelectionExt {
21143 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21144 fn spanned_rows(
21145 &self,
21146 include_end_if_at_line_start: bool,
21147 map: &DisplaySnapshot,
21148 ) -> Range<MultiBufferRow>;
21149}
21150
21151impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21152 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21153 let start = self
21154 .start
21155 .to_point(&map.buffer_snapshot)
21156 .to_display_point(map);
21157 let end = self
21158 .end
21159 .to_point(&map.buffer_snapshot)
21160 .to_display_point(map);
21161 if self.reversed {
21162 end..start
21163 } else {
21164 start..end
21165 }
21166 }
21167
21168 fn spanned_rows(
21169 &self,
21170 include_end_if_at_line_start: bool,
21171 map: &DisplaySnapshot,
21172 ) -> Range<MultiBufferRow> {
21173 let start = self.start.to_point(&map.buffer_snapshot);
21174 let mut end = self.end.to_point(&map.buffer_snapshot);
21175 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21176 end.row -= 1;
21177 }
21178
21179 let buffer_start = map.prev_line_boundary(start).0;
21180 let buffer_end = map.next_line_boundary(end).0;
21181 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21182 }
21183}
21184
21185impl<T: InvalidationRegion> InvalidationStack<T> {
21186 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21187 where
21188 S: Clone + ToOffset,
21189 {
21190 while let Some(region) = self.last() {
21191 let all_selections_inside_invalidation_ranges =
21192 if selections.len() == region.ranges().len() {
21193 selections
21194 .iter()
21195 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21196 .all(|(selection, invalidation_range)| {
21197 let head = selection.head().to_offset(buffer);
21198 invalidation_range.start <= head && invalidation_range.end >= head
21199 })
21200 } else {
21201 false
21202 };
21203
21204 if all_selections_inside_invalidation_ranges {
21205 break;
21206 } else {
21207 self.pop();
21208 }
21209 }
21210 }
21211}
21212
21213impl<T> Default for InvalidationStack<T> {
21214 fn default() -> Self {
21215 Self(Default::default())
21216 }
21217}
21218
21219impl<T> Deref for InvalidationStack<T> {
21220 type Target = Vec<T>;
21221
21222 fn deref(&self) -> &Self::Target {
21223 &self.0
21224 }
21225}
21226
21227impl<T> DerefMut for InvalidationStack<T> {
21228 fn deref_mut(&mut self) -> &mut Self::Target {
21229 &mut self.0
21230 }
21231}
21232
21233impl InvalidationRegion for SnippetState {
21234 fn ranges(&self) -> &[Range<Anchor>] {
21235 &self.ranges[self.active_index]
21236 }
21237}
21238
21239fn inline_completion_edit_text(
21240 current_snapshot: &BufferSnapshot,
21241 edits: &[(Range<Anchor>, String)],
21242 edit_preview: &EditPreview,
21243 include_deletions: bool,
21244 cx: &App,
21245) -> HighlightedText {
21246 let edits = edits
21247 .iter()
21248 .map(|(anchor, text)| {
21249 (
21250 anchor.start.text_anchor..anchor.end.text_anchor,
21251 text.clone(),
21252 )
21253 })
21254 .collect::<Vec<_>>();
21255
21256 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21257}
21258
21259pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21260 match severity {
21261 lsp::DiagnosticSeverity::ERROR => colors.error,
21262 lsp::DiagnosticSeverity::WARNING => colors.warning,
21263 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21264 lsp::DiagnosticSeverity::HINT => colors.info,
21265 _ => colors.ignored,
21266 }
21267}
21268
21269pub fn styled_runs_for_code_label<'a>(
21270 label: &'a CodeLabel,
21271 syntax_theme: &'a theme::SyntaxTheme,
21272) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21273 let fade_out = HighlightStyle {
21274 fade_out: Some(0.35),
21275 ..Default::default()
21276 };
21277
21278 let mut prev_end = label.filter_range.end;
21279 label
21280 .runs
21281 .iter()
21282 .enumerate()
21283 .flat_map(move |(ix, (range, highlight_id))| {
21284 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21285 style
21286 } else {
21287 return Default::default();
21288 };
21289 let mut muted_style = style;
21290 muted_style.highlight(fade_out);
21291
21292 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21293 if range.start >= label.filter_range.end {
21294 if range.start > prev_end {
21295 runs.push((prev_end..range.start, fade_out));
21296 }
21297 runs.push((range.clone(), muted_style));
21298 } else if range.end <= label.filter_range.end {
21299 runs.push((range.clone(), style));
21300 } else {
21301 runs.push((range.start..label.filter_range.end, style));
21302 runs.push((label.filter_range.end..range.end, muted_style));
21303 }
21304 prev_end = cmp::max(prev_end, range.end);
21305
21306 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21307 runs.push((prev_end..label.text.len(), fade_out));
21308 }
21309
21310 runs
21311 })
21312}
21313
21314pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21315 let mut prev_index = 0;
21316 let mut prev_codepoint: Option<char> = None;
21317 text.char_indices()
21318 .chain([(text.len(), '\0')])
21319 .filter_map(move |(index, codepoint)| {
21320 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21321 let is_boundary = index == text.len()
21322 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21323 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21324 if is_boundary {
21325 let chunk = &text[prev_index..index];
21326 prev_index = index;
21327 Some(chunk)
21328 } else {
21329 None
21330 }
21331 })
21332}
21333
21334pub trait RangeToAnchorExt: Sized {
21335 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21336
21337 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21338 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21339 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21340 }
21341}
21342
21343impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21344 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21345 let start_offset = self.start.to_offset(snapshot);
21346 let end_offset = self.end.to_offset(snapshot);
21347 if start_offset == end_offset {
21348 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21349 } else {
21350 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21351 }
21352 }
21353}
21354
21355pub trait RowExt {
21356 fn as_f32(&self) -> f32;
21357
21358 fn next_row(&self) -> Self;
21359
21360 fn previous_row(&self) -> Self;
21361
21362 fn minus(&self, other: Self) -> u32;
21363}
21364
21365impl RowExt for DisplayRow {
21366 fn as_f32(&self) -> f32 {
21367 self.0 as f32
21368 }
21369
21370 fn next_row(&self) -> Self {
21371 Self(self.0 + 1)
21372 }
21373
21374 fn previous_row(&self) -> Self {
21375 Self(self.0.saturating_sub(1))
21376 }
21377
21378 fn minus(&self, other: Self) -> u32 {
21379 self.0 - other.0
21380 }
21381}
21382
21383impl RowExt for MultiBufferRow {
21384 fn as_f32(&self) -> f32 {
21385 self.0 as f32
21386 }
21387
21388 fn next_row(&self) -> Self {
21389 Self(self.0 + 1)
21390 }
21391
21392 fn previous_row(&self) -> Self {
21393 Self(self.0.saturating_sub(1))
21394 }
21395
21396 fn minus(&self, other: Self) -> u32 {
21397 self.0 - other.0
21398 }
21399}
21400
21401trait RowRangeExt {
21402 type Row;
21403
21404 fn len(&self) -> usize;
21405
21406 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21407}
21408
21409impl RowRangeExt for Range<MultiBufferRow> {
21410 type Row = MultiBufferRow;
21411
21412 fn len(&self) -> usize {
21413 (self.end.0 - self.start.0) as usize
21414 }
21415
21416 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21417 (self.start.0..self.end.0).map(MultiBufferRow)
21418 }
21419}
21420
21421impl RowRangeExt for Range<DisplayRow> {
21422 type Row = DisplayRow;
21423
21424 fn len(&self) -> usize {
21425 (self.end.0 - self.start.0) as usize
21426 }
21427
21428 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21429 (self.start.0..self.end.0).map(DisplayRow)
21430 }
21431}
21432
21433/// If select range has more than one line, we
21434/// just point the cursor to range.start.
21435fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21436 if range.start.row == range.end.row {
21437 range
21438 } else {
21439 range.start..range.start
21440 }
21441}
21442pub struct KillRing(ClipboardItem);
21443impl Global for KillRing {}
21444
21445const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21446
21447enum BreakpointPromptEditAction {
21448 Log,
21449 Condition,
21450 HitCondition,
21451}
21452
21453struct BreakpointPromptEditor {
21454 pub(crate) prompt: Entity<Editor>,
21455 editor: WeakEntity<Editor>,
21456 breakpoint_anchor: Anchor,
21457 breakpoint: Breakpoint,
21458 edit_action: BreakpointPromptEditAction,
21459 block_ids: HashSet<CustomBlockId>,
21460 editor_margins: Arc<Mutex<EditorMargins>>,
21461 _subscriptions: Vec<Subscription>,
21462}
21463
21464impl BreakpointPromptEditor {
21465 const MAX_LINES: u8 = 4;
21466
21467 fn new(
21468 editor: WeakEntity<Editor>,
21469 breakpoint_anchor: Anchor,
21470 breakpoint: Breakpoint,
21471 edit_action: BreakpointPromptEditAction,
21472 window: &mut Window,
21473 cx: &mut Context<Self>,
21474 ) -> Self {
21475 let base_text = match edit_action {
21476 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21477 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21478 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21479 }
21480 .map(|msg| msg.to_string())
21481 .unwrap_or_default();
21482
21483 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21484 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21485
21486 let prompt = cx.new(|cx| {
21487 let mut prompt = Editor::new(
21488 EditorMode::AutoHeight {
21489 max_lines: Self::MAX_LINES as usize,
21490 },
21491 buffer,
21492 None,
21493 window,
21494 cx,
21495 );
21496 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21497 prompt.set_show_cursor_when_unfocused(false, cx);
21498 prompt.set_placeholder_text(
21499 match edit_action {
21500 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21501 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21502 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21503 },
21504 cx,
21505 );
21506
21507 prompt
21508 });
21509
21510 Self {
21511 prompt,
21512 editor,
21513 breakpoint_anchor,
21514 breakpoint,
21515 edit_action,
21516 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21517 block_ids: Default::default(),
21518 _subscriptions: vec![],
21519 }
21520 }
21521
21522 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21523 self.block_ids.extend(block_ids)
21524 }
21525
21526 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21527 if let Some(editor) = self.editor.upgrade() {
21528 let message = self
21529 .prompt
21530 .read(cx)
21531 .buffer
21532 .read(cx)
21533 .as_singleton()
21534 .expect("A multi buffer in breakpoint prompt isn't possible")
21535 .read(cx)
21536 .as_rope()
21537 .to_string();
21538
21539 editor.update(cx, |editor, cx| {
21540 editor.edit_breakpoint_at_anchor(
21541 self.breakpoint_anchor,
21542 self.breakpoint.clone(),
21543 match self.edit_action {
21544 BreakpointPromptEditAction::Log => {
21545 BreakpointEditAction::EditLogMessage(message.into())
21546 }
21547 BreakpointPromptEditAction::Condition => {
21548 BreakpointEditAction::EditCondition(message.into())
21549 }
21550 BreakpointPromptEditAction::HitCondition => {
21551 BreakpointEditAction::EditHitCondition(message.into())
21552 }
21553 },
21554 cx,
21555 );
21556
21557 editor.remove_blocks(self.block_ids.clone(), None, cx);
21558 cx.focus_self(window);
21559 });
21560 }
21561 }
21562
21563 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21564 self.editor
21565 .update(cx, |editor, cx| {
21566 editor.remove_blocks(self.block_ids.clone(), None, cx);
21567 window.focus(&editor.focus_handle);
21568 })
21569 .log_err();
21570 }
21571
21572 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21573 let settings = ThemeSettings::get_global(cx);
21574 let text_style = TextStyle {
21575 color: if self.prompt.read(cx).read_only(cx) {
21576 cx.theme().colors().text_disabled
21577 } else {
21578 cx.theme().colors().text
21579 },
21580 font_family: settings.buffer_font.family.clone(),
21581 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21582 font_size: settings.buffer_font_size(cx).into(),
21583 font_weight: settings.buffer_font.weight,
21584 line_height: relative(settings.buffer_line_height.value()),
21585 ..Default::default()
21586 };
21587 EditorElement::new(
21588 &self.prompt,
21589 EditorStyle {
21590 background: cx.theme().colors().editor_background,
21591 local_player: cx.theme().players().local(),
21592 text: text_style,
21593 ..Default::default()
21594 },
21595 )
21596 }
21597}
21598
21599impl Render for BreakpointPromptEditor {
21600 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21601 let editor_margins = *self.editor_margins.lock();
21602 let gutter_dimensions = editor_margins.gutter;
21603 h_flex()
21604 .key_context("Editor")
21605 .bg(cx.theme().colors().editor_background)
21606 .border_y_1()
21607 .border_color(cx.theme().status().info_border)
21608 .size_full()
21609 .py(window.line_height() / 2.5)
21610 .on_action(cx.listener(Self::confirm))
21611 .on_action(cx.listener(Self::cancel))
21612 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21613 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21614 }
21615}
21616
21617impl Focusable for BreakpointPromptEditor {
21618 fn focus_handle(&self, cx: &App) -> FocusHandle {
21619 self.prompt.focus_handle(cx)
21620 }
21621}
21622
21623fn all_edits_insertions_or_deletions(
21624 edits: &Vec<(Range<Anchor>, String)>,
21625 snapshot: &MultiBufferSnapshot,
21626) -> bool {
21627 let mut all_insertions = true;
21628 let mut all_deletions = true;
21629
21630 for (range, new_text) in edits.iter() {
21631 let range_is_empty = range.to_offset(&snapshot).is_empty();
21632 let text_is_empty = new_text.is_empty();
21633
21634 if range_is_empty != text_is_empty {
21635 if range_is_empty {
21636 all_deletions = false;
21637 } else {
21638 all_insertions = false;
21639 }
21640 } else {
21641 return false;
21642 }
21643
21644 if !all_insertions && !all_deletions {
21645 return false;
21646 }
21647 }
21648 all_insertions || all_deletions
21649}
21650
21651struct MissingEditPredictionKeybindingTooltip;
21652
21653impl Render for MissingEditPredictionKeybindingTooltip {
21654 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21655 ui::tooltip_container(window, cx, |container, _, cx| {
21656 container
21657 .flex_shrink_0()
21658 .max_w_80()
21659 .min_h(rems_from_px(124.))
21660 .justify_between()
21661 .child(
21662 v_flex()
21663 .flex_1()
21664 .text_ui_sm(cx)
21665 .child(Label::new("Conflict with Accept Keybinding"))
21666 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21667 )
21668 .child(
21669 h_flex()
21670 .pb_1()
21671 .gap_1()
21672 .items_end()
21673 .w_full()
21674 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21675 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21676 }))
21677 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21678 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21679 })),
21680 )
21681 })
21682 }
21683}
21684
21685#[derive(Debug, Clone, Copy, PartialEq)]
21686pub struct LineHighlight {
21687 pub background: Background,
21688 pub border: Option<gpui::Hsla>,
21689 pub include_gutter: bool,
21690 pub type_id: Option<TypeId>,
21691}
21692
21693fn render_diff_hunk_controls(
21694 row: u32,
21695 status: &DiffHunkStatus,
21696 hunk_range: Range<Anchor>,
21697 is_created_file: bool,
21698 line_height: Pixels,
21699 editor: &Entity<Editor>,
21700 _window: &mut Window,
21701 cx: &mut App,
21702) -> AnyElement {
21703 h_flex()
21704 .h(line_height)
21705 .mr_1()
21706 .gap_1()
21707 .px_0p5()
21708 .pb_1()
21709 .border_x_1()
21710 .border_b_1()
21711 .border_color(cx.theme().colors().border_variant)
21712 .rounded_b_lg()
21713 .bg(cx.theme().colors().editor_background)
21714 .gap_1()
21715 .occlude()
21716 .shadow_md()
21717 .child(if status.has_secondary_hunk() {
21718 Button::new(("stage", row as u64), "Stage")
21719 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21720 .tooltip({
21721 let focus_handle = editor.focus_handle(cx);
21722 move |window, cx| {
21723 Tooltip::for_action_in(
21724 "Stage Hunk",
21725 &::git::ToggleStaged,
21726 &focus_handle,
21727 window,
21728 cx,
21729 )
21730 }
21731 })
21732 .on_click({
21733 let editor = editor.clone();
21734 move |_event, _window, cx| {
21735 editor.update(cx, |editor, cx| {
21736 editor.stage_or_unstage_diff_hunks(
21737 true,
21738 vec![hunk_range.start..hunk_range.start],
21739 cx,
21740 );
21741 });
21742 }
21743 })
21744 } else {
21745 Button::new(("unstage", row as u64), "Unstage")
21746 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21747 .tooltip({
21748 let focus_handle = editor.focus_handle(cx);
21749 move |window, cx| {
21750 Tooltip::for_action_in(
21751 "Unstage Hunk",
21752 &::git::ToggleStaged,
21753 &focus_handle,
21754 window,
21755 cx,
21756 )
21757 }
21758 })
21759 .on_click({
21760 let editor = editor.clone();
21761 move |_event, _window, cx| {
21762 editor.update(cx, |editor, cx| {
21763 editor.stage_or_unstage_diff_hunks(
21764 false,
21765 vec![hunk_range.start..hunk_range.start],
21766 cx,
21767 );
21768 });
21769 }
21770 })
21771 })
21772 .child(
21773 Button::new(("restore", row as u64), "Restore")
21774 .tooltip({
21775 let focus_handle = editor.focus_handle(cx);
21776 move |window, cx| {
21777 Tooltip::for_action_in(
21778 "Restore Hunk",
21779 &::git::Restore,
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 point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21792 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21793 });
21794 }
21795 })
21796 .disabled(is_created_file),
21797 )
21798 .when(
21799 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21800 |el| {
21801 el.child(
21802 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21803 .shape(IconButtonShape::Square)
21804 .icon_size(IconSize::Small)
21805 // .disabled(!has_multiple_hunks)
21806 .tooltip({
21807 let focus_handle = editor.focus_handle(cx);
21808 move |window, cx| {
21809 Tooltip::for_action_in(
21810 "Next Hunk",
21811 &GoToHunk,
21812 &focus_handle,
21813 window,
21814 cx,
21815 )
21816 }
21817 })
21818 .on_click({
21819 let editor = editor.clone();
21820 move |_event, window, cx| {
21821 editor.update(cx, |editor, cx| {
21822 let snapshot = editor.snapshot(window, cx);
21823 let position =
21824 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21825 editor.go_to_hunk_before_or_after_position(
21826 &snapshot,
21827 position,
21828 Direction::Next,
21829 window,
21830 cx,
21831 );
21832 editor.expand_selected_diff_hunks(cx);
21833 });
21834 }
21835 }),
21836 )
21837 .child(
21838 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21839 .shape(IconButtonShape::Square)
21840 .icon_size(IconSize::Small)
21841 // .disabled(!has_multiple_hunks)
21842 .tooltip({
21843 let focus_handle = editor.focus_handle(cx);
21844 move |window, cx| {
21845 Tooltip::for_action_in(
21846 "Previous Hunk",
21847 &GoToPreviousHunk,
21848 &focus_handle,
21849 window,
21850 cx,
21851 )
21852 }
21853 })
21854 .on_click({
21855 let editor = editor.clone();
21856 move |_event, window, cx| {
21857 editor.update(cx, |editor, cx| {
21858 let snapshot = editor.snapshot(window, cx);
21859 let point =
21860 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21861 editor.go_to_hunk_before_or_after_position(
21862 &snapshot,
21863 point,
21864 Direction::Prev,
21865 window,
21866 cx,
21867 );
21868 editor.expand_selected_diff_hunks(cx);
21869 });
21870 }
21871 }),
21872 )
21873 },
21874 )
21875 .into_any_element()
21876}