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 let snapshot = self.buffer.read(cx).snapshot(cx);
6550 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6551
6552 self.buffer.update(cx, |buffer, cx| {
6553 buffer.edit(edits.iter().cloned(), None, cx)
6554 });
6555
6556 self.change_selections(None, window, cx, |s| {
6557 s.select_anchor_ranges([last_edit_end..last_edit_end])
6558 });
6559
6560 self.update_visible_inline_completion(window, cx);
6561 if self.active_inline_completion.is_none() {
6562 self.refresh_inline_completion(true, true, window, cx);
6563 }
6564
6565 cx.notify();
6566 }
6567 }
6568
6569 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6570 }
6571
6572 pub fn accept_partial_inline_completion(
6573 &mut self,
6574 _: &AcceptPartialEditPrediction,
6575 window: &mut Window,
6576 cx: &mut Context<Self>,
6577 ) {
6578 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6579 return;
6580 };
6581 if self.selections.count() != 1 {
6582 return;
6583 }
6584
6585 self.report_inline_completion_event(
6586 active_inline_completion.completion_id.clone(),
6587 true,
6588 cx,
6589 );
6590
6591 match &active_inline_completion.completion {
6592 InlineCompletion::Move { target, .. } => {
6593 let target = *target;
6594 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6595 selections.select_anchor_ranges([target..target]);
6596 });
6597 }
6598 InlineCompletion::Edit { edits, .. } => {
6599 // Find an insertion that starts at the cursor position.
6600 let snapshot = self.buffer.read(cx).snapshot(cx);
6601 let cursor_offset = self.selections.newest::<usize>(cx).head();
6602 let insertion = edits.iter().find_map(|(range, text)| {
6603 let range = range.to_offset(&snapshot);
6604 if range.is_empty() && range.start == cursor_offset {
6605 Some(text)
6606 } else {
6607 None
6608 }
6609 });
6610
6611 if let Some(text) = insertion {
6612 let mut partial_completion = text
6613 .chars()
6614 .by_ref()
6615 .take_while(|c| c.is_alphabetic())
6616 .collect::<String>();
6617 if partial_completion.is_empty() {
6618 partial_completion = text
6619 .chars()
6620 .by_ref()
6621 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6622 .collect::<String>();
6623 }
6624
6625 cx.emit(EditorEvent::InputHandled {
6626 utf16_range_to_replace: None,
6627 text: partial_completion.clone().into(),
6628 });
6629
6630 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6631
6632 self.refresh_inline_completion(true, true, window, cx);
6633 cx.notify();
6634 } else {
6635 self.accept_edit_prediction(&Default::default(), window, cx);
6636 }
6637 }
6638 }
6639 }
6640
6641 fn discard_inline_completion(
6642 &mut self,
6643 should_report_inline_completion_event: bool,
6644 cx: &mut Context<Self>,
6645 ) -> bool {
6646 if should_report_inline_completion_event {
6647 let completion_id = self
6648 .active_inline_completion
6649 .as_ref()
6650 .and_then(|active_completion| active_completion.completion_id.clone());
6651
6652 self.report_inline_completion_event(completion_id, false, cx);
6653 }
6654
6655 if let Some(provider) = self.edit_prediction_provider() {
6656 provider.discard(cx);
6657 }
6658
6659 self.take_active_inline_completion(cx)
6660 }
6661
6662 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6663 let Some(provider) = self.edit_prediction_provider() else {
6664 return;
6665 };
6666
6667 let Some((_, buffer, _)) = self
6668 .buffer
6669 .read(cx)
6670 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6671 else {
6672 return;
6673 };
6674
6675 let extension = buffer
6676 .read(cx)
6677 .file()
6678 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6679
6680 let event_type = match accepted {
6681 true => "Edit Prediction Accepted",
6682 false => "Edit Prediction Discarded",
6683 };
6684 telemetry::event!(
6685 event_type,
6686 provider = provider.name(),
6687 prediction_id = id,
6688 suggestion_accepted = accepted,
6689 file_extension = extension,
6690 );
6691 }
6692
6693 pub fn has_active_inline_completion(&self) -> bool {
6694 self.active_inline_completion.is_some()
6695 }
6696
6697 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6698 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6699 return false;
6700 };
6701
6702 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6703 self.clear_highlights::<InlineCompletionHighlight>(cx);
6704 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6705 true
6706 }
6707
6708 /// Returns true when we're displaying the edit prediction popover below the cursor
6709 /// like we are not previewing and the LSP autocomplete menu is visible
6710 /// or we are in `when_holding_modifier` mode.
6711 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6712 if self.edit_prediction_preview_is_active()
6713 || !self.show_edit_predictions_in_menu()
6714 || !self.edit_predictions_enabled()
6715 {
6716 return false;
6717 }
6718
6719 if self.has_visible_completions_menu() {
6720 return true;
6721 }
6722
6723 has_completion && self.edit_prediction_requires_modifier()
6724 }
6725
6726 fn handle_modifiers_changed(
6727 &mut self,
6728 modifiers: Modifiers,
6729 position_map: &PositionMap,
6730 window: &mut Window,
6731 cx: &mut Context<Self>,
6732 ) {
6733 if self.show_edit_predictions_in_menu() {
6734 self.update_edit_prediction_preview(&modifiers, window, cx);
6735 }
6736
6737 self.update_selection_mode(&modifiers, position_map, window, cx);
6738
6739 let mouse_position = window.mouse_position();
6740 if !position_map.text_hitbox.is_hovered(window) {
6741 return;
6742 }
6743
6744 self.update_hovered_link(
6745 position_map.point_for_position(mouse_position),
6746 &position_map.snapshot,
6747 modifiers,
6748 window,
6749 cx,
6750 )
6751 }
6752
6753 fn update_selection_mode(
6754 &mut self,
6755 modifiers: &Modifiers,
6756 position_map: &PositionMap,
6757 window: &mut Window,
6758 cx: &mut Context<Self>,
6759 ) {
6760 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6761 return;
6762 }
6763
6764 let mouse_position = window.mouse_position();
6765 let point_for_position = position_map.point_for_position(mouse_position);
6766 let position = point_for_position.previous_valid;
6767
6768 self.select(
6769 SelectPhase::BeginColumnar {
6770 position,
6771 reset: false,
6772 goal_column: point_for_position.exact_unclipped.column(),
6773 },
6774 window,
6775 cx,
6776 );
6777 }
6778
6779 fn update_edit_prediction_preview(
6780 &mut self,
6781 modifiers: &Modifiers,
6782 window: &mut Window,
6783 cx: &mut Context<Self>,
6784 ) {
6785 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6786 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6787 return;
6788 };
6789
6790 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6791 if matches!(
6792 self.edit_prediction_preview,
6793 EditPredictionPreview::Inactive { .. }
6794 ) {
6795 self.edit_prediction_preview = EditPredictionPreview::Active {
6796 previous_scroll_position: None,
6797 since: Instant::now(),
6798 };
6799
6800 self.update_visible_inline_completion(window, cx);
6801 cx.notify();
6802 }
6803 } else if let EditPredictionPreview::Active {
6804 previous_scroll_position,
6805 since,
6806 } = self.edit_prediction_preview
6807 {
6808 if let (Some(previous_scroll_position), Some(position_map)) =
6809 (previous_scroll_position, self.last_position_map.as_ref())
6810 {
6811 self.set_scroll_position(
6812 previous_scroll_position
6813 .scroll_position(&position_map.snapshot.display_snapshot),
6814 window,
6815 cx,
6816 );
6817 }
6818
6819 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6820 released_too_fast: since.elapsed() < Duration::from_millis(200),
6821 };
6822 self.clear_row_highlights::<EditPredictionPreview>();
6823 self.update_visible_inline_completion(window, cx);
6824 cx.notify();
6825 }
6826 }
6827
6828 fn update_visible_inline_completion(
6829 &mut self,
6830 _window: &mut Window,
6831 cx: &mut Context<Self>,
6832 ) -> Option<()> {
6833 let selection = self.selections.newest_anchor();
6834 let cursor = selection.head();
6835 let multibuffer = self.buffer.read(cx).snapshot(cx);
6836 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6837 let excerpt_id = cursor.excerpt_id;
6838
6839 let show_in_menu = self.show_edit_predictions_in_menu();
6840 let completions_menu_has_precedence = !show_in_menu
6841 && (self.context_menu.borrow().is_some()
6842 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6843
6844 if completions_menu_has_precedence
6845 || !offset_selection.is_empty()
6846 || self
6847 .active_inline_completion
6848 .as_ref()
6849 .map_or(false, |completion| {
6850 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6851 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6852 !invalidation_range.contains(&offset_selection.head())
6853 })
6854 {
6855 self.discard_inline_completion(false, cx);
6856 return None;
6857 }
6858
6859 self.take_active_inline_completion(cx);
6860 let Some(provider) = self.edit_prediction_provider() else {
6861 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6862 return None;
6863 };
6864
6865 let (buffer, cursor_buffer_position) =
6866 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6867
6868 self.edit_prediction_settings =
6869 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6870
6871 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6872
6873 if self.edit_prediction_indent_conflict {
6874 let cursor_point = cursor.to_point(&multibuffer);
6875
6876 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6877
6878 if let Some((_, indent)) = indents.iter().next() {
6879 if indent.len == cursor_point.column {
6880 self.edit_prediction_indent_conflict = false;
6881 }
6882 }
6883 }
6884
6885 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6886 let edits = inline_completion
6887 .edits
6888 .into_iter()
6889 .flat_map(|(range, new_text)| {
6890 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6891 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6892 Some((start..end, new_text))
6893 })
6894 .collect::<Vec<_>>();
6895 if edits.is_empty() {
6896 return None;
6897 }
6898
6899 let first_edit_start = edits.first().unwrap().0.start;
6900 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6901 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6902
6903 let last_edit_end = edits.last().unwrap().0.end;
6904 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6905 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6906
6907 let cursor_row = cursor.to_point(&multibuffer).row;
6908
6909 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6910
6911 let mut inlay_ids = Vec::new();
6912 let invalidation_row_range;
6913 let move_invalidation_row_range = if cursor_row < edit_start_row {
6914 Some(cursor_row..edit_end_row)
6915 } else if cursor_row > edit_end_row {
6916 Some(edit_start_row..cursor_row)
6917 } else {
6918 None
6919 };
6920 let is_move =
6921 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6922 let completion = if is_move {
6923 invalidation_row_range =
6924 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6925 let target = first_edit_start;
6926 InlineCompletion::Move { target, snapshot }
6927 } else {
6928 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6929 && !self.inline_completions_hidden_for_vim_mode;
6930
6931 if show_completions_in_buffer {
6932 if edits
6933 .iter()
6934 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6935 {
6936 let mut inlays = Vec::new();
6937 for (range, new_text) in &edits {
6938 let inlay = Inlay::inline_completion(
6939 post_inc(&mut self.next_inlay_id),
6940 range.start,
6941 new_text.as_str(),
6942 );
6943 inlay_ids.push(inlay.id);
6944 inlays.push(inlay);
6945 }
6946
6947 self.splice_inlays(&[], inlays, cx);
6948 } else {
6949 let background_color = cx.theme().status().deleted_background;
6950 self.highlight_text::<InlineCompletionHighlight>(
6951 edits.iter().map(|(range, _)| range.clone()).collect(),
6952 HighlightStyle {
6953 background_color: Some(background_color),
6954 ..Default::default()
6955 },
6956 cx,
6957 );
6958 }
6959 }
6960
6961 invalidation_row_range = edit_start_row..edit_end_row;
6962
6963 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6964 if provider.show_tab_accept_marker() {
6965 EditDisplayMode::TabAccept
6966 } else {
6967 EditDisplayMode::Inline
6968 }
6969 } else {
6970 EditDisplayMode::DiffPopover
6971 };
6972
6973 InlineCompletion::Edit {
6974 edits,
6975 edit_preview: inline_completion.edit_preview,
6976 display_mode,
6977 snapshot,
6978 }
6979 };
6980
6981 let invalidation_range = multibuffer
6982 .anchor_before(Point::new(invalidation_row_range.start, 0))
6983 ..multibuffer.anchor_after(Point::new(
6984 invalidation_row_range.end,
6985 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6986 ));
6987
6988 self.stale_inline_completion_in_menu = None;
6989 self.active_inline_completion = Some(InlineCompletionState {
6990 inlay_ids,
6991 completion,
6992 completion_id: inline_completion.id,
6993 invalidation_range,
6994 });
6995
6996 cx.notify();
6997
6998 Some(())
6999 }
7000
7001 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7002 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7003 }
7004
7005 fn clear_tasks(&mut self) {
7006 self.tasks.clear()
7007 }
7008
7009 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7010 if self.tasks.insert(key, value).is_some() {
7011 // This case should hopefully be rare, but just in case...
7012 log::error!(
7013 "multiple different run targets found on a single line, only the last target will be rendered"
7014 )
7015 }
7016 }
7017
7018 /// Get all display points of breakpoints that will be rendered within editor
7019 ///
7020 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7021 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7022 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7023 fn active_breakpoints(
7024 &self,
7025 range: Range<DisplayRow>,
7026 window: &mut Window,
7027 cx: &mut Context<Self>,
7028 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7029 let mut breakpoint_display_points = HashMap::default();
7030
7031 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7032 return breakpoint_display_points;
7033 };
7034
7035 let snapshot = self.snapshot(window, cx);
7036
7037 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7038 let Some(project) = self.project.as_ref() else {
7039 return breakpoint_display_points;
7040 };
7041
7042 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7043 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7044
7045 for (buffer_snapshot, range, excerpt_id) in
7046 multi_buffer_snapshot.range_to_buffer_ranges(range)
7047 {
7048 let Some(buffer) = project.read_with(cx, |this, cx| {
7049 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7050 }) else {
7051 continue;
7052 };
7053 let breakpoints = breakpoint_store.read(cx).breakpoints(
7054 &buffer,
7055 Some(
7056 buffer_snapshot.anchor_before(range.start)
7057 ..buffer_snapshot.anchor_after(range.end),
7058 ),
7059 buffer_snapshot,
7060 cx,
7061 );
7062 for (breakpoint, state) in breakpoints {
7063 let multi_buffer_anchor =
7064 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7065 let position = multi_buffer_anchor
7066 .to_point(&multi_buffer_snapshot)
7067 .to_display_point(&snapshot);
7068
7069 breakpoint_display_points.insert(
7070 position.row(),
7071 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7072 );
7073 }
7074 }
7075
7076 breakpoint_display_points
7077 }
7078
7079 fn breakpoint_context_menu(
7080 &self,
7081 anchor: Anchor,
7082 window: &mut Window,
7083 cx: &mut Context<Self>,
7084 ) -> Entity<ui::ContextMenu> {
7085 let weak_editor = cx.weak_entity();
7086 let focus_handle = self.focus_handle(cx);
7087
7088 let row = self
7089 .buffer
7090 .read(cx)
7091 .snapshot(cx)
7092 .summary_for_anchor::<Point>(&anchor)
7093 .row;
7094
7095 let breakpoint = self
7096 .breakpoint_at_row(row, window, cx)
7097 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7098
7099 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7100 "Edit Log Breakpoint"
7101 } else {
7102 "Set Log Breakpoint"
7103 };
7104
7105 let condition_breakpoint_msg = if breakpoint
7106 .as_ref()
7107 .is_some_and(|bp| bp.1.condition.is_some())
7108 {
7109 "Edit Condition Breakpoint"
7110 } else {
7111 "Set Condition Breakpoint"
7112 };
7113
7114 let hit_condition_breakpoint_msg = if breakpoint
7115 .as_ref()
7116 .is_some_and(|bp| bp.1.hit_condition.is_some())
7117 {
7118 "Edit Hit Condition Breakpoint"
7119 } else {
7120 "Set Hit Condition Breakpoint"
7121 };
7122
7123 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7124 "Unset Breakpoint"
7125 } else {
7126 "Set Breakpoint"
7127 };
7128
7129 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7130 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7131
7132 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7133 BreakpointState::Enabled => Some("Disable"),
7134 BreakpointState::Disabled => Some("Enable"),
7135 });
7136
7137 let (anchor, breakpoint) =
7138 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7139
7140 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7141 menu.on_blur_subscription(Subscription::new(|| {}))
7142 .context(focus_handle)
7143 .when(run_to_cursor, |this| {
7144 let weak_editor = weak_editor.clone();
7145 this.entry("Run to cursor", None, move |window, cx| {
7146 weak_editor
7147 .update(cx, |editor, cx| {
7148 editor.change_selections(None, window, cx, |s| {
7149 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7150 });
7151 })
7152 .ok();
7153
7154 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7155 })
7156 .separator()
7157 })
7158 .when_some(toggle_state_msg, |this, msg| {
7159 this.entry(msg, None, {
7160 let weak_editor = weak_editor.clone();
7161 let breakpoint = breakpoint.clone();
7162 move |_window, cx| {
7163 weak_editor
7164 .update(cx, |this, cx| {
7165 this.edit_breakpoint_at_anchor(
7166 anchor,
7167 breakpoint.as_ref().clone(),
7168 BreakpointEditAction::InvertState,
7169 cx,
7170 );
7171 })
7172 .log_err();
7173 }
7174 })
7175 })
7176 .entry(set_breakpoint_msg, None, {
7177 let weak_editor = weak_editor.clone();
7178 let breakpoint = breakpoint.clone();
7179 move |_window, cx| {
7180 weak_editor
7181 .update(cx, |this, cx| {
7182 this.edit_breakpoint_at_anchor(
7183 anchor,
7184 breakpoint.as_ref().clone(),
7185 BreakpointEditAction::Toggle,
7186 cx,
7187 );
7188 })
7189 .log_err();
7190 }
7191 })
7192 .entry(log_breakpoint_msg, None, {
7193 let breakpoint = breakpoint.clone();
7194 let weak_editor = weak_editor.clone();
7195 move |window, cx| {
7196 weak_editor
7197 .update(cx, |this, cx| {
7198 this.add_edit_breakpoint_block(
7199 anchor,
7200 breakpoint.as_ref(),
7201 BreakpointPromptEditAction::Log,
7202 window,
7203 cx,
7204 );
7205 })
7206 .log_err();
7207 }
7208 })
7209 .entry(condition_breakpoint_msg, None, {
7210 let breakpoint = breakpoint.clone();
7211 let weak_editor = weak_editor.clone();
7212 move |window, cx| {
7213 weak_editor
7214 .update(cx, |this, cx| {
7215 this.add_edit_breakpoint_block(
7216 anchor,
7217 breakpoint.as_ref(),
7218 BreakpointPromptEditAction::Condition,
7219 window,
7220 cx,
7221 );
7222 })
7223 .log_err();
7224 }
7225 })
7226 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7227 weak_editor
7228 .update(cx, |this, cx| {
7229 this.add_edit_breakpoint_block(
7230 anchor,
7231 breakpoint.as_ref(),
7232 BreakpointPromptEditAction::HitCondition,
7233 window,
7234 cx,
7235 );
7236 })
7237 .log_err();
7238 })
7239 })
7240 }
7241
7242 fn render_breakpoint(
7243 &self,
7244 position: Anchor,
7245 row: DisplayRow,
7246 breakpoint: &Breakpoint,
7247 state: Option<BreakpointSessionState>,
7248 cx: &mut Context<Self>,
7249 ) -> IconButton {
7250 let is_rejected = state.is_some_and(|s| !s.verified);
7251 // Is it a breakpoint that shows up when hovering over gutter?
7252 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7253 (false, false),
7254 |PhantomBreakpointIndicator {
7255 is_active,
7256 display_row,
7257 collides_with_existing_breakpoint,
7258 }| {
7259 (
7260 is_active && display_row == row,
7261 collides_with_existing_breakpoint,
7262 )
7263 },
7264 );
7265
7266 let (color, icon) = {
7267 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7268 (false, false) => ui::IconName::DebugBreakpoint,
7269 (true, false) => ui::IconName::DebugLogBreakpoint,
7270 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7271 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7272 };
7273
7274 let color = if is_phantom {
7275 Color::Hint
7276 } else if is_rejected {
7277 Color::Disabled
7278 } else {
7279 Color::Debugger
7280 };
7281
7282 (color, icon)
7283 };
7284
7285 let breakpoint = Arc::from(breakpoint.clone());
7286
7287 let alt_as_text = gpui::Keystroke {
7288 modifiers: Modifiers::secondary_key(),
7289 ..Default::default()
7290 };
7291 let primary_action_text = if breakpoint.is_disabled() {
7292 "Enable breakpoint"
7293 } else if is_phantom && !collides_with_existing {
7294 "Set breakpoint"
7295 } else {
7296 "Unset breakpoint"
7297 };
7298 let focus_handle = self.focus_handle.clone();
7299
7300 let meta = if is_rejected {
7301 SharedString::from("No executable code is associated with this line.")
7302 } else if collides_with_existing && !breakpoint.is_disabled() {
7303 SharedString::from(format!(
7304 "{alt_as_text}-click to disable,\nright-click for more options."
7305 ))
7306 } else {
7307 SharedString::from("Right-click for more options.")
7308 };
7309 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7310 .icon_size(IconSize::XSmall)
7311 .size(ui::ButtonSize::None)
7312 .when(is_rejected, |this| {
7313 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7314 })
7315 .icon_color(color)
7316 .style(ButtonStyle::Transparent)
7317 .on_click(cx.listener({
7318 let breakpoint = breakpoint.clone();
7319
7320 move |editor, event: &ClickEvent, window, cx| {
7321 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7322 BreakpointEditAction::InvertState
7323 } else {
7324 BreakpointEditAction::Toggle
7325 };
7326
7327 window.focus(&editor.focus_handle(cx));
7328 editor.edit_breakpoint_at_anchor(
7329 position,
7330 breakpoint.as_ref().clone(),
7331 edit_action,
7332 cx,
7333 );
7334 }
7335 }))
7336 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7337 editor.set_breakpoint_context_menu(
7338 row,
7339 Some(position),
7340 event.down.position,
7341 window,
7342 cx,
7343 );
7344 }))
7345 .tooltip(move |window, cx| {
7346 Tooltip::with_meta_in(
7347 primary_action_text,
7348 Some(&ToggleBreakpoint),
7349 meta.clone(),
7350 &focus_handle,
7351 window,
7352 cx,
7353 )
7354 })
7355 }
7356
7357 fn build_tasks_context(
7358 project: &Entity<Project>,
7359 buffer: &Entity<Buffer>,
7360 buffer_row: u32,
7361 tasks: &Arc<RunnableTasks>,
7362 cx: &mut Context<Self>,
7363 ) -> Task<Option<task::TaskContext>> {
7364 let position = Point::new(buffer_row, tasks.column);
7365 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7366 let location = Location {
7367 buffer: buffer.clone(),
7368 range: range_start..range_start,
7369 };
7370 // Fill in the environmental variables from the tree-sitter captures
7371 let mut captured_task_variables = TaskVariables::default();
7372 for (capture_name, value) in tasks.extra_variables.clone() {
7373 captured_task_variables.insert(
7374 task::VariableName::Custom(capture_name.into()),
7375 value.clone(),
7376 );
7377 }
7378 project.update(cx, |project, cx| {
7379 project.task_store().update(cx, |task_store, cx| {
7380 task_store.task_context_for_location(captured_task_variables, location, cx)
7381 })
7382 })
7383 }
7384
7385 pub fn spawn_nearest_task(
7386 &mut self,
7387 action: &SpawnNearestTask,
7388 window: &mut Window,
7389 cx: &mut Context<Self>,
7390 ) {
7391 let Some((workspace, _)) = self.workspace.clone() else {
7392 return;
7393 };
7394 let Some(project) = self.project.clone() else {
7395 return;
7396 };
7397
7398 // Try to find a closest, enclosing node using tree-sitter that has a
7399 // task
7400 let Some((buffer, buffer_row, tasks)) = self
7401 .find_enclosing_node_task(cx)
7402 // Or find the task that's closest in row-distance.
7403 .or_else(|| self.find_closest_task(cx))
7404 else {
7405 return;
7406 };
7407
7408 let reveal_strategy = action.reveal;
7409 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7410 cx.spawn_in(window, async move |_, cx| {
7411 let context = task_context.await?;
7412 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7413
7414 let resolved = &mut resolved_task.resolved;
7415 resolved.reveal = reveal_strategy;
7416
7417 workspace
7418 .update_in(cx, |workspace, window, cx| {
7419 workspace.schedule_resolved_task(
7420 task_source_kind,
7421 resolved_task,
7422 false,
7423 window,
7424 cx,
7425 );
7426 })
7427 .ok()
7428 })
7429 .detach();
7430 }
7431
7432 fn find_closest_task(
7433 &mut self,
7434 cx: &mut Context<Self>,
7435 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7436 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7437
7438 let ((buffer_id, row), tasks) = self
7439 .tasks
7440 .iter()
7441 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7442
7443 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7444 let tasks = Arc::new(tasks.to_owned());
7445 Some((buffer, *row, tasks))
7446 }
7447
7448 fn find_enclosing_node_task(
7449 &mut self,
7450 cx: &mut Context<Self>,
7451 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7452 let snapshot = self.buffer.read(cx).snapshot(cx);
7453 let offset = self.selections.newest::<usize>(cx).head();
7454 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7455 let buffer_id = excerpt.buffer().remote_id();
7456
7457 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7458 let mut cursor = layer.node().walk();
7459
7460 while cursor.goto_first_child_for_byte(offset).is_some() {
7461 if cursor.node().end_byte() == offset {
7462 cursor.goto_next_sibling();
7463 }
7464 }
7465
7466 // Ascend to the smallest ancestor that contains the range and has a task.
7467 loop {
7468 let node = cursor.node();
7469 let node_range = node.byte_range();
7470 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7471
7472 // Check if this node contains our offset
7473 if node_range.start <= offset && node_range.end >= offset {
7474 // If it contains offset, check for task
7475 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7476 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7477 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7478 }
7479 }
7480
7481 if !cursor.goto_parent() {
7482 break;
7483 }
7484 }
7485 None
7486 }
7487
7488 fn render_run_indicator(
7489 &self,
7490 _style: &EditorStyle,
7491 is_active: bool,
7492 row: DisplayRow,
7493 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7494 cx: &mut Context<Self>,
7495 ) -> IconButton {
7496 let color = Color::Muted;
7497 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7498
7499 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7500 .shape(ui::IconButtonShape::Square)
7501 .icon_size(IconSize::XSmall)
7502 .icon_color(color)
7503 .toggle_state(is_active)
7504 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7505 let quick_launch = e.down.button == MouseButton::Left;
7506 window.focus(&editor.focus_handle(cx));
7507 editor.toggle_code_actions(
7508 &ToggleCodeActions {
7509 deployed_from: Some(CodeActionSource::Indicator(row)),
7510 quick_launch,
7511 },
7512 window,
7513 cx,
7514 );
7515 }))
7516 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7517 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7518 }))
7519 }
7520
7521 pub fn context_menu_visible(&self) -> bool {
7522 !self.edit_prediction_preview_is_active()
7523 && self
7524 .context_menu
7525 .borrow()
7526 .as_ref()
7527 .map_or(false, |menu| menu.visible())
7528 }
7529
7530 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7531 self.context_menu
7532 .borrow()
7533 .as_ref()
7534 .map(|menu| menu.origin())
7535 }
7536
7537 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7538 self.context_menu_options = Some(options);
7539 }
7540
7541 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7542 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7543
7544 fn render_edit_prediction_popover(
7545 &mut self,
7546 text_bounds: &Bounds<Pixels>,
7547 content_origin: gpui::Point<Pixels>,
7548 right_margin: Pixels,
7549 editor_snapshot: &EditorSnapshot,
7550 visible_row_range: Range<DisplayRow>,
7551 scroll_top: f32,
7552 scroll_bottom: f32,
7553 line_layouts: &[LineWithInvisibles],
7554 line_height: Pixels,
7555 scroll_pixel_position: gpui::Point<Pixels>,
7556 newest_selection_head: Option<DisplayPoint>,
7557 editor_width: Pixels,
7558 style: &EditorStyle,
7559 window: &mut Window,
7560 cx: &mut App,
7561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7562 if self.mode().is_minimap() {
7563 return None;
7564 }
7565 let active_inline_completion = self.active_inline_completion.as_ref()?;
7566
7567 if self.edit_prediction_visible_in_cursor_popover(true) {
7568 return None;
7569 }
7570
7571 match &active_inline_completion.completion {
7572 InlineCompletion::Move { target, .. } => {
7573 let target_display_point = target.to_display_point(editor_snapshot);
7574
7575 if self.edit_prediction_requires_modifier() {
7576 if !self.edit_prediction_preview_is_active() {
7577 return None;
7578 }
7579
7580 self.render_edit_prediction_modifier_jump_popover(
7581 text_bounds,
7582 content_origin,
7583 visible_row_range,
7584 line_layouts,
7585 line_height,
7586 scroll_pixel_position,
7587 newest_selection_head,
7588 target_display_point,
7589 window,
7590 cx,
7591 )
7592 } else {
7593 self.render_edit_prediction_eager_jump_popover(
7594 text_bounds,
7595 content_origin,
7596 editor_snapshot,
7597 visible_row_range,
7598 scroll_top,
7599 scroll_bottom,
7600 line_height,
7601 scroll_pixel_position,
7602 target_display_point,
7603 editor_width,
7604 window,
7605 cx,
7606 )
7607 }
7608 }
7609 InlineCompletion::Edit {
7610 display_mode: EditDisplayMode::Inline,
7611 ..
7612 } => None,
7613 InlineCompletion::Edit {
7614 display_mode: EditDisplayMode::TabAccept,
7615 edits,
7616 ..
7617 } => {
7618 let range = &edits.first()?.0;
7619 let target_display_point = range.end.to_display_point(editor_snapshot);
7620
7621 self.render_edit_prediction_end_of_line_popover(
7622 "Accept",
7623 editor_snapshot,
7624 visible_row_range,
7625 target_display_point,
7626 line_height,
7627 scroll_pixel_position,
7628 content_origin,
7629 editor_width,
7630 window,
7631 cx,
7632 )
7633 }
7634 InlineCompletion::Edit {
7635 edits,
7636 edit_preview,
7637 display_mode: EditDisplayMode::DiffPopover,
7638 snapshot,
7639 } => self.render_edit_prediction_diff_popover(
7640 text_bounds,
7641 content_origin,
7642 right_margin,
7643 editor_snapshot,
7644 visible_row_range,
7645 line_layouts,
7646 line_height,
7647 scroll_pixel_position,
7648 newest_selection_head,
7649 editor_width,
7650 style,
7651 edits,
7652 edit_preview,
7653 snapshot,
7654 window,
7655 cx,
7656 ),
7657 }
7658 }
7659
7660 fn render_edit_prediction_modifier_jump_popover(
7661 &mut self,
7662 text_bounds: &Bounds<Pixels>,
7663 content_origin: gpui::Point<Pixels>,
7664 visible_row_range: Range<DisplayRow>,
7665 line_layouts: &[LineWithInvisibles],
7666 line_height: Pixels,
7667 scroll_pixel_position: gpui::Point<Pixels>,
7668 newest_selection_head: Option<DisplayPoint>,
7669 target_display_point: DisplayPoint,
7670 window: &mut Window,
7671 cx: &mut App,
7672 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7673 let scrolled_content_origin =
7674 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7675
7676 const SCROLL_PADDING_Y: Pixels = px(12.);
7677
7678 if target_display_point.row() < visible_row_range.start {
7679 return self.render_edit_prediction_scroll_popover(
7680 |_| SCROLL_PADDING_Y,
7681 IconName::ArrowUp,
7682 visible_row_range,
7683 line_layouts,
7684 newest_selection_head,
7685 scrolled_content_origin,
7686 window,
7687 cx,
7688 );
7689 } else if target_display_point.row() >= visible_row_range.end {
7690 return self.render_edit_prediction_scroll_popover(
7691 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7692 IconName::ArrowDown,
7693 visible_row_range,
7694 line_layouts,
7695 newest_selection_head,
7696 scrolled_content_origin,
7697 window,
7698 cx,
7699 );
7700 }
7701
7702 const POLE_WIDTH: Pixels = px(2.);
7703
7704 let line_layout =
7705 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7706 let target_column = target_display_point.column() as usize;
7707
7708 let target_x = line_layout.x_for_index(target_column);
7709 let target_y =
7710 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7711
7712 let flag_on_right = target_x < text_bounds.size.width / 2.;
7713
7714 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7715 border_color.l += 0.001;
7716
7717 let mut element = v_flex()
7718 .items_end()
7719 .when(flag_on_right, |el| el.items_start())
7720 .child(if flag_on_right {
7721 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7722 .rounded_bl(px(0.))
7723 .rounded_tl(px(0.))
7724 .border_l_2()
7725 .border_color(border_color)
7726 } else {
7727 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7728 .rounded_br(px(0.))
7729 .rounded_tr(px(0.))
7730 .border_r_2()
7731 .border_color(border_color)
7732 })
7733 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7734 .into_any();
7735
7736 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7737
7738 let mut origin = scrolled_content_origin + point(target_x, target_y)
7739 - point(
7740 if flag_on_right {
7741 POLE_WIDTH
7742 } else {
7743 size.width - POLE_WIDTH
7744 },
7745 size.height - line_height,
7746 );
7747
7748 origin.x = origin.x.max(content_origin.x);
7749
7750 element.prepaint_at(origin, window, cx);
7751
7752 Some((element, origin))
7753 }
7754
7755 fn render_edit_prediction_scroll_popover(
7756 &mut self,
7757 to_y: impl Fn(Size<Pixels>) -> Pixels,
7758 scroll_icon: IconName,
7759 visible_row_range: Range<DisplayRow>,
7760 line_layouts: &[LineWithInvisibles],
7761 newest_selection_head: Option<DisplayPoint>,
7762 scrolled_content_origin: gpui::Point<Pixels>,
7763 window: &mut Window,
7764 cx: &mut App,
7765 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7766 let mut element = self
7767 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7768 .into_any();
7769
7770 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7771
7772 let cursor = newest_selection_head?;
7773 let cursor_row_layout =
7774 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7775 let cursor_column = cursor.column() as usize;
7776
7777 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7778
7779 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7780
7781 element.prepaint_at(origin, window, cx);
7782 Some((element, origin))
7783 }
7784
7785 fn render_edit_prediction_eager_jump_popover(
7786 &mut self,
7787 text_bounds: &Bounds<Pixels>,
7788 content_origin: gpui::Point<Pixels>,
7789 editor_snapshot: &EditorSnapshot,
7790 visible_row_range: Range<DisplayRow>,
7791 scroll_top: f32,
7792 scroll_bottom: f32,
7793 line_height: Pixels,
7794 scroll_pixel_position: gpui::Point<Pixels>,
7795 target_display_point: DisplayPoint,
7796 editor_width: Pixels,
7797 window: &mut Window,
7798 cx: &mut App,
7799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7800 if target_display_point.row().as_f32() < scroll_top {
7801 let mut element = self
7802 .render_edit_prediction_line_popover(
7803 "Jump to Edit",
7804 Some(IconName::ArrowUp),
7805 window,
7806 cx,
7807 )?
7808 .into_any();
7809
7810 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7811 let offset = point(
7812 (text_bounds.size.width - size.width) / 2.,
7813 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7814 );
7815
7816 let origin = text_bounds.origin + offset;
7817 element.prepaint_at(origin, window, cx);
7818 Some((element, origin))
7819 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7820 let mut element = self
7821 .render_edit_prediction_line_popover(
7822 "Jump to Edit",
7823 Some(IconName::ArrowDown),
7824 window,
7825 cx,
7826 )?
7827 .into_any();
7828
7829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7830 let offset = point(
7831 (text_bounds.size.width - size.width) / 2.,
7832 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7833 );
7834
7835 let origin = text_bounds.origin + offset;
7836 element.prepaint_at(origin, window, cx);
7837 Some((element, origin))
7838 } else {
7839 self.render_edit_prediction_end_of_line_popover(
7840 "Jump to Edit",
7841 editor_snapshot,
7842 visible_row_range,
7843 target_display_point,
7844 line_height,
7845 scroll_pixel_position,
7846 content_origin,
7847 editor_width,
7848 window,
7849 cx,
7850 )
7851 }
7852 }
7853
7854 fn render_edit_prediction_end_of_line_popover(
7855 self: &mut Editor,
7856 label: &'static str,
7857 editor_snapshot: &EditorSnapshot,
7858 visible_row_range: Range<DisplayRow>,
7859 target_display_point: DisplayPoint,
7860 line_height: Pixels,
7861 scroll_pixel_position: gpui::Point<Pixels>,
7862 content_origin: gpui::Point<Pixels>,
7863 editor_width: Pixels,
7864 window: &mut Window,
7865 cx: &mut App,
7866 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7867 let target_line_end = DisplayPoint::new(
7868 target_display_point.row(),
7869 editor_snapshot.line_len(target_display_point.row()),
7870 );
7871
7872 let mut element = self
7873 .render_edit_prediction_line_popover(label, None, window, cx)?
7874 .into_any();
7875
7876 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7877
7878 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7879
7880 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7881 let mut origin = start_point
7882 + line_origin
7883 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7884 origin.x = origin.x.max(content_origin.x);
7885
7886 let max_x = content_origin.x + editor_width - size.width;
7887
7888 if origin.x > max_x {
7889 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7890
7891 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7892 origin.y += offset;
7893 IconName::ArrowUp
7894 } else {
7895 origin.y -= offset;
7896 IconName::ArrowDown
7897 };
7898
7899 element = self
7900 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7901 .into_any();
7902
7903 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7904
7905 origin.x = content_origin.x + editor_width - size.width - px(2.);
7906 }
7907
7908 element.prepaint_at(origin, window, cx);
7909 Some((element, origin))
7910 }
7911
7912 fn render_edit_prediction_diff_popover(
7913 self: &Editor,
7914 text_bounds: &Bounds<Pixels>,
7915 content_origin: gpui::Point<Pixels>,
7916 right_margin: Pixels,
7917 editor_snapshot: &EditorSnapshot,
7918 visible_row_range: Range<DisplayRow>,
7919 line_layouts: &[LineWithInvisibles],
7920 line_height: Pixels,
7921 scroll_pixel_position: gpui::Point<Pixels>,
7922 newest_selection_head: Option<DisplayPoint>,
7923 editor_width: Pixels,
7924 style: &EditorStyle,
7925 edits: &Vec<(Range<Anchor>, String)>,
7926 edit_preview: &Option<language::EditPreview>,
7927 snapshot: &language::BufferSnapshot,
7928 window: &mut Window,
7929 cx: &mut App,
7930 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7931 let edit_start = edits
7932 .first()
7933 .unwrap()
7934 .0
7935 .start
7936 .to_display_point(editor_snapshot);
7937 let edit_end = edits
7938 .last()
7939 .unwrap()
7940 .0
7941 .end
7942 .to_display_point(editor_snapshot);
7943
7944 let is_visible = visible_row_range.contains(&edit_start.row())
7945 || visible_row_range.contains(&edit_end.row());
7946 if !is_visible {
7947 return None;
7948 }
7949
7950 let highlighted_edits =
7951 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7952
7953 let styled_text = highlighted_edits.to_styled_text(&style.text);
7954 let line_count = highlighted_edits.text.lines().count();
7955
7956 const BORDER_WIDTH: Pixels = px(1.);
7957
7958 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7959 let has_keybind = keybind.is_some();
7960
7961 let mut element = h_flex()
7962 .items_start()
7963 .child(
7964 h_flex()
7965 .bg(cx.theme().colors().editor_background)
7966 .border(BORDER_WIDTH)
7967 .shadow_sm()
7968 .border_color(cx.theme().colors().border)
7969 .rounded_l_lg()
7970 .when(line_count > 1, |el| el.rounded_br_lg())
7971 .pr_1()
7972 .child(styled_text),
7973 )
7974 .child(
7975 h_flex()
7976 .h(line_height + BORDER_WIDTH * 2.)
7977 .px_1p5()
7978 .gap_1()
7979 // Workaround: For some reason, there's a gap if we don't do this
7980 .ml(-BORDER_WIDTH)
7981 .shadow(smallvec![gpui::BoxShadow {
7982 color: gpui::black().opacity(0.05),
7983 offset: point(px(1.), px(1.)),
7984 blur_radius: px(2.),
7985 spread_radius: px(0.),
7986 }])
7987 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7988 .border(BORDER_WIDTH)
7989 .border_color(cx.theme().colors().border)
7990 .rounded_r_lg()
7991 .id("edit_prediction_diff_popover_keybind")
7992 .when(!has_keybind, |el| {
7993 let status_colors = cx.theme().status();
7994
7995 el.bg(status_colors.error_background)
7996 .border_color(status_colors.error.opacity(0.6))
7997 .child(Icon::new(IconName::Info).color(Color::Error))
7998 .cursor_default()
7999 .hoverable_tooltip(move |_window, cx| {
8000 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8001 })
8002 })
8003 .children(keybind),
8004 )
8005 .into_any();
8006
8007 let longest_row =
8008 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8009 let longest_line_width = if visible_row_range.contains(&longest_row) {
8010 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8011 } else {
8012 layout_line(
8013 longest_row,
8014 editor_snapshot,
8015 style,
8016 editor_width,
8017 |_| false,
8018 window,
8019 cx,
8020 )
8021 .width
8022 };
8023
8024 let viewport_bounds =
8025 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8026 right: -right_margin,
8027 ..Default::default()
8028 });
8029
8030 let x_after_longest =
8031 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8032 - scroll_pixel_position.x;
8033
8034 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8035
8036 // Fully visible if it can be displayed within the window (allow overlapping other
8037 // panes). However, this is only allowed if the popover starts within text_bounds.
8038 let can_position_to_the_right = x_after_longest < text_bounds.right()
8039 && x_after_longest + element_bounds.width < viewport_bounds.right();
8040
8041 let mut origin = if can_position_to_the_right {
8042 point(
8043 x_after_longest,
8044 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8045 - scroll_pixel_position.y,
8046 )
8047 } else {
8048 let cursor_row = newest_selection_head.map(|head| head.row());
8049 let above_edit = edit_start
8050 .row()
8051 .0
8052 .checked_sub(line_count as u32)
8053 .map(DisplayRow);
8054 let below_edit = Some(edit_end.row() + 1);
8055 let above_cursor =
8056 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8057 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8058
8059 // Place the edit popover adjacent to the edit if there is a location
8060 // available that is onscreen and does not obscure the cursor. Otherwise,
8061 // place it adjacent to the cursor.
8062 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8063 .into_iter()
8064 .flatten()
8065 .find(|&start_row| {
8066 let end_row = start_row + line_count as u32;
8067 visible_row_range.contains(&start_row)
8068 && visible_row_range.contains(&end_row)
8069 && cursor_row.map_or(true, |cursor_row| {
8070 !((start_row..end_row).contains(&cursor_row))
8071 })
8072 })?;
8073
8074 content_origin
8075 + point(
8076 -scroll_pixel_position.x,
8077 row_target.as_f32() * line_height - scroll_pixel_position.y,
8078 )
8079 };
8080
8081 origin.x -= BORDER_WIDTH;
8082
8083 window.defer_draw(element, origin, 1);
8084
8085 // Do not return an element, since it will already be drawn due to defer_draw.
8086 None
8087 }
8088
8089 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8090 px(30.)
8091 }
8092
8093 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8094 if self.read_only(cx) {
8095 cx.theme().players().read_only()
8096 } else {
8097 self.style.as_ref().unwrap().local_player
8098 }
8099 }
8100
8101 fn render_edit_prediction_accept_keybind(
8102 &self,
8103 window: &mut Window,
8104 cx: &App,
8105 ) -> Option<AnyElement> {
8106 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8107 let accept_keystroke = accept_binding.keystroke()?;
8108
8109 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8110
8111 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8112 Color::Accent
8113 } else {
8114 Color::Muted
8115 };
8116
8117 h_flex()
8118 .px_0p5()
8119 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8120 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8121 .text_size(TextSize::XSmall.rems(cx))
8122 .child(h_flex().children(ui::render_modifiers(
8123 &accept_keystroke.modifiers,
8124 PlatformStyle::platform(),
8125 Some(modifiers_color),
8126 Some(IconSize::XSmall.rems().into()),
8127 true,
8128 )))
8129 .when(is_platform_style_mac, |parent| {
8130 parent.child(accept_keystroke.key.clone())
8131 })
8132 .when(!is_platform_style_mac, |parent| {
8133 parent.child(
8134 Key::new(
8135 util::capitalize(&accept_keystroke.key),
8136 Some(Color::Default),
8137 )
8138 .size(Some(IconSize::XSmall.rems().into())),
8139 )
8140 })
8141 .into_any()
8142 .into()
8143 }
8144
8145 fn render_edit_prediction_line_popover(
8146 &self,
8147 label: impl Into<SharedString>,
8148 icon: Option<IconName>,
8149 window: &mut Window,
8150 cx: &App,
8151 ) -> Option<Stateful<Div>> {
8152 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8153
8154 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8155 let has_keybind = keybind.is_some();
8156
8157 let result = h_flex()
8158 .id("ep-line-popover")
8159 .py_0p5()
8160 .pl_1()
8161 .pr(padding_right)
8162 .gap_1()
8163 .rounded_md()
8164 .border_1()
8165 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8166 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8167 .shadow_sm()
8168 .when(!has_keybind, |el| {
8169 let status_colors = cx.theme().status();
8170
8171 el.bg(status_colors.error_background)
8172 .border_color(status_colors.error.opacity(0.6))
8173 .pl_2()
8174 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8175 .cursor_default()
8176 .hoverable_tooltip(move |_window, cx| {
8177 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8178 })
8179 })
8180 .children(keybind)
8181 .child(
8182 Label::new(label)
8183 .size(LabelSize::Small)
8184 .when(!has_keybind, |el| {
8185 el.color(cx.theme().status().error.into()).strikethrough()
8186 }),
8187 )
8188 .when(!has_keybind, |el| {
8189 el.child(
8190 h_flex().ml_1().child(
8191 Icon::new(IconName::Info)
8192 .size(IconSize::Small)
8193 .color(cx.theme().status().error.into()),
8194 ),
8195 )
8196 })
8197 .when_some(icon, |element, icon| {
8198 element.child(
8199 div()
8200 .mt(px(1.5))
8201 .child(Icon::new(icon).size(IconSize::Small)),
8202 )
8203 });
8204
8205 Some(result)
8206 }
8207
8208 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8209 let accent_color = cx.theme().colors().text_accent;
8210 let editor_bg_color = cx.theme().colors().editor_background;
8211 editor_bg_color.blend(accent_color.opacity(0.1))
8212 }
8213
8214 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8215 let accent_color = cx.theme().colors().text_accent;
8216 let editor_bg_color = cx.theme().colors().editor_background;
8217 editor_bg_color.blend(accent_color.opacity(0.6))
8218 }
8219
8220 fn render_edit_prediction_cursor_popover(
8221 &self,
8222 min_width: Pixels,
8223 max_width: Pixels,
8224 cursor_point: Point,
8225 style: &EditorStyle,
8226 accept_keystroke: Option<&gpui::Keystroke>,
8227 _window: &Window,
8228 cx: &mut Context<Editor>,
8229 ) -> Option<AnyElement> {
8230 let provider = self.edit_prediction_provider.as_ref()?;
8231
8232 if provider.provider.needs_terms_acceptance(cx) {
8233 return Some(
8234 h_flex()
8235 .min_w(min_width)
8236 .flex_1()
8237 .px_2()
8238 .py_1()
8239 .gap_3()
8240 .elevation_2(cx)
8241 .hover(|style| style.bg(cx.theme().colors().element_hover))
8242 .id("accept-terms")
8243 .cursor_pointer()
8244 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8245 .on_click(cx.listener(|this, _event, window, cx| {
8246 cx.stop_propagation();
8247 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8248 window.dispatch_action(
8249 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8250 cx,
8251 );
8252 }))
8253 .child(
8254 h_flex()
8255 .flex_1()
8256 .gap_2()
8257 .child(Icon::new(IconName::ZedPredict))
8258 .child(Label::new("Accept Terms of Service"))
8259 .child(div().w_full())
8260 .child(
8261 Icon::new(IconName::ArrowUpRight)
8262 .color(Color::Muted)
8263 .size(IconSize::Small),
8264 )
8265 .into_any_element(),
8266 )
8267 .into_any(),
8268 );
8269 }
8270
8271 let is_refreshing = provider.provider.is_refreshing(cx);
8272
8273 fn pending_completion_container() -> Div {
8274 h_flex()
8275 .h_full()
8276 .flex_1()
8277 .gap_2()
8278 .child(Icon::new(IconName::ZedPredict))
8279 }
8280
8281 let completion = match &self.active_inline_completion {
8282 Some(prediction) => {
8283 if !self.has_visible_completions_menu() {
8284 const RADIUS: Pixels = px(6.);
8285 const BORDER_WIDTH: Pixels = px(1.);
8286
8287 return Some(
8288 h_flex()
8289 .elevation_2(cx)
8290 .border(BORDER_WIDTH)
8291 .border_color(cx.theme().colors().border)
8292 .when(accept_keystroke.is_none(), |el| {
8293 el.border_color(cx.theme().status().error)
8294 })
8295 .rounded(RADIUS)
8296 .rounded_tl(px(0.))
8297 .overflow_hidden()
8298 .child(div().px_1p5().child(match &prediction.completion {
8299 InlineCompletion::Move { target, snapshot } => {
8300 use text::ToPoint as _;
8301 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8302 {
8303 Icon::new(IconName::ZedPredictDown)
8304 } else {
8305 Icon::new(IconName::ZedPredictUp)
8306 }
8307 }
8308 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8309 }))
8310 .child(
8311 h_flex()
8312 .gap_1()
8313 .py_1()
8314 .px_2()
8315 .rounded_r(RADIUS - BORDER_WIDTH)
8316 .border_l_1()
8317 .border_color(cx.theme().colors().border)
8318 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8319 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8320 el.child(
8321 Label::new("Hold")
8322 .size(LabelSize::Small)
8323 .when(accept_keystroke.is_none(), |el| {
8324 el.strikethrough()
8325 })
8326 .line_height_style(LineHeightStyle::UiLabel),
8327 )
8328 })
8329 .id("edit_prediction_cursor_popover_keybind")
8330 .when(accept_keystroke.is_none(), |el| {
8331 let status_colors = cx.theme().status();
8332
8333 el.bg(status_colors.error_background)
8334 .border_color(status_colors.error.opacity(0.6))
8335 .child(Icon::new(IconName::Info).color(Color::Error))
8336 .cursor_default()
8337 .hoverable_tooltip(move |_window, cx| {
8338 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8339 .into()
8340 })
8341 })
8342 .when_some(
8343 accept_keystroke.as_ref(),
8344 |el, accept_keystroke| {
8345 el.child(h_flex().children(ui::render_modifiers(
8346 &accept_keystroke.modifiers,
8347 PlatformStyle::platform(),
8348 Some(Color::Default),
8349 Some(IconSize::XSmall.rems().into()),
8350 false,
8351 )))
8352 },
8353 ),
8354 )
8355 .into_any(),
8356 );
8357 }
8358
8359 self.render_edit_prediction_cursor_popover_preview(
8360 prediction,
8361 cursor_point,
8362 style,
8363 cx,
8364 )?
8365 }
8366
8367 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8368 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8369 stale_completion,
8370 cursor_point,
8371 style,
8372 cx,
8373 )?,
8374
8375 None => {
8376 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8377 }
8378 },
8379
8380 None => pending_completion_container().child(Label::new("No Prediction")),
8381 };
8382
8383 let completion = if is_refreshing {
8384 completion
8385 .with_animation(
8386 "loading-completion",
8387 Animation::new(Duration::from_secs(2))
8388 .repeat()
8389 .with_easing(pulsating_between(0.4, 0.8)),
8390 |label, delta| label.opacity(delta),
8391 )
8392 .into_any_element()
8393 } else {
8394 completion.into_any_element()
8395 };
8396
8397 let has_completion = self.active_inline_completion.is_some();
8398
8399 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8400 Some(
8401 h_flex()
8402 .min_w(min_width)
8403 .max_w(max_width)
8404 .flex_1()
8405 .elevation_2(cx)
8406 .border_color(cx.theme().colors().border)
8407 .child(
8408 div()
8409 .flex_1()
8410 .py_1()
8411 .px_2()
8412 .overflow_hidden()
8413 .child(completion),
8414 )
8415 .when_some(accept_keystroke, |el, accept_keystroke| {
8416 if !accept_keystroke.modifiers.modified() {
8417 return el;
8418 }
8419
8420 el.child(
8421 h_flex()
8422 .h_full()
8423 .border_l_1()
8424 .rounded_r_lg()
8425 .border_color(cx.theme().colors().border)
8426 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8427 .gap_1()
8428 .py_1()
8429 .px_2()
8430 .child(
8431 h_flex()
8432 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8433 .when(is_platform_style_mac, |parent| parent.gap_1())
8434 .child(h_flex().children(ui::render_modifiers(
8435 &accept_keystroke.modifiers,
8436 PlatformStyle::platform(),
8437 Some(if !has_completion {
8438 Color::Muted
8439 } else {
8440 Color::Default
8441 }),
8442 None,
8443 false,
8444 ))),
8445 )
8446 .child(Label::new("Preview").into_any_element())
8447 .opacity(if has_completion { 1.0 } else { 0.4 }),
8448 )
8449 })
8450 .into_any(),
8451 )
8452 }
8453
8454 fn render_edit_prediction_cursor_popover_preview(
8455 &self,
8456 completion: &InlineCompletionState,
8457 cursor_point: Point,
8458 style: &EditorStyle,
8459 cx: &mut Context<Editor>,
8460 ) -> Option<Div> {
8461 use text::ToPoint as _;
8462
8463 fn render_relative_row_jump(
8464 prefix: impl Into<String>,
8465 current_row: u32,
8466 target_row: u32,
8467 ) -> Div {
8468 let (row_diff, arrow) = if target_row < current_row {
8469 (current_row - target_row, IconName::ArrowUp)
8470 } else {
8471 (target_row - current_row, IconName::ArrowDown)
8472 };
8473
8474 h_flex()
8475 .child(
8476 Label::new(format!("{}{}", prefix.into(), row_diff))
8477 .color(Color::Muted)
8478 .size(LabelSize::Small),
8479 )
8480 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8481 }
8482
8483 match &completion.completion {
8484 InlineCompletion::Move {
8485 target, snapshot, ..
8486 } => Some(
8487 h_flex()
8488 .px_2()
8489 .gap_2()
8490 .flex_1()
8491 .child(
8492 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8493 Icon::new(IconName::ZedPredictDown)
8494 } else {
8495 Icon::new(IconName::ZedPredictUp)
8496 },
8497 )
8498 .child(Label::new("Jump to Edit")),
8499 ),
8500
8501 InlineCompletion::Edit {
8502 edits,
8503 edit_preview,
8504 snapshot,
8505 display_mode: _,
8506 } => {
8507 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8508
8509 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8510 &snapshot,
8511 &edits,
8512 edit_preview.as_ref()?,
8513 true,
8514 cx,
8515 )
8516 .first_line_preview();
8517
8518 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8519 .with_default_highlights(&style.text, highlighted_edits.highlights);
8520
8521 let preview = h_flex()
8522 .gap_1()
8523 .min_w_16()
8524 .child(styled_text)
8525 .when(has_more_lines, |parent| parent.child("…"));
8526
8527 let left = if first_edit_row != cursor_point.row {
8528 render_relative_row_jump("", cursor_point.row, first_edit_row)
8529 .into_any_element()
8530 } else {
8531 Icon::new(IconName::ZedPredict).into_any_element()
8532 };
8533
8534 Some(
8535 h_flex()
8536 .h_full()
8537 .flex_1()
8538 .gap_2()
8539 .pr_1()
8540 .overflow_x_hidden()
8541 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8542 .child(left)
8543 .child(preview),
8544 )
8545 }
8546 }
8547 }
8548
8549 pub fn render_context_menu(
8550 &self,
8551 style: &EditorStyle,
8552 max_height_in_lines: u32,
8553 window: &mut Window,
8554 cx: &mut Context<Editor>,
8555 ) -> Option<AnyElement> {
8556 let menu = self.context_menu.borrow();
8557 let menu = menu.as_ref()?;
8558 if !menu.visible() {
8559 return None;
8560 };
8561 Some(menu.render(style, max_height_in_lines, window, cx))
8562 }
8563
8564 fn render_context_menu_aside(
8565 &mut self,
8566 max_size: Size<Pixels>,
8567 window: &mut Window,
8568 cx: &mut Context<Editor>,
8569 ) -> Option<AnyElement> {
8570 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8571 if menu.visible() {
8572 menu.render_aside(self, max_size, window, cx)
8573 } else {
8574 None
8575 }
8576 })
8577 }
8578
8579 fn hide_context_menu(
8580 &mut self,
8581 window: &mut Window,
8582 cx: &mut Context<Self>,
8583 ) -> Option<CodeContextMenu> {
8584 cx.notify();
8585 self.completion_tasks.clear();
8586 let context_menu = self.context_menu.borrow_mut().take();
8587 self.stale_inline_completion_in_menu.take();
8588 self.update_visible_inline_completion(window, cx);
8589 context_menu
8590 }
8591
8592 fn show_snippet_choices(
8593 &mut self,
8594 choices: &Vec<String>,
8595 selection: Range<Anchor>,
8596 cx: &mut Context<Self>,
8597 ) {
8598 if selection.start.buffer_id.is_none() {
8599 return;
8600 }
8601 let buffer_id = selection.start.buffer_id.unwrap();
8602 let buffer = self.buffer().read(cx).buffer(buffer_id);
8603 let id = post_inc(&mut self.next_completion_id);
8604 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8605
8606 if let Some(buffer) = buffer {
8607 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8608 CompletionsMenu::new_snippet_choices(
8609 id,
8610 true,
8611 choices,
8612 selection,
8613 buffer,
8614 snippet_sort_order,
8615 ),
8616 ));
8617 }
8618 }
8619
8620 pub fn insert_snippet(
8621 &mut self,
8622 insertion_ranges: &[Range<usize>],
8623 snippet: Snippet,
8624 window: &mut Window,
8625 cx: &mut Context<Self>,
8626 ) -> Result<()> {
8627 struct Tabstop<T> {
8628 is_end_tabstop: bool,
8629 ranges: Vec<Range<T>>,
8630 choices: Option<Vec<String>>,
8631 }
8632
8633 let tabstops = self.buffer.update(cx, |buffer, cx| {
8634 let snippet_text: Arc<str> = snippet.text.clone().into();
8635 let edits = insertion_ranges
8636 .iter()
8637 .cloned()
8638 .map(|range| (range, snippet_text.clone()));
8639 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8640
8641 let snapshot = &*buffer.read(cx);
8642 let snippet = &snippet;
8643 snippet
8644 .tabstops
8645 .iter()
8646 .map(|tabstop| {
8647 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8648 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8649 });
8650 let mut tabstop_ranges = tabstop
8651 .ranges
8652 .iter()
8653 .flat_map(|tabstop_range| {
8654 let mut delta = 0_isize;
8655 insertion_ranges.iter().map(move |insertion_range| {
8656 let insertion_start = insertion_range.start as isize + delta;
8657 delta +=
8658 snippet.text.len() as isize - insertion_range.len() as isize;
8659
8660 let start = ((insertion_start + tabstop_range.start) as usize)
8661 .min(snapshot.len());
8662 let end = ((insertion_start + tabstop_range.end) as usize)
8663 .min(snapshot.len());
8664 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8665 })
8666 })
8667 .collect::<Vec<_>>();
8668 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8669
8670 Tabstop {
8671 is_end_tabstop,
8672 ranges: tabstop_ranges,
8673 choices: tabstop.choices.clone(),
8674 }
8675 })
8676 .collect::<Vec<_>>()
8677 });
8678 if let Some(tabstop) = tabstops.first() {
8679 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8680 s.select_ranges(tabstop.ranges.iter().cloned());
8681 });
8682
8683 if let Some(choices) = &tabstop.choices {
8684 if let Some(selection) = tabstop.ranges.first() {
8685 self.show_snippet_choices(choices, selection.clone(), cx)
8686 }
8687 }
8688
8689 // If we're already at the last tabstop and it's at the end of the snippet,
8690 // we're done, we don't need to keep the state around.
8691 if !tabstop.is_end_tabstop {
8692 let choices = tabstops
8693 .iter()
8694 .map(|tabstop| tabstop.choices.clone())
8695 .collect();
8696
8697 let ranges = tabstops
8698 .into_iter()
8699 .map(|tabstop| tabstop.ranges)
8700 .collect::<Vec<_>>();
8701
8702 self.snippet_stack.push(SnippetState {
8703 active_index: 0,
8704 ranges,
8705 choices,
8706 });
8707 }
8708
8709 // Check whether the just-entered snippet ends with an auto-closable bracket.
8710 if self.autoclose_regions.is_empty() {
8711 let snapshot = self.buffer.read(cx).snapshot(cx);
8712 for selection in &mut self.selections.all::<Point>(cx) {
8713 let selection_head = selection.head();
8714 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8715 continue;
8716 };
8717
8718 let mut bracket_pair = None;
8719 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8720 let prev_chars = snapshot
8721 .reversed_chars_at(selection_head)
8722 .collect::<String>();
8723 for (pair, enabled) in scope.brackets() {
8724 if enabled
8725 && pair.close
8726 && prev_chars.starts_with(pair.start.as_str())
8727 && next_chars.starts_with(pair.end.as_str())
8728 {
8729 bracket_pair = Some(pair.clone());
8730 break;
8731 }
8732 }
8733 if let Some(pair) = bracket_pair {
8734 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8735 let autoclose_enabled =
8736 self.use_autoclose && snapshot_settings.use_autoclose;
8737 if autoclose_enabled {
8738 let start = snapshot.anchor_after(selection_head);
8739 let end = snapshot.anchor_after(selection_head);
8740 self.autoclose_regions.push(AutocloseRegion {
8741 selection_id: selection.id,
8742 range: start..end,
8743 pair,
8744 });
8745 }
8746 }
8747 }
8748 }
8749 }
8750 Ok(())
8751 }
8752
8753 pub fn move_to_next_snippet_tabstop(
8754 &mut self,
8755 window: &mut Window,
8756 cx: &mut Context<Self>,
8757 ) -> bool {
8758 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8759 }
8760
8761 pub fn move_to_prev_snippet_tabstop(
8762 &mut self,
8763 window: &mut Window,
8764 cx: &mut Context<Self>,
8765 ) -> bool {
8766 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8767 }
8768
8769 pub fn move_to_snippet_tabstop(
8770 &mut self,
8771 bias: Bias,
8772 window: &mut Window,
8773 cx: &mut Context<Self>,
8774 ) -> bool {
8775 if let Some(mut snippet) = self.snippet_stack.pop() {
8776 match bias {
8777 Bias::Left => {
8778 if snippet.active_index > 0 {
8779 snippet.active_index -= 1;
8780 } else {
8781 self.snippet_stack.push(snippet);
8782 return false;
8783 }
8784 }
8785 Bias::Right => {
8786 if snippet.active_index + 1 < snippet.ranges.len() {
8787 snippet.active_index += 1;
8788 } else {
8789 self.snippet_stack.push(snippet);
8790 return false;
8791 }
8792 }
8793 }
8794 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8795 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8796 s.select_anchor_ranges(current_ranges.iter().cloned())
8797 });
8798
8799 if let Some(choices) = &snippet.choices[snippet.active_index] {
8800 if let Some(selection) = current_ranges.first() {
8801 self.show_snippet_choices(&choices, selection.clone(), cx);
8802 }
8803 }
8804
8805 // If snippet state is not at the last tabstop, push it back on the stack
8806 if snippet.active_index + 1 < snippet.ranges.len() {
8807 self.snippet_stack.push(snippet);
8808 }
8809 return true;
8810 }
8811 }
8812
8813 false
8814 }
8815
8816 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8817 self.transact(window, cx, |this, window, cx| {
8818 this.select_all(&SelectAll, window, cx);
8819 this.insert("", window, cx);
8820 });
8821 }
8822
8823 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8824 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8825 self.transact(window, cx, |this, window, cx| {
8826 this.select_autoclose_pair(window, cx);
8827 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8828 if !this.linked_edit_ranges.is_empty() {
8829 let selections = this.selections.all::<MultiBufferPoint>(cx);
8830 let snapshot = this.buffer.read(cx).snapshot(cx);
8831
8832 for selection in selections.iter() {
8833 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8834 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8835 if selection_start.buffer_id != selection_end.buffer_id {
8836 continue;
8837 }
8838 if let Some(ranges) =
8839 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8840 {
8841 for (buffer, entries) in ranges {
8842 linked_ranges.entry(buffer).or_default().extend(entries);
8843 }
8844 }
8845 }
8846 }
8847
8848 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8849 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8850 for selection in &mut selections {
8851 if selection.is_empty() {
8852 let old_head = selection.head();
8853 let mut new_head =
8854 movement::left(&display_map, old_head.to_display_point(&display_map))
8855 .to_point(&display_map);
8856 if let Some((buffer, line_buffer_range)) = display_map
8857 .buffer_snapshot
8858 .buffer_line_for_row(MultiBufferRow(old_head.row))
8859 {
8860 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8861 let indent_len = match indent_size.kind {
8862 IndentKind::Space => {
8863 buffer.settings_at(line_buffer_range.start, cx).tab_size
8864 }
8865 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8866 };
8867 if old_head.column <= indent_size.len && old_head.column > 0 {
8868 let indent_len = indent_len.get();
8869 new_head = cmp::min(
8870 new_head,
8871 MultiBufferPoint::new(
8872 old_head.row,
8873 ((old_head.column - 1) / indent_len) * indent_len,
8874 ),
8875 );
8876 }
8877 }
8878
8879 selection.set_head(new_head, SelectionGoal::None);
8880 }
8881 }
8882
8883 this.signature_help_state.set_backspace_pressed(true);
8884 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8885 s.select(selections)
8886 });
8887 this.insert("", window, cx);
8888 let empty_str: Arc<str> = Arc::from("");
8889 for (buffer, edits) in linked_ranges {
8890 let snapshot = buffer.read(cx).snapshot();
8891 use text::ToPoint as TP;
8892
8893 let edits = edits
8894 .into_iter()
8895 .map(|range| {
8896 let end_point = TP::to_point(&range.end, &snapshot);
8897 let mut start_point = TP::to_point(&range.start, &snapshot);
8898
8899 if end_point == start_point {
8900 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8901 .saturating_sub(1);
8902 start_point =
8903 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8904 };
8905
8906 (start_point..end_point, empty_str.clone())
8907 })
8908 .sorted_by_key(|(range, _)| range.start)
8909 .collect::<Vec<_>>();
8910 buffer.update(cx, |this, cx| {
8911 this.edit(edits, None, cx);
8912 })
8913 }
8914 this.refresh_inline_completion(true, false, window, cx);
8915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8916 });
8917 }
8918
8919 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8920 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8921 self.transact(window, cx, |this, window, cx| {
8922 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8923 s.move_with(|map, selection| {
8924 if selection.is_empty() {
8925 let cursor = movement::right(map, selection.head());
8926 selection.end = cursor;
8927 selection.reversed = true;
8928 selection.goal = SelectionGoal::None;
8929 }
8930 })
8931 });
8932 this.insert("", window, cx);
8933 this.refresh_inline_completion(true, false, window, cx);
8934 });
8935 }
8936
8937 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8938 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8939 if self.move_to_prev_snippet_tabstop(window, cx) {
8940 return;
8941 }
8942 self.outdent(&Outdent, window, cx);
8943 }
8944
8945 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8946 if self.move_to_next_snippet_tabstop(window, cx) {
8947 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8948 return;
8949 }
8950 if self.read_only(cx) {
8951 return;
8952 }
8953 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8954 let mut selections = self.selections.all_adjusted(cx);
8955 let buffer = self.buffer.read(cx);
8956 let snapshot = buffer.snapshot(cx);
8957 let rows_iter = selections.iter().map(|s| s.head().row);
8958 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8959
8960 let has_some_cursor_in_whitespace = selections
8961 .iter()
8962 .filter(|selection| selection.is_empty())
8963 .any(|selection| {
8964 let cursor = selection.head();
8965 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8966 cursor.column < current_indent.len
8967 });
8968
8969 let mut edits = Vec::new();
8970 let mut prev_edited_row = 0;
8971 let mut row_delta = 0;
8972 for selection in &mut selections {
8973 if selection.start.row != prev_edited_row {
8974 row_delta = 0;
8975 }
8976 prev_edited_row = selection.end.row;
8977
8978 // If the selection is non-empty, then increase the indentation of the selected lines.
8979 if !selection.is_empty() {
8980 row_delta =
8981 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8982 continue;
8983 }
8984
8985 let cursor = selection.head();
8986 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8987 if let Some(suggested_indent) =
8988 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8989 {
8990 // Don't do anything if already at suggested indent
8991 // and there is any other cursor which is not
8992 if has_some_cursor_in_whitespace
8993 && cursor.column == current_indent.len
8994 && current_indent.len == suggested_indent.len
8995 {
8996 continue;
8997 }
8998
8999 // Adjust line and move cursor to suggested indent
9000 // if cursor is not at suggested indent
9001 if cursor.column < suggested_indent.len
9002 && cursor.column <= current_indent.len
9003 && current_indent.len <= suggested_indent.len
9004 {
9005 selection.start = Point::new(cursor.row, suggested_indent.len);
9006 selection.end = selection.start;
9007 if row_delta == 0 {
9008 edits.extend(Buffer::edit_for_indent_size_adjustment(
9009 cursor.row,
9010 current_indent,
9011 suggested_indent,
9012 ));
9013 row_delta = suggested_indent.len - current_indent.len;
9014 }
9015 continue;
9016 }
9017
9018 // If current indent is more than suggested indent
9019 // only move cursor to current indent and skip indent
9020 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9021 selection.start = Point::new(cursor.row, current_indent.len);
9022 selection.end = selection.start;
9023 continue;
9024 }
9025 }
9026
9027 // Otherwise, insert a hard or soft tab.
9028 let settings = buffer.language_settings_at(cursor, cx);
9029 let tab_size = if settings.hard_tabs {
9030 IndentSize::tab()
9031 } else {
9032 let tab_size = settings.tab_size.get();
9033 let indent_remainder = snapshot
9034 .text_for_range(Point::new(cursor.row, 0)..cursor)
9035 .flat_map(str::chars)
9036 .fold(row_delta % tab_size, |counter: u32, c| {
9037 if c == '\t' {
9038 0
9039 } else {
9040 (counter + 1) % tab_size
9041 }
9042 });
9043
9044 let chars_to_next_tab_stop = tab_size - indent_remainder;
9045 IndentSize::spaces(chars_to_next_tab_stop)
9046 };
9047 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9048 selection.end = selection.start;
9049 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9050 row_delta += tab_size.len;
9051 }
9052
9053 self.transact(window, cx, |this, window, cx| {
9054 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9055 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9056 s.select(selections)
9057 });
9058 this.refresh_inline_completion(true, false, window, cx);
9059 });
9060 }
9061
9062 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9063 if self.read_only(cx) {
9064 return;
9065 }
9066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9067 let mut selections = self.selections.all::<Point>(cx);
9068 let mut prev_edited_row = 0;
9069 let mut row_delta = 0;
9070 let mut edits = Vec::new();
9071 let buffer = self.buffer.read(cx);
9072 let snapshot = buffer.snapshot(cx);
9073 for selection in &mut selections {
9074 if selection.start.row != prev_edited_row {
9075 row_delta = 0;
9076 }
9077 prev_edited_row = selection.end.row;
9078
9079 row_delta =
9080 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9081 }
9082
9083 self.transact(window, cx, |this, window, cx| {
9084 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9085 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9086 s.select(selections)
9087 });
9088 });
9089 }
9090
9091 fn indent_selection(
9092 buffer: &MultiBuffer,
9093 snapshot: &MultiBufferSnapshot,
9094 selection: &mut Selection<Point>,
9095 edits: &mut Vec<(Range<Point>, String)>,
9096 delta_for_start_row: u32,
9097 cx: &App,
9098 ) -> u32 {
9099 let settings = buffer.language_settings_at(selection.start, cx);
9100 let tab_size = settings.tab_size.get();
9101 let indent_kind = if settings.hard_tabs {
9102 IndentKind::Tab
9103 } else {
9104 IndentKind::Space
9105 };
9106 let mut start_row = selection.start.row;
9107 let mut end_row = selection.end.row + 1;
9108
9109 // If a selection ends at the beginning of a line, don't indent
9110 // that last line.
9111 if selection.end.column == 0 && selection.end.row > selection.start.row {
9112 end_row -= 1;
9113 }
9114
9115 // Avoid re-indenting a row that has already been indented by a
9116 // previous selection, but still update this selection's column
9117 // to reflect that indentation.
9118 if delta_for_start_row > 0 {
9119 start_row += 1;
9120 selection.start.column += delta_for_start_row;
9121 if selection.end.row == selection.start.row {
9122 selection.end.column += delta_for_start_row;
9123 }
9124 }
9125
9126 let mut delta_for_end_row = 0;
9127 let has_multiple_rows = start_row + 1 != end_row;
9128 for row in start_row..end_row {
9129 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9130 let indent_delta = match (current_indent.kind, indent_kind) {
9131 (IndentKind::Space, IndentKind::Space) => {
9132 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9133 IndentSize::spaces(columns_to_next_tab_stop)
9134 }
9135 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9136 (_, IndentKind::Tab) => IndentSize::tab(),
9137 };
9138
9139 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9140 0
9141 } else {
9142 selection.start.column
9143 };
9144 let row_start = Point::new(row, start);
9145 edits.push((
9146 row_start..row_start,
9147 indent_delta.chars().collect::<String>(),
9148 ));
9149
9150 // Update this selection's endpoints to reflect the indentation.
9151 if row == selection.start.row {
9152 selection.start.column += indent_delta.len;
9153 }
9154 if row == selection.end.row {
9155 selection.end.column += indent_delta.len;
9156 delta_for_end_row = indent_delta.len;
9157 }
9158 }
9159
9160 if selection.start.row == selection.end.row {
9161 delta_for_start_row + delta_for_end_row
9162 } else {
9163 delta_for_end_row
9164 }
9165 }
9166
9167 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9168 if self.read_only(cx) {
9169 return;
9170 }
9171 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9173 let selections = self.selections.all::<Point>(cx);
9174 let mut deletion_ranges = Vec::new();
9175 let mut last_outdent = None;
9176 {
9177 let buffer = self.buffer.read(cx);
9178 let snapshot = buffer.snapshot(cx);
9179 for selection in &selections {
9180 let settings = buffer.language_settings_at(selection.start, cx);
9181 let tab_size = settings.tab_size.get();
9182 let mut rows = selection.spanned_rows(false, &display_map);
9183
9184 // Avoid re-outdenting a row that has already been outdented by a
9185 // previous selection.
9186 if let Some(last_row) = last_outdent {
9187 if last_row == rows.start {
9188 rows.start = rows.start.next_row();
9189 }
9190 }
9191 let has_multiple_rows = rows.len() > 1;
9192 for row in rows.iter_rows() {
9193 let indent_size = snapshot.indent_size_for_line(row);
9194 if indent_size.len > 0 {
9195 let deletion_len = match indent_size.kind {
9196 IndentKind::Space => {
9197 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9198 if columns_to_prev_tab_stop == 0 {
9199 tab_size
9200 } else {
9201 columns_to_prev_tab_stop
9202 }
9203 }
9204 IndentKind::Tab => 1,
9205 };
9206 let start = if has_multiple_rows
9207 || deletion_len > selection.start.column
9208 || indent_size.len < selection.start.column
9209 {
9210 0
9211 } else {
9212 selection.start.column - deletion_len
9213 };
9214 deletion_ranges.push(
9215 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9216 );
9217 last_outdent = Some(row);
9218 }
9219 }
9220 }
9221 }
9222
9223 self.transact(window, cx, |this, window, cx| {
9224 this.buffer.update(cx, |buffer, cx| {
9225 let empty_str: Arc<str> = Arc::default();
9226 buffer.edit(
9227 deletion_ranges
9228 .into_iter()
9229 .map(|range| (range, empty_str.clone())),
9230 None,
9231 cx,
9232 );
9233 });
9234 let selections = this.selections.all::<usize>(cx);
9235 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9236 s.select(selections)
9237 });
9238 });
9239 }
9240
9241 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9242 if self.read_only(cx) {
9243 return;
9244 }
9245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9246 let selections = self
9247 .selections
9248 .all::<usize>(cx)
9249 .into_iter()
9250 .map(|s| s.range());
9251
9252 self.transact(window, cx, |this, window, cx| {
9253 this.buffer.update(cx, |buffer, cx| {
9254 buffer.autoindent_ranges(selections, cx);
9255 });
9256 let selections = this.selections.all::<usize>(cx);
9257 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9258 s.select(selections)
9259 });
9260 });
9261 }
9262
9263 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9264 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9266 let selections = self.selections.all::<Point>(cx);
9267
9268 let mut new_cursors = Vec::new();
9269 let mut edit_ranges = Vec::new();
9270 let mut selections = selections.iter().peekable();
9271 while let Some(selection) = selections.next() {
9272 let mut rows = selection.spanned_rows(false, &display_map);
9273 let goal_display_column = selection.head().to_display_point(&display_map).column();
9274
9275 // Accumulate contiguous regions of rows that we want to delete.
9276 while let Some(next_selection) = selections.peek() {
9277 let next_rows = next_selection.spanned_rows(false, &display_map);
9278 if next_rows.start <= rows.end {
9279 rows.end = next_rows.end;
9280 selections.next().unwrap();
9281 } else {
9282 break;
9283 }
9284 }
9285
9286 let buffer = &display_map.buffer_snapshot;
9287 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9288 let edit_end;
9289 let cursor_buffer_row;
9290 if buffer.max_point().row >= rows.end.0 {
9291 // If there's a line after the range, delete the \n from the end of the row range
9292 // and position the cursor on the next line.
9293 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9294 cursor_buffer_row = rows.end;
9295 } else {
9296 // If there isn't a line after the range, delete the \n from the line before the
9297 // start of the row range and position the cursor there.
9298 edit_start = edit_start.saturating_sub(1);
9299 edit_end = buffer.len();
9300 cursor_buffer_row = rows.start.previous_row();
9301 }
9302
9303 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9304 *cursor.column_mut() =
9305 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9306
9307 new_cursors.push((
9308 selection.id,
9309 buffer.anchor_after(cursor.to_point(&display_map)),
9310 ));
9311 edit_ranges.push(edit_start..edit_end);
9312 }
9313
9314 self.transact(window, cx, |this, window, cx| {
9315 let buffer = this.buffer.update(cx, |buffer, cx| {
9316 let empty_str: Arc<str> = Arc::default();
9317 buffer.edit(
9318 edit_ranges
9319 .into_iter()
9320 .map(|range| (range, empty_str.clone())),
9321 None,
9322 cx,
9323 );
9324 buffer.snapshot(cx)
9325 });
9326 let new_selections = new_cursors
9327 .into_iter()
9328 .map(|(id, cursor)| {
9329 let cursor = cursor.to_point(&buffer);
9330 Selection {
9331 id,
9332 start: cursor,
9333 end: cursor,
9334 reversed: false,
9335 goal: SelectionGoal::None,
9336 }
9337 })
9338 .collect();
9339
9340 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9341 s.select(new_selections);
9342 });
9343 });
9344 }
9345
9346 pub fn join_lines_impl(
9347 &mut self,
9348 insert_whitespace: bool,
9349 window: &mut Window,
9350 cx: &mut Context<Self>,
9351 ) {
9352 if self.read_only(cx) {
9353 return;
9354 }
9355 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9356 for selection in self.selections.all::<Point>(cx) {
9357 let start = MultiBufferRow(selection.start.row);
9358 // Treat single line selections as if they include the next line. Otherwise this action
9359 // would do nothing for single line selections individual cursors.
9360 let end = if selection.start.row == selection.end.row {
9361 MultiBufferRow(selection.start.row + 1)
9362 } else {
9363 MultiBufferRow(selection.end.row)
9364 };
9365
9366 if let Some(last_row_range) = row_ranges.last_mut() {
9367 if start <= last_row_range.end {
9368 last_row_range.end = end;
9369 continue;
9370 }
9371 }
9372 row_ranges.push(start..end);
9373 }
9374
9375 let snapshot = self.buffer.read(cx).snapshot(cx);
9376 let mut cursor_positions = Vec::new();
9377 for row_range in &row_ranges {
9378 let anchor = snapshot.anchor_before(Point::new(
9379 row_range.end.previous_row().0,
9380 snapshot.line_len(row_range.end.previous_row()),
9381 ));
9382 cursor_positions.push(anchor..anchor);
9383 }
9384
9385 self.transact(window, cx, |this, window, cx| {
9386 for row_range in row_ranges.into_iter().rev() {
9387 for row in row_range.iter_rows().rev() {
9388 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9389 let next_line_row = row.next_row();
9390 let indent = snapshot.indent_size_for_line(next_line_row);
9391 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9392
9393 let replace =
9394 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9395 " "
9396 } else {
9397 ""
9398 };
9399
9400 this.buffer.update(cx, |buffer, cx| {
9401 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9402 });
9403 }
9404 }
9405
9406 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9407 s.select_anchor_ranges(cursor_positions)
9408 });
9409 });
9410 }
9411
9412 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9413 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9414 self.join_lines_impl(true, window, cx);
9415 }
9416
9417 pub fn sort_lines_case_sensitive(
9418 &mut self,
9419 _: &SortLinesCaseSensitive,
9420 window: &mut Window,
9421 cx: &mut Context<Self>,
9422 ) {
9423 self.manipulate_lines(window, cx, |lines| lines.sort())
9424 }
9425
9426 pub fn sort_lines_case_insensitive(
9427 &mut self,
9428 _: &SortLinesCaseInsensitive,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) {
9432 self.manipulate_lines(window, cx, |lines| {
9433 lines.sort_by_key(|line| line.to_lowercase())
9434 })
9435 }
9436
9437 pub fn unique_lines_case_insensitive(
9438 &mut self,
9439 _: &UniqueLinesCaseInsensitive,
9440 window: &mut Window,
9441 cx: &mut Context<Self>,
9442 ) {
9443 self.manipulate_lines(window, cx, |lines| {
9444 let mut seen = HashSet::default();
9445 lines.retain(|line| seen.insert(line.to_lowercase()));
9446 })
9447 }
9448
9449 pub fn unique_lines_case_sensitive(
9450 &mut self,
9451 _: &UniqueLinesCaseSensitive,
9452 window: &mut Window,
9453 cx: &mut Context<Self>,
9454 ) {
9455 self.manipulate_lines(window, cx, |lines| {
9456 let mut seen = HashSet::default();
9457 lines.retain(|line| seen.insert(*line));
9458 })
9459 }
9460
9461 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9462 let Some(project) = self.project.clone() else {
9463 return;
9464 };
9465 self.reload(project, window, cx)
9466 .detach_and_notify_err(window, cx);
9467 }
9468
9469 pub fn restore_file(
9470 &mut self,
9471 _: &::git::RestoreFile,
9472 window: &mut Window,
9473 cx: &mut Context<Self>,
9474 ) {
9475 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9476 let mut buffer_ids = HashSet::default();
9477 let snapshot = self.buffer().read(cx).snapshot(cx);
9478 for selection in self.selections.all::<usize>(cx) {
9479 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9480 }
9481
9482 let buffer = self.buffer().read(cx);
9483 let ranges = buffer_ids
9484 .into_iter()
9485 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9486 .collect::<Vec<_>>();
9487
9488 self.restore_hunks_in_ranges(ranges, window, cx);
9489 }
9490
9491 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9493 let selections = self
9494 .selections
9495 .all(cx)
9496 .into_iter()
9497 .map(|s| s.range())
9498 .collect();
9499 self.restore_hunks_in_ranges(selections, window, cx);
9500 }
9501
9502 pub fn restore_hunks_in_ranges(
9503 &mut self,
9504 ranges: Vec<Range<Point>>,
9505 window: &mut Window,
9506 cx: &mut Context<Editor>,
9507 ) {
9508 let mut revert_changes = HashMap::default();
9509 let chunk_by = self
9510 .snapshot(window, cx)
9511 .hunks_for_ranges(ranges)
9512 .into_iter()
9513 .chunk_by(|hunk| hunk.buffer_id);
9514 for (buffer_id, hunks) in &chunk_by {
9515 let hunks = hunks.collect::<Vec<_>>();
9516 for hunk in &hunks {
9517 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9518 }
9519 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9520 }
9521 drop(chunk_by);
9522 if !revert_changes.is_empty() {
9523 self.transact(window, cx, |editor, window, cx| {
9524 editor.restore(revert_changes, window, cx);
9525 });
9526 }
9527 }
9528
9529 pub fn open_active_item_in_terminal(
9530 &mut self,
9531 _: &OpenInTerminal,
9532 window: &mut Window,
9533 cx: &mut Context<Self>,
9534 ) {
9535 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9536 let project_path = buffer.read(cx).project_path(cx)?;
9537 let project = self.project.as_ref()?.read(cx);
9538 let entry = project.entry_for_path(&project_path, cx)?;
9539 let parent = match &entry.canonical_path {
9540 Some(canonical_path) => canonical_path.to_path_buf(),
9541 None => project.absolute_path(&project_path, cx)?,
9542 }
9543 .parent()?
9544 .to_path_buf();
9545 Some(parent)
9546 }) {
9547 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9548 }
9549 }
9550
9551 fn set_breakpoint_context_menu(
9552 &mut self,
9553 display_row: DisplayRow,
9554 position: Option<Anchor>,
9555 clicked_point: gpui::Point<Pixels>,
9556 window: &mut Window,
9557 cx: &mut Context<Self>,
9558 ) {
9559 if !cx.has_flag::<DebuggerFeatureFlag>() {
9560 return;
9561 }
9562 let source = self
9563 .buffer
9564 .read(cx)
9565 .snapshot(cx)
9566 .anchor_before(Point::new(display_row.0, 0u32));
9567
9568 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9569
9570 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9571 self,
9572 source,
9573 clicked_point,
9574 context_menu,
9575 window,
9576 cx,
9577 );
9578 }
9579
9580 fn add_edit_breakpoint_block(
9581 &mut self,
9582 anchor: Anchor,
9583 breakpoint: &Breakpoint,
9584 edit_action: BreakpointPromptEditAction,
9585 window: &mut Window,
9586 cx: &mut Context<Self>,
9587 ) {
9588 let weak_editor = cx.weak_entity();
9589 let bp_prompt = cx.new(|cx| {
9590 BreakpointPromptEditor::new(
9591 weak_editor,
9592 anchor,
9593 breakpoint.clone(),
9594 edit_action,
9595 window,
9596 cx,
9597 )
9598 });
9599
9600 let height = bp_prompt.update(cx, |this, cx| {
9601 this.prompt
9602 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9603 });
9604 let cloned_prompt = bp_prompt.clone();
9605 let blocks = vec![BlockProperties {
9606 style: BlockStyle::Sticky,
9607 placement: BlockPlacement::Above(anchor),
9608 height: Some(height),
9609 render: Arc::new(move |cx| {
9610 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9611 cloned_prompt.clone().into_any_element()
9612 }),
9613 priority: 0,
9614 render_in_minimap: true,
9615 }];
9616
9617 let focus_handle = bp_prompt.focus_handle(cx);
9618 window.focus(&focus_handle);
9619
9620 let block_ids = self.insert_blocks(blocks, None, cx);
9621 bp_prompt.update(cx, |prompt, _| {
9622 prompt.add_block_ids(block_ids);
9623 });
9624 }
9625
9626 pub(crate) fn breakpoint_at_row(
9627 &self,
9628 row: u32,
9629 window: &mut Window,
9630 cx: &mut Context<Self>,
9631 ) -> Option<(Anchor, Breakpoint)> {
9632 let snapshot = self.snapshot(window, cx);
9633 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9634
9635 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9636 }
9637
9638 pub(crate) fn breakpoint_at_anchor(
9639 &self,
9640 breakpoint_position: Anchor,
9641 snapshot: &EditorSnapshot,
9642 cx: &mut Context<Self>,
9643 ) -> Option<(Anchor, Breakpoint)> {
9644 let project = self.project.clone()?;
9645
9646 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9647 snapshot
9648 .buffer_snapshot
9649 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9650 })?;
9651
9652 let enclosing_excerpt = breakpoint_position.excerpt_id;
9653 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9654 let buffer_snapshot = buffer.read(cx).snapshot();
9655
9656 let row = buffer_snapshot
9657 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9658 .row;
9659
9660 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9661 let anchor_end = snapshot
9662 .buffer_snapshot
9663 .anchor_after(Point::new(row, line_len));
9664
9665 let bp = self
9666 .breakpoint_store
9667 .as_ref()?
9668 .read_with(cx, |breakpoint_store, cx| {
9669 breakpoint_store
9670 .breakpoints(
9671 &buffer,
9672 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9673 &buffer_snapshot,
9674 cx,
9675 )
9676 .next()
9677 .and_then(|(bp, _)| {
9678 let breakpoint_row = buffer_snapshot
9679 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9680 .row;
9681
9682 if breakpoint_row == row {
9683 snapshot
9684 .buffer_snapshot
9685 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9686 .map(|position| (position, bp.bp.clone()))
9687 } else {
9688 None
9689 }
9690 })
9691 });
9692 bp
9693 }
9694
9695 pub fn edit_log_breakpoint(
9696 &mut self,
9697 _: &EditLogBreakpoint,
9698 window: &mut Window,
9699 cx: &mut Context<Self>,
9700 ) {
9701 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9702 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9703 message: None,
9704 state: BreakpointState::Enabled,
9705 condition: None,
9706 hit_condition: None,
9707 });
9708
9709 self.add_edit_breakpoint_block(
9710 anchor,
9711 &breakpoint,
9712 BreakpointPromptEditAction::Log,
9713 window,
9714 cx,
9715 );
9716 }
9717 }
9718
9719 fn breakpoints_at_cursors(
9720 &self,
9721 window: &mut Window,
9722 cx: &mut Context<Self>,
9723 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9724 let snapshot = self.snapshot(window, cx);
9725 let cursors = self
9726 .selections
9727 .disjoint_anchors()
9728 .into_iter()
9729 .map(|selection| {
9730 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9731
9732 let breakpoint_position = self
9733 .breakpoint_at_row(cursor_position.row, window, cx)
9734 .map(|bp| bp.0)
9735 .unwrap_or_else(|| {
9736 snapshot
9737 .display_snapshot
9738 .buffer_snapshot
9739 .anchor_after(Point::new(cursor_position.row, 0))
9740 });
9741
9742 let breakpoint = self
9743 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9744 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9745
9746 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9747 })
9748 // 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.
9749 .collect::<HashMap<Anchor, _>>();
9750
9751 cursors.into_iter().collect()
9752 }
9753
9754 pub fn enable_breakpoint(
9755 &mut self,
9756 _: &crate::actions::EnableBreakpoint,
9757 window: &mut Window,
9758 cx: &mut Context<Self>,
9759 ) {
9760 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9761 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9762 continue;
9763 };
9764 self.edit_breakpoint_at_anchor(
9765 anchor,
9766 breakpoint,
9767 BreakpointEditAction::InvertState,
9768 cx,
9769 );
9770 }
9771 }
9772
9773 pub fn disable_breakpoint(
9774 &mut self,
9775 _: &crate::actions::DisableBreakpoint,
9776 window: &mut Window,
9777 cx: &mut Context<Self>,
9778 ) {
9779 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9780 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9781 continue;
9782 };
9783 self.edit_breakpoint_at_anchor(
9784 anchor,
9785 breakpoint,
9786 BreakpointEditAction::InvertState,
9787 cx,
9788 );
9789 }
9790 }
9791
9792 pub fn toggle_breakpoint(
9793 &mut self,
9794 _: &crate::actions::ToggleBreakpoint,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) {
9798 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9799 if let Some(breakpoint) = breakpoint {
9800 self.edit_breakpoint_at_anchor(
9801 anchor,
9802 breakpoint,
9803 BreakpointEditAction::Toggle,
9804 cx,
9805 );
9806 } else {
9807 self.edit_breakpoint_at_anchor(
9808 anchor,
9809 Breakpoint::new_standard(),
9810 BreakpointEditAction::Toggle,
9811 cx,
9812 );
9813 }
9814 }
9815 }
9816
9817 pub fn edit_breakpoint_at_anchor(
9818 &mut self,
9819 breakpoint_position: Anchor,
9820 breakpoint: Breakpoint,
9821 edit_action: BreakpointEditAction,
9822 cx: &mut Context<Self>,
9823 ) {
9824 let Some(breakpoint_store) = &self.breakpoint_store else {
9825 return;
9826 };
9827
9828 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9829 if breakpoint_position == Anchor::min() {
9830 self.buffer()
9831 .read(cx)
9832 .excerpt_buffer_ids()
9833 .into_iter()
9834 .next()
9835 } else {
9836 None
9837 }
9838 }) else {
9839 return;
9840 };
9841
9842 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9843 return;
9844 };
9845
9846 breakpoint_store.update(cx, |breakpoint_store, cx| {
9847 breakpoint_store.toggle_breakpoint(
9848 buffer,
9849 BreakpointWithPosition {
9850 position: breakpoint_position.text_anchor,
9851 bp: breakpoint,
9852 },
9853 edit_action,
9854 cx,
9855 );
9856 });
9857
9858 cx.notify();
9859 }
9860
9861 #[cfg(any(test, feature = "test-support"))]
9862 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9863 self.breakpoint_store.clone()
9864 }
9865
9866 pub fn prepare_restore_change(
9867 &self,
9868 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9869 hunk: &MultiBufferDiffHunk,
9870 cx: &mut App,
9871 ) -> Option<()> {
9872 if hunk.is_created_file() {
9873 return None;
9874 }
9875 let buffer = self.buffer.read(cx);
9876 let diff = buffer.diff_for(hunk.buffer_id)?;
9877 let buffer = buffer.buffer(hunk.buffer_id)?;
9878 let buffer = buffer.read(cx);
9879 let original_text = diff
9880 .read(cx)
9881 .base_text()
9882 .as_rope()
9883 .slice(hunk.diff_base_byte_range.clone());
9884 let buffer_snapshot = buffer.snapshot();
9885 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9886 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9887 probe
9888 .0
9889 .start
9890 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9891 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9892 }) {
9893 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9894 Some(())
9895 } else {
9896 None
9897 }
9898 }
9899
9900 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9901 self.manipulate_lines(window, cx, |lines| lines.reverse())
9902 }
9903
9904 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9905 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9906 }
9907
9908 fn manipulate_lines<Fn>(
9909 &mut self,
9910 window: &mut Window,
9911 cx: &mut Context<Self>,
9912 mut callback: Fn,
9913 ) where
9914 Fn: FnMut(&mut Vec<&str>),
9915 {
9916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9917
9918 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9919 let buffer = self.buffer.read(cx).snapshot(cx);
9920
9921 let mut edits = Vec::new();
9922
9923 let selections = self.selections.all::<Point>(cx);
9924 let mut selections = selections.iter().peekable();
9925 let mut contiguous_row_selections = Vec::new();
9926 let mut new_selections = Vec::new();
9927 let mut added_lines = 0;
9928 let mut removed_lines = 0;
9929
9930 while let Some(selection) = selections.next() {
9931 let (start_row, end_row) = consume_contiguous_rows(
9932 &mut contiguous_row_selections,
9933 selection,
9934 &display_map,
9935 &mut selections,
9936 );
9937
9938 let start_point = Point::new(start_row.0, 0);
9939 let end_point = Point::new(
9940 end_row.previous_row().0,
9941 buffer.line_len(end_row.previous_row()),
9942 );
9943 let text = buffer
9944 .text_for_range(start_point..end_point)
9945 .collect::<String>();
9946
9947 let mut lines = text.split('\n').collect_vec();
9948
9949 let lines_before = lines.len();
9950 callback(&mut lines);
9951 let lines_after = lines.len();
9952
9953 edits.push((start_point..end_point, lines.join("\n")));
9954
9955 // Selections must change based on added and removed line count
9956 let start_row =
9957 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9958 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9959 new_selections.push(Selection {
9960 id: selection.id,
9961 start: start_row,
9962 end: end_row,
9963 goal: SelectionGoal::None,
9964 reversed: selection.reversed,
9965 });
9966
9967 if lines_after > lines_before {
9968 added_lines += lines_after - lines_before;
9969 } else if lines_before > lines_after {
9970 removed_lines += lines_before - lines_after;
9971 }
9972 }
9973
9974 self.transact(window, cx, |this, window, cx| {
9975 let buffer = this.buffer.update(cx, |buffer, cx| {
9976 buffer.edit(edits, None, cx);
9977 buffer.snapshot(cx)
9978 });
9979
9980 // Recalculate offsets on newly edited buffer
9981 let new_selections = new_selections
9982 .iter()
9983 .map(|s| {
9984 let start_point = Point::new(s.start.0, 0);
9985 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9986 Selection {
9987 id: s.id,
9988 start: buffer.point_to_offset(start_point),
9989 end: buffer.point_to_offset(end_point),
9990 goal: s.goal,
9991 reversed: s.reversed,
9992 }
9993 })
9994 .collect();
9995
9996 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9997 s.select(new_selections);
9998 });
9999
10000 this.request_autoscroll(Autoscroll::fit(), cx);
10001 });
10002 }
10003
10004 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10005 self.manipulate_text(window, cx, |text| {
10006 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10007 if has_upper_case_characters {
10008 text.to_lowercase()
10009 } else {
10010 text.to_uppercase()
10011 }
10012 })
10013 }
10014
10015 pub fn convert_to_upper_case(
10016 &mut self,
10017 _: &ConvertToUpperCase,
10018 window: &mut Window,
10019 cx: &mut Context<Self>,
10020 ) {
10021 self.manipulate_text(window, cx, |text| text.to_uppercase())
10022 }
10023
10024 pub fn convert_to_lower_case(
10025 &mut self,
10026 _: &ConvertToLowerCase,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) {
10030 self.manipulate_text(window, cx, |text| text.to_lowercase())
10031 }
10032
10033 pub fn convert_to_title_case(
10034 &mut self,
10035 _: &ConvertToTitleCase,
10036 window: &mut Window,
10037 cx: &mut Context<Self>,
10038 ) {
10039 self.manipulate_text(window, cx, |text| {
10040 text.split('\n')
10041 .map(|line| line.to_case(Case::Title))
10042 .join("\n")
10043 })
10044 }
10045
10046 pub fn convert_to_snake_case(
10047 &mut self,
10048 _: &ConvertToSnakeCase,
10049 window: &mut Window,
10050 cx: &mut Context<Self>,
10051 ) {
10052 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10053 }
10054
10055 pub fn convert_to_kebab_case(
10056 &mut self,
10057 _: &ConvertToKebabCase,
10058 window: &mut Window,
10059 cx: &mut Context<Self>,
10060 ) {
10061 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10062 }
10063
10064 pub fn convert_to_upper_camel_case(
10065 &mut self,
10066 _: &ConvertToUpperCamelCase,
10067 window: &mut Window,
10068 cx: &mut Context<Self>,
10069 ) {
10070 self.manipulate_text(window, cx, |text| {
10071 text.split('\n')
10072 .map(|line| line.to_case(Case::UpperCamel))
10073 .join("\n")
10074 })
10075 }
10076
10077 pub fn convert_to_lower_camel_case(
10078 &mut self,
10079 _: &ConvertToLowerCamelCase,
10080 window: &mut Window,
10081 cx: &mut Context<Self>,
10082 ) {
10083 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10084 }
10085
10086 pub fn convert_to_opposite_case(
10087 &mut self,
10088 _: &ConvertToOppositeCase,
10089 window: &mut Window,
10090 cx: &mut Context<Self>,
10091 ) {
10092 self.manipulate_text(window, cx, |text| {
10093 text.chars()
10094 .fold(String::with_capacity(text.len()), |mut t, c| {
10095 if c.is_uppercase() {
10096 t.extend(c.to_lowercase());
10097 } else {
10098 t.extend(c.to_uppercase());
10099 }
10100 t
10101 })
10102 })
10103 }
10104
10105 pub fn convert_to_rot13(
10106 &mut self,
10107 _: &ConvertToRot13,
10108 window: &mut Window,
10109 cx: &mut Context<Self>,
10110 ) {
10111 self.manipulate_text(window, cx, |text| {
10112 text.chars()
10113 .map(|c| match c {
10114 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10115 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10116 _ => c,
10117 })
10118 .collect()
10119 })
10120 }
10121
10122 pub fn convert_to_rot47(
10123 &mut self,
10124 _: &ConvertToRot47,
10125 window: &mut Window,
10126 cx: &mut Context<Self>,
10127 ) {
10128 self.manipulate_text(window, cx, |text| {
10129 text.chars()
10130 .map(|c| {
10131 let code_point = c as u32;
10132 if code_point >= 33 && code_point <= 126 {
10133 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10134 }
10135 c
10136 })
10137 .collect()
10138 })
10139 }
10140
10141 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10142 where
10143 Fn: FnMut(&str) -> String,
10144 {
10145 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10146 let buffer = self.buffer.read(cx).snapshot(cx);
10147
10148 let mut new_selections = Vec::new();
10149 let mut edits = Vec::new();
10150 let mut selection_adjustment = 0i32;
10151
10152 for selection in self.selections.all::<usize>(cx) {
10153 let selection_is_empty = selection.is_empty();
10154
10155 let (start, end) = if selection_is_empty {
10156 let word_range = movement::surrounding_word(
10157 &display_map,
10158 selection.start.to_display_point(&display_map),
10159 );
10160 let start = word_range.start.to_offset(&display_map, Bias::Left);
10161 let end = word_range.end.to_offset(&display_map, Bias::Left);
10162 (start, end)
10163 } else {
10164 (selection.start, selection.end)
10165 };
10166
10167 let text = buffer.text_for_range(start..end).collect::<String>();
10168 let old_length = text.len() as i32;
10169 let text = callback(&text);
10170
10171 new_selections.push(Selection {
10172 start: (start as i32 - selection_adjustment) as usize,
10173 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10174 goal: SelectionGoal::None,
10175 ..selection
10176 });
10177
10178 selection_adjustment += old_length - text.len() as i32;
10179
10180 edits.push((start..end, text));
10181 }
10182
10183 self.transact(window, cx, |this, window, cx| {
10184 this.buffer.update(cx, |buffer, cx| {
10185 buffer.edit(edits, None, cx);
10186 });
10187
10188 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10189 s.select(new_selections);
10190 });
10191
10192 this.request_autoscroll(Autoscroll::fit(), cx);
10193 });
10194 }
10195
10196 pub fn duplicate(
10197 &mut self,
10198 upwards: bool,
10199 whole_lines: bool,
10200 window: &mut Window,
10201 cx: &mut Context<Self>,
10202 ) {
10203 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10204
10205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10206 let buffer = &display_map.buffer_snapshot;
10207 let selections = self.selections.all::<Point>(cx);
10208
10209 let mut edits = Vec::new();
10210 let mut selections_iter = selections.iter().peekable();
10211 while let Some(selection) = selections_iter.next() {
10212 let mut rows = selection.spanned_rows(false, &display_map);
10213 // duplicate line-wise
10214 if whole_lines || selection.start == selection.end {
10215 // Avoid duplicating the same lines twice.
10216 while let Some(next_selection) = selections_iter.peek() {
10217 let next_rows = next_selection.spanned_rows(false, &display_map);
10218 if next_rows.start < rows.end {
10219 rows.end = next_rows.end;
10220 selections_iter.next().unwrap();
10221 } else {
10222 break;
10223 }
10224 }
10225
10226 // Copy the text from the selected row region and splice it either at the start
10227 // or end of the region.
10228 let start = Point::new(rows.start.0, 0);
10229 let end = Point::new(
10230 rows.end.previous_row().0,
10231 buffer.line_len(rows.end.previous_row()),
10232 );
10233 let text = buffer
10234 .text_for_range(start..end)
10235 .chain(Some("\n"))
10236 .collect::<String>();
10237 let insert_location = if upwards {
10238 Point::new(rows.end.0, 0)
10239 } else {
10240 start
10241 };
10242 edits.push((insert_location..insert_location, text));
10243 } else {
10244 // duplicate character-wise
10245 let start = selection.start;
10246 let end = selection.end;
10247 let text = buffer.text_for_range(start..end).collect::<String>();
10248 edits.push((selection.end..selection.end, text));
10249 }
10250 }
10251
10252 self.transact(window, cx, |this, _, cx| {
10253 this.buffer.update(cx, |buffer, cx| {
10254 buffer.edit(edits, None, cx);
10255 });
10256
10257 this.request_autoscroll(Autoscroll::fit(), cx);
10258 });
10259 }
10260
10261 pub fn duplicate_line_up(
10262 &mut self,
10263 _: &DuplicateLineUp,
10264 window: &mut Window,
10265 cx: &mut Context<Self>,
10266 ) {
10267 self.duplicate(true, true, window, cx);
10268 }
10269
10270 pub fn duplicate_line_down(
10271 &mut self,
10272 _: &DuplicateLineDown,
10273 window: &mut Window,
10274 cx: &mut Context<Self>,
10275 ) {
10276 self.duplicate(false, true, window, cx);
10277 }
10278
10279 pub fn duplicate_selection(
10280 &mut self,
10281 _: &DuplicateSelection,
10282 window: &mut Window,
10283 cx: &mut Context<Self>,
10284 ) {
10285 self.duplicate(false, false, window, cx);
10286 }
10287
10288 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10289 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10290
10291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10292 let buffer = self.buffer.read(cx).snapshot(cx);
10293
10294 let mut edits = Vec::new();
10295 let mut unfold_ranges = Vec::new();
10296 let mut refold_creases = Vec::new();
10297
10298 let selections = self.selections.all::<Point>(cx);
10299 let mut selections = selections.iter().peekable();
10300 let mut contiguous_row_selections = Vec::new();
10301 let mut new_selections = Vec::new();
10302
10303 while let Some(selection) = selections.next() {
10304 // Find all the selections that span a contiguous row range
10305 let (start_row, end_row) = consume_contiguous_rows(
10306 &mut contiguous_row_selections,
10307 selection,
10308 &display_map,
10309 &mut selections,
10310 );
10311
10312 // Move the text spanned by the row range to be before the line preceding the row range
10313 if start_row.0 > 0 {
10314 let range_to_move = Point::new(
10315 start_row.previous_row().0,
10316 buffer.line_len(start_row.previous_row()),
10317 )
10318 ..Point::new(
10319 end_row.previous_row().0,
10320 buffer.line_len(end_row.previous_row()),
10321 );
10322 let insertion_point = display_map
10323 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10324 .0;
10325
10326 // Don't move lines across excerpts
10327 if buffer
10328 .excerpt_containing(insertion_point..range_to_move.end)
10329 .is_some()
10330 {
10331 let text = buffer
10332 .text_for_range(range_to_move.clone())
10333 .flat_map(|s| s.chars())
10334 .skip(1)
10335 .chain(['\n'])
10336 .collect::<String>();
10337
10338 edits.push((
10339 buffer.anchor_after(range_to_move.start)
10340 ..buffer.anchor_before(range_to_move.end),
10341 String::new(),
10342 ));
10343 let insertion_anchor = buffer.anchor_after(insertion_point);
10344 edits.push((insertion_anchor..insertion_anchor, text));
10345
10346 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10347
10348 // Move selections up
10349 new_selections.extend(contiguous_row_selections.drain(..).map(
10350 |mut selection| {
10351 selection.start.row -= row_delta;
10352 selection.end.row -= row_delta;
10353 selection
10354 },
10355 ));
10356
10357 // Move folds up
10358 unfold_ranges.push(range_to_move.clone());
10359 for fold in display_map.folds_in_range(
10360 buffer.anchor_before(range_to_move.start)
10361 ..buffer.anchor_after(range_to_move.end),
10362 ) {
10363 let mut start = fold.range.start.to_point(&buffer);
10364 let mut end = fold.range.end.to_point(&buffer);
10365 start.row -= row_delta;
10366 end.row -= row_delta;
10367 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10368 }
10369 }
10370 }
10371
10372 // If we didn't move line(s), preserve the existing selections
10373 new_selections.append(&mut contiguous_row_selections);
10374 }
10375
10376 self.transact(window, cx, |this, window, cx| {
10377 this.unfold_ranges(&unfold_ranges, true, true, cx);
10378 this.buffer.update(cx, |buffer, cx| {
10379 for (range, text) in edits {
10380 buffer.edit([(range, text)], None, cx);
10381 }
10382 });
10383 this.fold_creases(refold_creases, true, window, cx);
10384 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10385 s.select(new_selections);
10386 })
10387 });
10388 }
10389
10390 pub fn move_line_down(
10391 &mut self,
10392 _: &MoveLineDown,
10393 window: &mut Window,
10394 cx: &mut Context<Self>,
10395 ) {
10396 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10397
10398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10399 let buffer = self.buffer.read(cx).snapshot(cx);
10400
10401 let mut edits = Vec::new();
10402 let mut unfold_ranges = Vec::new();
10403 let mut refold_creases = Vec::new();
10404
10405 let selections = self.selections.all::<Point>(cx);
10406 let mut selections = selections.iter().peekable();
10407 let mut contiguous_row_selections = Vec::new();
10408 let mut new_selections = Vec::new();
10409
10410 while let Some(selection) = selections.next() {
10411 // Find all the selections that span a contiguous row range
10412 let (start_row, end_row) = consume_contiguous_rows(
10413 &mut contiguous_row_selections,
10414 selection,
10415 &display_map,
10416 &mut selections,
10417 );
10418
10419 // Move the text spanned by the row range to be after the last line of the row range
10420 if end_row.0 <= buffer.max_point().row {
10421 let range_to_move =
10422 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10423 let insertion_point = display_map
10424 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10425 .0;
10426
10427 // Don't move lines across excerpt boundaries
10428 if buffer
10429 .excerpt_containing(range_to_move.start..insertion_point)
10430 .is_some()
10431 {
10432 let mut text = String::from("\n");
10433 text.extend(buffer.text_for_range(range_to_move.clone()));
10434 text.pop(); // Drop trailing newline
10435 edits.push((
10436 buffer.anchor_after(range_to_move.start)
10437 ..buffer.anchor_before(range_to_move.end),
10438 String::new(),
10439 ));
10440 let insertion_anchor = buffer.anchor_after(insertion_point);
10441 edits.push((insertion_anchor..insertion_anchor, text));
10442
10443 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10444
10445 // Move selections down
10446 new_selections.extend(contiguous_row_selections.drain(..).map(
10447 |mut selection| {
10448 selection.start.row += row_delta;
10449 selection.end.row += row_delta;
10450 selection
10451 },
10452 ));
10453
10454 // Move folds down
10455 unfold_ranges.push(range_to_move.clone());
10456 for fold in display_map.folds_in_range(
10457 buffer.anchor_before(range_to_move.start)
10458 ..buffer.anchor_after(range_to_move.end),
10459 ) {
10460 let mut start = fold.range.start.to_point(&buffer);
10461 let mut end = fold.range.end.to_point(&buffer);
10462 start.row += row_delta;
10463 end.row += row_delta;
10464 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10465 }
10466 }
10467 }
10468
10469 // If we didn't move line(s), preserve the existing selections
10470 new_selections.append(&mut contiguous_row_selections);
10471 }
10472
10473 self.transact(window, cx, |this, window, cx| {
10474 this.unfold_ranges(&unfold_ranges, true, true, cx);
10475 this.buffer.update(cx, |buffer, cx| {
10476 for (range, text) in edits {
10477 buffer.edit([(range, text)], None, cx);
10478 }
10479 });
10480 this.fold_creases(refold_creases, true, window, cx);
10481 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10482 s.select(new_selections)
10483 });
10484 });
10485 }
10486
10487 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10488 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10489 let text_layout_details = &self.text_layout_details(window);
10490 self.transact(window, cx, |this, window, cx| {
10491 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10492 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10493 s.move_with(|display_map, selection| {
10494 if !selection.is_empty() {
10495 return;
10496 }
10497
10498 let mut head = selection.head();
10499 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10500 if head.column() == display_map.line_len(head.row()) {
10501 transpose_offset = display_map
10502 .buffer_snapshot
10503 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10504 }
10505
10506 if transpose_offset == 0 {
10507 return;
10508 }
10509
10510 *head.column_mut() += 1;
10511 head = display_map.clip_point(head, Bias::Right);
10512 let goal = SelectionGoal::HorizontalPosition(
10513 display_map
10514 .x_for_display_point(head, text_layout_details)
10515 .into(),
10516 );
10517 selection.collapse_to(head, goal);
10518
10519 let transpose_start = display_map
10520 .buffer_snapshot
10521 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10522 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10523 let transpose_end = display_map
10524 .buffer_snapshot
10525 .clip_offset(transpose_offset + 1, Bias::Right);
10526 if let Some(ch) =
10527 display_map.buffer_snapshot.chars_at(transpose_start).next()
10528 {
10529 edits.push((transpose_start..transpose_offset, String::new()));
10530 edits.push((transpose_end..transpose_end, ch.to_string()));
10531 }
10532 }
10533 });
10534 edits
10535 });
10536 this.buffer
10537 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10538 let selections = this.selections.all::<usize>(cx);
10539 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10540 s.select(selections);
10541 });
10542 });
10543 }
10544
10545 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10546 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10547 self.rewrap_impl(RewrapOptions::default(), cx)
10548 }
10549
10550 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10551 let buffer = self.buffer.read(cx).snapshot(cx);
10552 let selections = self.selections.all::<Point>(cx);
10553 let mut selections = selections.iter().peekable();
10554
10555 let mut edits = Vec::new();
10556 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10557
10558 while let Some(selection) = selections.next() {
10559 let mut start_row = selection.start.row;
10560 let mut end_row = selection.end.row;
10561
10562 // Skip selections that overlap with a range that has already been rewrapped.
10563 let selection_range = start_row..end_row;
10564 if rewrapped_row_ranges
10565 .iter()
10566 .any(|range| range.overlaps(&selection_range))
10567 {
10568 continue;
10569 }
10570
10571 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10572
10573 // Since not all lines in the selection may be at the same indent
10574 // level, choose the indent size that is the most common between all
10575 // of the lines.
10576 //
10577 // If there is a tie, we use the deepest indent.
10578 let (indent_size, indent_end) = {
10579 let mut indent_size_occurrences = HashMap::default();
10580 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10581
10582 for row in start_row..=end_row {
10583 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10584 rows_by_indent_size.entry(indent).or_default().push(row);
10585 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10586 }
10587
10588 let indent_size = indent_size_occurrences
10589 .into_iter()
10590 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10591 .map(|(indent, _)| indent)
10592 .unwrap_or_default();
10593 let row = rows_by_indent_size[&indent_size][0];
10594 let indent_end = Point::new(row, indent_size.len);
10595
10596 (indent_size, indent_end)
10597 };
10598
10599 let mut line_prefix = indent_size.chars().collect::<String>();
10600
10601 let mut inside_comment = false;
10602 if let Some(comment_prefix) =
10603 buffer
10604 .language_scope_at(selection.head())
10605 .and_then(|language| {
10606 language
10607 .line_comment_prefixes()
10608 .iter()
10609 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10610 .cloned()
10611 })
10612 {
10613 line_prefix.push_str(&comment_prefix);
10614 inside_comment = true;
10615 }
10616
10617 let language_settings = buffer.language_settings_at(selection.head(), cx);
10618 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10619 RewrapBehavior::InComments => inside_comment,
10620 RewrapBehavior::InSelections => !selection.is_empty(),
10621 RewrapBehavior::Anywhere => true,
10622 };
10623
10624 let should_rewrap = options.override_language_settings
10625 || allow_rewrap_based_on_language
10626 || self.hard_wrap.is_some();
10627 if !should_rewrap {
10628 continue;
10629 }
10630
10631 if selection.is_empty() {
10632 'expand_upwards: while start_row > 0 {
10633 let prev_row = start_row - 1;
10634 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10635 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10636 {
10637 start_row = prev_row;
10638 } else {
10639 break 'expand_upwards;
10640 }
10641 }
10642
10643 'expand_downwards: while end_row < buffer.max_point().row {
10644 let next_row = end_row + 1;
10645 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10646 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10647 {
10648 end_row = next_row;
10649 } else {
10650 break 'expand_downwards;
10651 }
10652 }
10653 }
10654
10655 let start = Point::new(start_row, 0);
10656 let start_offset = start.to_offset(&buffer);
10657 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10658 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10659 let Some(lines_without_prefixes) = selection_text
10660 .lines()
10661 .map(|line| {
10662 line.strip_prefix(&line_prefix)
10663 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10664 .with_context(|| {
10665 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10666 })
10667 })
10668 .collect::<Result<Vec<_>, _>>()
10669 .log_err()
10670 else {
10671 continue;
10672 };
10673
10674 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10675 buffer
10676 .language_settings_at(Point::new(start_row, 0), cx)
10677 .preferred_line_length as usize
10678 });
10679 let wrapped_text = wrap_with_prefix(
10680 line_prefix,
10681 lines_without_prefixes.join("\n"),
10682 wrap_column,
10683 tab_size,
10684 options.preserve_existing_whitespace,
10685 );
10686
10687 // TODO: should always use char-based diff while still supporting cursor behavior that
10688 // matches vim.
10689 let mut diff_options = DiffOptions::default();
10690 if options.override_language_settings {
10691 diff_options.max_word_diff_len = 0;
10692 diff_options.max_word_diff_line_count = 0;
10693 } else {
10694 diff_options.max_word_diff_len = usize::MAX;
10695 diff_options.max_word_diff_line_count = usize::MAX;
10696 }
10697
10698 for (old_range, new_text) in
10699 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10700 {
10701 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10702 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10703 edits.push((edit_start..edit_end, new_text));
10704 }
10705
10706 rewrapped_row_ranges.push(start_row..=end_row);
10707 }
10708
10709 self.buffer
10710 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10711 }
10712
10713 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10714 let mut text = String::new();
10715 let buffer = self.buffer.read(cx).snapshot(cx);
10716 let mut selections = self.selections.all::<Point>(cx);
10717 let mut clipboard_selections = Vec::with_capacity(selections.len());
10718 {
10719 let max_point = buffer.max_point();
10720 let mut is_first = true;
10721 for selection in &mut selections {
10722 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10723 if is_entire_line {
10724 selection.start = Point::new(selection.start.row, 0);
10725 if !selection.is_empty() && selection.end.column == 0 {
10726 selection.end = cmp::min(max_point, selection.end);
10727 } else {
10728 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10729 }
10730 selection.goal = SelectionGoal::None;
10731 }
10732 if is_first {
10733 is_first = false;
10734 } else {
10735 text += "\n";
10736 }
10737 let mut len = 0;
10738 for chunk in buffer.text_for_range(selection.start..selection.end) {
10739 text.push_str(chunk);
10740 len += chunk.len();
10741 }
10742 clipboard_selections.push(ClipboardSelection {
10743 len,
10744 is_entire_line,
10745 first_line_indent: buffer
10746 .indent_size_for_line(MultiBufferRow(selection.start.row))
10747 .len,
10748 });
10749 }
10750 }
10751
10752 self.transact(window, cx, |this, window, cx| {
10753 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10754 s.select(selections);
10755 });
10756 this.insert("", window, cx);
10757 });
10758 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10759 }
10760
10761 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10762 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10763 let item = self.cut_common(window, cx);
10764 cx.write_to_clipboard(item);
10765 }
10766
10767 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10769 self.change_selections(None, window, cx, |s| {
10770 s.move_with(|snapshot, sel| {
10771 if sel.is_empty() {
10772 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10773 }
10774 });
10775 });
10776 let item = self.cut_common(window, cx);
10777 cx.set_global(KillRing(item))
10778 }
10779
10780 pub fn kill_ring_yank(
10781 &mut self,
10782 _: &KillRingYank,
10783 window: &mut Window,
10784 cx: &mut Context<Self>,
10785 ) {
10786 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10787 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10788 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10789 (kill_ring.text().to_string(), kill_ring.metadata_json())
10790 } else {
10791 return;
10792 }
10793 } else {
10794 return;
10795 };
10796 self.do_paste(&text, metadata, false, window, cx);
10797 }
10798
10799 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10800 self.do_copy(true, cx);
10801 }
10802
10803 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10804 self.do_copy(false, cx);
10805 }
10806
10807 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10808 let selections = self.selections.all::<Point>(cx);
10809 let buffer = self.buffer.read(cx).read(cx);
10810 let mut text = String::new();
10811
10812 let mut clipboard_selections = Vec::with_capacity(selections.len());
10813 {
10814 let max_point = buffer.max_point();
10815 let mut is_first = true;
10816 for selection in &selections {
10817 let mut start = selection.start;
10818 let mut end = selection.end;
10819 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10820 if is_entire_line {
10821 start = Point::new(start.row, 0);
10822 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10823 }
10824
10825 let mut trimmed_selections = Vec::new();
10826 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10827 let row = MultiBufferRow(start.row);
10828 let first_indent = buffer.indent_size_for_line(row);
10829 if first_indent.len == 0 || start.column > first_indent.len {
10830 trimmed_selections.push(start..end);
10831 } else {
10832 trimmed_selections.push(
10833 Point::new(row.0, first_indent.len)
10834 ..Point::new(row.0, buffer.line_len(row)),
10835 );
10836 for row in start.row + 1..=end.row {
10837 let mut line_len = buffer.line_len(MultiBufferRow(row));
10838 if row == end.row {
10839 line_len = end.column;
10840 }
10841 if line_len == 0 {
10842 trimmed_selections
10843 .push(Point::new(row, 0)..Point::new(row, line_len));
10844 continue;
10845 }
10846 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10847 if row_indent_size.len >= first_indent.len {
10848 trimmed_selections.push(
10849 Point::new(row, first_indent.len)..Point::new(row, line_len),
10850 );
10851 } else {
10852 trimmed_selections.clear();
10853 trimmed_selections.push(start..end);
10854 break;
10855 }
10856 }
10857 }
10858 } else {
10859 trimmed_selections.push(start..end);
10860 }
10861
10862 for trimmed_range in trimmed_selections {
10863 if is_first {
10864 is_first = false;
10865 } else {
10866 text += "\n";
10867 }
10868 let mut len = 0;
10869 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10870 text.push_str(chunk);
10871 len += chunk.len();
10872 }
10873 clipboard_selections.push(ClipboardSelection {
10874 len,
10875 is_entire_line,
10876 first_line_indent: buffer
10877 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10878 .len,
10879 });
10880 }
10881 }
10882 }
10883
10884 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10885 text,
10886 clipboard_selections,
10887 ));
10888 }
10889
10890 pub fn do_paste(
10891 &mut self,
10892 text: &String,
10893 clipboard_selections: Option<Vec<ClipboardSelection>>,
10894 handle_entire_lines: bool,
10895 window: &mut Window,
10896 cx: &mut Context<Self>,
10897 ) {
10898 if self.read_only(cx) {
10899 return;
10900 }
10901
10902 let clipboard_text = Cow::Borrowed(text);
10903
10904 self.transact(window, cx, |this, window, cx| {
10905 if let Some(mut clipboard_selections) = clipboard_selections {
10906 let old_selections = this.selections.all::<usize>(cx);
10907 let all_selections_were_entire_line =
10908 clipboard_selections.iter().all(|s| s.is_entire_line);
10909 let first_selection_indent_column =
10910 clipboard_selections.first().map(|s| s.first_line_indent);
10911 if clipboard_selections.len() != old_selections.len() {
10912 clipboard_selections.drain(..);
10913 }
10914 let cursor_offset = this.selections.last::<usize>(cx).head();
10915 let mut auto_indent_on_paste = true;
10916
10917 this.buffer.update(cx, |buffer, cx| {
10918 let snapshot = buffer.read(cx);
10919 auto_indent_on_paste = snapshot
10920 .language_settings_at(cursor_offset, cx)
10921 .auto_indent_on_paste;
10922
10923 let mut start_offset = 0;
10924 let mut edits = Vec::new();
10925 let mut original_indent_columns = Vec::new();
10926 for (ix, selection) in old_selections.iter().enumerate() {
10927 let to_insert;
10928 let entire_line;
10929 let original_indent_column;
10930 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10931 let end_offset = start_offset + clipboard_selection.len;
10932 to_insert = &clipboard_text[start_offset..end_offset];
10933 entire_line = clipboard_selection.is_entire_line;
10934 start_offset = end_offset + 1;
10935 original_indent_column = Some(clipboard_selection.first_line_indent);
10936 } else {
10937 to_insert = clipboard_text.as_str();
10938 entire_line = all_selections_were_entire_line;
10939 original_indent_column = first_selection_indent_column
10940 }
10941
10942 // If the corresponding selection was empty when this slice of the
10943 // clipboard text was written, then the entire line containing the
10944 // selection was copied. If this selection is also currently empty,
10945 // then paste the line before the current line of the buffer.
10946 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10947 let column = selection.start.to_point(&snapshot).column as usize;
10948 let line_start = selection.start - column;
10949 line_start..line_start
10950 } else {
10951 selection.range()
10952 };
10953
10954 edits.push((range, to_insert));
10955 original_indent_columns.push(original_indent_column);
10956 }
10957 drop(snapshot);
10958
10959 buffer.edit(
10960 edits,
10961 if auto_indent_on_paste {
10962 Some(AutoindentMode::Block {
10963 original_indent_columns,
10964 })
10965 } else {
10966 None
10967 },
10968 cx,
10969 );
10970 });
10971
10972 let selections = this.selections.all::<usize>(cx);
10973 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10974 s.select(selections)
10975 });
10976 } else {
10977 this.insert(&clipboard_text, window, cx);
10978 }
10979 });
10980 }
10981
10982 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10983 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10984 if let Some(item) = cx.read_from_clipboard() {
10985 let entries = item.entries();
10986
10987 match entries.first() {
10988 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10989 // of all the pasted entries.
10990 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10991 .do_paste(
10992 clipboard_string.text(),
10993 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10994 true,
10995 window,
10996 cx,
10997 ),
10998 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10999 }
11000 }
11001 }
11002
11003 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11004 if self.read_only(cx) {
11005 return;
11006 }
11007
11008 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11009
11010 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11011 if let Some((selections, _)) =
11012 self.selection_history.transaction(transaction_id).cloned()
11013 {
11014 self.change_selections(None, window, cx, |s| {
11015 s.select_anchors(selections.to_vec());
11016 });
11017 } else {
11018 log::error!(
11019 "No entry in selection_history found for undo. \
11020 This may correspond to a bug where undo does not update the selection. \
11021 If this is occurring, please add details to \
11022 https://github.com/zed-industries/zed/issues/22692"
11023 );
11024 }
11025 self.request_autoscroll(Autoscroll::fit(), cx);
11026 self.unmark_text(window, cx);
11027 self.refresh_inline_completion(true, false, window, cx);
11028 cx.emit(EditorEvent::Edited { transaction_id });
11029 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11030 }
11031 }
11032
11033 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11034 if self.read_only(cx) {
11035 return;
11036 }
11037
11038 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11039
11040 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11041 if let Some((_, Some(selections))) =
11042 self.selection_history.transaction(transaction_id).cloned()
11043 {
11044 self.change_selections(None, window, cx, |s| {
11045 s.select_anchors(selections.to_vec());
11046 });
11047 } else {
11048 log::error!(
11049 "No entry in selection_history found for redo. \
11050 This may correspond to a bug where undo does not update the selection. \
11051 If this is occurring, please add details to \
11052 https://github.com/zed-industries/zed/issues/22692"
11053 );
11054 }
11055 self.request_autoscroll(Autoscroll::fit(), cx);
11056 self.unmark_text(window, cx);
11057 self.refresh_inline_completion(true, false, window, cx);
11058 cx.emit(EditorEvent::Edited { transaction_id });
11059 }
11060 }
11061
11062 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11063 self.buffer
11064 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11065 }
11066
11067 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11068 self.buffer
11069 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11070 }
11071
11072 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11073 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11074 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11075 s.move_with(|map, selection| {
11076 let cursor = if selection.is_empty() {
11077 movement::left(map, selection.start)
11078 } else {
11079 selection.start
11080 };
11081 selection.collapse_to(cursor, SelectionGoal::None);
11082 });
11083 })
11084 }
11085
11086 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11088 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11089 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11090 })
11091 }
11092
11093 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11094 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11095 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11096 s.move_with(|map, selection| {
11097 let cursor = if selection.is_empty() {
11098 movement::right(map, selection.end)
11099 } else {
11100 selection.end
11101 };
11102 selection.collapse_to(cursor, SelectionGoal::None)
11103 });
11104 })
11105 }
11106
11107 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11108 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11109 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11110 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11111 })
11112 }
11113
11114 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11115 if self.take_rename(true, window, cx).is_some() {
11116 return;
11117 }
11118
11119 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11120 cx.propagate();
11121 return;
11122 }
11123
11124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11125
11126 let text_layout_details = &self.text_layout_details(window);
11127 let selection_count = self.selections.count();
11128 let first_selection = self.selections.first_anchor();
11129
11130 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11131 s.move_with(|map, selection| {
11132 if !selection.is_empty() {
11133 selection.goal = SelectionGoal::None;
11134 }
11135 let (cursor, goal) = movement::up(
11136 map,
11137 selection.start,
11138 selection.goal,
11139 false,
11140 text_layout_details,
11141 );
11142 selection.collapse_to(cursor, goal);
11143 });
11144 });
11145
11146 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11147 {
11148 cx.propagate();
11149 }
11150 }
11151
11152 pub fn move_up_by_lines(
11153 &mut self,
11154 action: &MoveUpByLines,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 if self.take_rename(true, window, cx).is_some() {
11159 return;
11160 }
11161
11162 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11163 cx.propagate();
11164 return;
11165 }
11166
11167 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11168
11169 let text_layout_details = &self.text_layout_details(window);
11170
11171 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11172 s.move_with(|map, selection| {
11173 if !selection.is_empty() {
11174 selection.goal = SelectionGoal::None;
11175 }
11176 let (cursor, goal) = movement::up_by_rows(
11177 map,
11178 selection.start,
11179 action.lines,
11180 selection.goal,
11181 false,
11182 text_layout_details,
11183 );
11184 selection.collapse_to(cursor, goal);
11185 });
11186 })
11187 }
11188
11189 pub fn move_down_by_lines(
11190 &mut self,
11191 action: &MoveDownByLines,
11192 window: &mut Window,
11193 cx: &mut Context<Self>,
11194 ) {
11195 if self.take_rename(true, window, cx).is_some() {
11196 return;
11197 }
11198
11199 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11200 cx.propagate();
11201 return;
11202 }
11203
11204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11205
11206 let text_layout_details = &self.text_layout_details(window);
11207
11208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11209 s.move_with(|map, selection| {
11210 if !selection.is_empty() {
11211 selection.goal = SelectionGoal::None;
11212 }
11213 let (cursor, goal) = movement::down_by_rows(
11214 map,
11215 selection.start,
11216 action.lines,
11217 selection.goal,
11218 false,
11219 text_layout_details,
11220 );
11221 selection.collapse_to(cursor, goal);
11222 });
11223 })
11224 }
11225
11226 pub fn select_down_by_lines(
11227 &mut self,
11228 action: &SelectDownByLines,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11233 let text_layout_details = &self.text_layout_details(window);
11234 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11235 s.move_heads_with(|map, head, goal| {
11236 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11237 })
11238 })
11239 }
11240
11241 pub fn select_up_by_lines(
11242 &mut self,
11243 action: &SelectUpByLines,
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::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11252 })
11253 })
11254 }
11255
11256 pub fn select_page_up(
11257 &mut self,
11258 _: &SelectPageUp,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 let Some(row_count) = self.visible_row_count() else {
11263 return;
11264 };
11265
11266 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11267
11268 let text_layout_details = &self.text_layout_details(window);
11269
11270 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11271 s.move_heads_with(|map, head, goal| {
11272 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11273 })
11274 })
11275 }
11276
11277 pub fn move_page_up(
11278 &mut self,
11279 action: &MovePageUp,
11280 window: &mut Window,
11281 cx: &mut Context<Self>,
11282 ) {
11283 if self.take_rename(true, window, cx).is_some() {
11284 return;
11285 }
11286
11287 if self
11288 .context_menu
11289 .borrow_mut()
11290 .as_mut()
11291 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11292 .unwrap_or(false)
11293 {
11294 return;
11295 }
11296
11297 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11298 cx.propagate();
11299 return;
11300 }
11301
11302 let Some(row_count) = self.visible_row_count() else {
11303 return;
11304 };
11305
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11307
11308 let autoscroll = if action.center_cursor {
11309 Autoscroll::center()
11310 } else {
11311 Autoscroll::fit()
11312 };
11313
11314 let text_layout_details = &self.text_layout_details(window);
11315
11316 self.change_selections(Some(autoscroll), window, cx, |s| {
11317 s.move_with(|map, selection| {
11318 if !selection.is_empty() {
11319 selection.goal = SelectionGoal::None;
11320 }
11321 let (cursor, goal) = movement::up_by_rows(
11322 map,
11323 selection.end,
11324 row_count,
11325 selection.goal,
11326 false,
11327 text_layout_details,
11328 );
11329 selection.collapse_to(cursor, goal);
11330 });
11331 });
11332 }
11333
11334 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11335 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11336 let text_layout_details = &self.text_layout_details(window);
11337 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11338 s.move_heads_with(|map, head, goal| {
11339 movement::up(map, head, goal, false, text_layout_details)
11340 })
11341 })
11342 }
11343
11344 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11345 self.take_rename(true, window, cx);
11346
11347 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11348 cx.propagate();
11349 return;
11350 }
11351
11352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11353
11354 let text_layout_details = &self.text_layout_details(window);
11355 let selection_count = self.selections.count();
11356 let first_selection = self.selections.first_anchor();
11357
11358 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11359 s.move_with(|map, selection| {
11360 if !selection.is_empty() {
11361 selection.goal = SelectionGoal::None;
11362 }
11363 let (cursor, goal) = movement::down(
11364 map,
11365 selection.end,
11366 selection.goal,
11367 false,
11368 text_layout_details,
11369 );
11370 selection.collapse_to(cursor, goal);
11371 });
11372 });
11373
11374 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11375 {
11376 cx.propagate();
11377 }
11378 }
11379
11380 pub fn select_page_down(
11381 &mut self,
11382 _: &SelectPageDown,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) {
11386 let Some(row_count) = self.visible_row_count() else {
11387 return;
11388 };
11389
11390 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11391
11392 let text_layout_details = &self.text_layout_details(window);
11393
11394 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11395 s.move_heads_with(|map, head, goal| {
11396 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11397 })
11398 })
11399 }
11400
11401 pub fn move_page_down(
11402 &mut self,
11403 action: &MovePageDown,
11404 window: &mut Window,
11405 cx: &mut Context<Self>,
11406 ) {
11407 if self.take_rename(true, window, cx).is_some() {
11408 return;
11409 }
11410
11411 if self
11412 .context_menu
11413 .borrow_mut()
11414 .as_mut()
11415 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11416 .unwrap_or(false)
11417 {
11418 return;
11419 }
11420
11421 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11422 cx.propagate();
11423 return;
11424 }
11425
11426 let Some(row_count) = self.visible_row_count() else {
11427 return;
11428 };
11429
11430 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11431
11432 let autoscroll = if action.center_cursor {
11433 Autoscroll::center()
11434 } else {
11435 Autoscroll::fit()
11436 };
11437
11438 let text_layout_details = &self.text_layout_details(window);
11439 self.change_selections(Some(autoscroll), window, cx, |s| {
11440 s.move_with(|map, selection| {
11441 if !selection.is_empty() {
11442 selection.goal = SelectionGoal::None;
11443 }
11444 let (cursor, goal) = movement::down_by_rows(
11445 map,
11446 selection.end,
11447 row_count,
11448 selection.goal,
11449 false,
11450 text_layout_details,
11451 );
11452 selection.collapse_to(cursor, goal);
11453 });
11454 });
11455 }
11456
11457 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11458 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11459 let text_layout_details = &self.text_layout_details(window);
11460 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11461 s.move_heads_with(|map, head, goal| {
11462 movement::down(map, head, goal, false, text_layout_details)
11463 })
11464 });
11465 }
11466
11467 pub fn context_menu_first(
11468 &mut self,
11469 _: &ContextMenuFirst,
11470 _window: &mut Window,
11471 cx: &mut Context<Self>,
11472 ) {
11473 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11474 context_menu.select_first(self.completion_provider.as_deref(), cx);
11475 }
11476 }
11477
11478 pub fn context_menu_prev(
11479 &mut self,
11480 _: &ContextMenuPrevious,
11481 _window: &mut Window,
11482 cx: &mut Context<Self>,
11483 ) {
11484 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11485 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11486 }
11487 }
11488
11489 pub fn context_menu_next(
11490 &mut self,
11491 _: &ContextMenuNext,
11492 _window: &mut Window,
11493 cx: &mut Context<Self>,
11494 ) {
11495 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11496 context_menu.select_next(self.completion_provider.as_deref(), cx);
11497 }
11498 }
11499
11500 pub fn context_menu_last(
11501 &mut self,
11502 _: &ContextMenuLast,
11503 _window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11507 context_menu.select_last(self.completion_provider.as_deref(), cx);
11508 }
11509 }
11510
11511 pub fn move_to_previous_word_start(
11512 &mut self,
11513 _: &MoveToPreviousWordStart,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11518 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11519 s.move_cursors_with(|map, head, _| {
11520 (
11521 movement::previous_word_start(map, head),
11522 SelectionGoal::None,
11523 )
11524 });
11525 })
11526 }
11527
11528 pub fn move_to_previous_subword_start(
11529 &mut self,
11530 _: &MoveToPreviousSubwordStart,
11531 window: &mut Window,
11532 cx: &mut Context<Self>,
11533 ) {
11534 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11536 s.move_cursors_with(|map, head, _| {
11537 (
11538 movement::previous_subword_start(map, head),
11539 SelectionGoal::None,
11540 )
11541 });
11542 })
11543 }
11544
11545 pub fn select_to_previous_word_start(
11546 &mut self,
11547 _: &SelectToPreviousWordStart,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11552 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11553 s.move_heads_with(|map, head, _| {
11554 (
11555 movement::previous_word_start(map, head),
11556 SelectionGoal::None,
11557 )
11558 });
11559 })
11560 }
11561
11562 pub fn select_to_previous_subword_start(
11563 &mut self,
11564 _: &SelectToPreviousSubwordStart,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 ) {
11568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11569 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11570 s.move_heads_with(|map, head, _| {
11571 (
11572 movement::previous_subword_start(map, head),
11573 SelectionGoal::None,
11574 )
11575 });
11576 })
11577 }
11578
11579 pub fn delete_to_previous_word_start(
11580 &mut self,
11581 action: &DeleteToPreviousWordStart,
11582 window: &mut Window,
11583 cx: &mut Context<Self>,
11584 ) {
11585 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11586 self.transact(window, cx, |this, window, cx| {
11587 this.select_autoclose_pair(window, cx);
11588 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11589 s.move_with(|map, selection| {
11590 if selection.is_empty() {
11591 let cursor = if action.ignore_newlines {
11592 movement::previous_word_start(map, selection.head())
11593 } else {
11594 movement::previous_word_start_or_newline(map, selection.head())
11595 };
11596 selection.set_head(cursor, SelectionGoal::None);
11597 }
11598 });
11599 });
11600 this.insert("", window, cx);
11601 });
11602 }
11603
11604 pub fn delete_to_previous_subword_start(
11605 &mut self,
11606 _: &DeleteToPreviousSubwordStart,
11607 window: &mut Window,
11608 cx: &mut Context<Self>,
11609 ) {
11610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11611 self.transact(window, cx, |this, window, cx| {
11612 this.select_autoclose_pair(window, cx);
11613 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11614 s.move_with(|map, selection| {
11615 if selection.is_empty() {
11616 let cursor = movement::previous_subword_start(map, selection.head());
11617 selection.set_head(cursor, SelectionGoal::None);
11618 }
11619 });
11620 });
11621 this.insert("", window, cx);
11622 });
11623 }
11624
11625 pub fn move_to_next_word_end(
11626 &mut self,
11627 _: &MoveToNextWordEnd,
11628 window: &mut Window,
11629 cx: &mut Context<Self>,
11630 ) {
11631 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11632 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11633 s.move_cursors_with(|map, head, _| {
11634 (movement::next_word_end(map, head), SelectionGoal::None)
11635 });
11636 })
11637 }
11638
11639 pub fn move_to_next_subword_end(
11640 &mut self,
11641 _: &MoveToNextSubwordEnd,
11642 window: &mut Window,
11643 cx: &mut Context<Self>,
11644 ) {
11645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11646 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11647 s.move_cursors_with(|map, head, _| {
11648 (movement::next_subword_end(map, head), SelectionGoal::None)
11649 });
11650 })
11651 }
11652
11653 pub fn select_to_next_word_end(
11654 &mut self,
11655 _: &SelectToNextWordEnd,
11656 window: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11660 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11661 s.move_heads_with(|map, head, _| {
11662 (movement::next_word_end(map, head), SelectionGoal::None)
11663 });
11664 })
11665 }
11666
11667 pub fn select_to_next_subword_end(
11668 &mut self,
11669 _: &SelectToNextSubwordEnd,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11674 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11675 s.move_heads_with(|map, head, _| {
11676 (movement::next_subword_end(map, head), SelectionGoal::None)
11677 });
11678 })
11679 }
11680
11681 pub fn delete_to_next_word_end(
11682 &mut self,
11683 action: &DeleteToNextWordEnd,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11688 self.transact(window, cx, |this, window, cx| {
11689 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11690 s.move_with(|map, selection| {
11691 if selection.is_empty() {
11692 let cursor = if action.ignore_newlines {
11693 movement::next_word_end(map, selection.head())
11694 } else {
11695 movement::next_word_end_or_newline(map, selection.head())
11696 };
11697 selection.set_head(cursor, SelectionGoal::None);
11698 }
11699 });
11700 });
11701 this.insert("", window, cx);
11702 });
11703 }
11704
11705 pub fn delete_to_next_subword_end(
11706 &mut self,
11707 _: &DeleteToNextSubwordEnd,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) {
11711 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11712 self.transact(window, cx, |this, window, cx| {
11713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11714 s.move_with(|map, selection| {
11715 if selection.is_empty() {
11716 let cursor = movement::next_subword_end(map, selection.head());
11717 selection.set_head(cursor, SelectionGoal::None);
11718 }
11719 });
11720 });
11721 this.insert("", window, cx);
11722 });
11723 }
11724
11725 pub fn move_to_beginning_of_line(
11726 &mut self,
11727 action: &MoveToBeginningOfLine,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11732 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11733 s.move_cursors_with(|map, head, _| {
11734 (
11735 movement::indented_line_beginning(
11736 map,
11737 head,
11738 action.stop_at_soft_wraps,
11739 action.stop_at_indent,
11740 ),
11741 SelectionGoal::None,
11742 )
11743 });
11744 })
11745 }
11746
11747 pub fn select_to_beginning_of_line(
11748 &mut self,
11749 action: &SelectToBeginningOfLine,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_heads_with(|map, head, _| {
11756 (
11757 movement::indented_line_beginning(
11758 map,
11759 head,
11760 action.stop_at_soft_wraps,
11761 action.stop_at_indent,
11762 ),
11763 SelectionGoal::None,
11764 )
11765 });
11766 });
11767 }
11768
11769 pub fn delete_to_beginning_of_line(
11770 &mut self,
11771 action: &DeleteToBeginningOfLine,
11772 window: &mut Window,
11773 cx: &mut Context<Self>,
11774 ) {
11775 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11776 self.transact(window, cx, |this, window, cx| {
11777 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11778 s.move_with(|_, selection| {
11779 selection.reversed = true;
11780 });
11781 });
11782
11783 this.select_to_beginning_of_line(
11784 &SelectToBeginningOfLine {
11785 stop_at_soft_wraps: false,
11786 stop_at_indent: action.stop_at_indent,
11787 },
11788 window,
11789 cx,
11790 );
11791 this.backspace(&Backspace, window, cx);
11792 });
11793 }
11794
11795 pub fn move_to_end_of_line(
11796 &mut self,
11797 action: &MoveToEndOfLine,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11803 s.move_cursors_with(|map, head, _| {
11804 (
11805 movement::line_end(map, head, action.stop_at_soft_wraps),
11806 SelectionGoal::None,
11807 )
11808 });
11809 })
11810 }
11811
11812 pub fn select_to_end_of_line(
11813 &mut self,
11814 action: &SelectToEndOfLine,
11815 window: &mut Window,
11816 cx: &mut Context<Self>,
11817 ) {
11818 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11820 s.move_heads_with(|map, head, _| {
11821 (
11822 movement::line_end(map, head, action.stop_at_soft_wraps),
11823 SelectionGoal::None,
11824 )
11825 });
11826 })
11827 }
11828
11829 pub fn delete_to_end_of_line(
11830 &mut self,
11831 _: &DeleteToEndOfLine,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11836 self.transact(window, cx, |this, window, cx| {
11837 this.select_to_end_of_line(
11838 &SelectToEndOfLine {
11839 stop_at_soft_wraps: false,
11840 },
11841 window,
11842 cx,
11843 );
11844 this.delete(&Delete, window, cx);
11845 });
11846 }
11847
11848 pub fn cut_to_end_of_line(
11849 &mut self,
11850 _: &CutToEndOfLine,
11851 window: &mut Window,
11852 cx: &mut Context<Self>,
11853 ) {
11854 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11855 self.transact(window, cx, |this, window, cx| {
11856 this.select_to_end_of_line(
11857 &SelectToEndOfLine {
11858 stop_at_soft_wraps: false,
11859 },
11860 window,
11861 cx,
11862 );
11863 this.cut(&Cut, window, cx);
11864 });
11865 }
11866
11867 pub fn move_to_start_of_paragraph(
11868 &mut self,
11869 _: &MoveToStartOfParagraph,
11870 window: &mut Window,
11871 cx: &mut Context<Self>,
11872 ) {
11873 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11874 cx.propagate();
11875 return;
11876 }
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11878 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11879 s.move_with(|map, selection| {
11880 selection.collapse_to(
11881 movement::start_of_paragraph(map, selection.head(), 1),
11882 SelectionGoal::None,
11883 )
11884 });
11885 })
11886 }
11887
11888 pub fn move_to_end_of_paragraph(
11889 &mut self,
11890 _: &MoveToEndOfParagraph,
11891 window: &mut Window,
11892 cx: &mut Context<Self>,
11893 ) {
11894 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11895 cx.propagate();
11896 return;
11897 }
11898 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11899 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11900 s.move_with(|map, selection| {
11901 selection.collapse_to(
11902 movement::end_of_paragraph(map, selection.head(), 1),
11903 SelectionGoal::None,
11904 )
11905 });
11906 })
11907 }
11908
11909 pub fn select_to_start_of_paragraph(
11910 &mut self,
11911 _: &SelectToStartOfParagraph,
11912 window: &mut Window,
11913 cx: &mut Context<Self>,
11914 ) {
11915 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11916 cx.propagate();
11917 return;
11918 }
11919 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11921 s.move_heads_with(|map, head, _| {
11922 (
11923 movement::start_of_paragraph(map, head, 1),
11924 SelectionGoal::None,
11925 )
11926 });
11927 })
11928 }
11929
11930 pub fn select_to_end_of_paragraph(
11931 &mut self,
11932 _: &SelectToEndOfParagraph,
11933 window: &mut Window,
11934 cx: &mut Context<Self>,
11935 ) {
11936 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11937 cx.propagate();
11938 return;
11939 }
11940 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11942 s.move_heads_with(|map, head, _| {
11943 (
11944 movement::end_of_paragraph(map, head, 1),
11945 SelectionGoal::None,
11946 )
11947 });
11948 })
11949 }
11950
11951 pub fn move_to_start_of_excerpt(
11952 &mut self,
11953 _: &MoveToStartOfExcerpt,
11954 window: &mut Window,
11955 cx: &mut Context<Self>,
11956 ) {
11957 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11958 cx.propagate();
11959 return;
11960 }
11961 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11962 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11963 s.move_with(|map, selection| {
11964 selection.collapse_to(
11965 movement::start_of_excerpt(
11966 map,
11967 selection.head(),
11968 workspace::searchable::Direction::Prev,
11969 ),
11970 SelectionGoal::None,
11971 )
11972 });
11973 })
11974 }
11975
11976 pub fn move_to_start_of_next_excerpt(
11977 &mut self,
11978 _: &MoveToStartOfNextExcerpt,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11983 cx.propagate();
11984 return;
11985 }
11986
11987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11988 s.move_with(|map, selection| {
11989 selection.collapse_to(
11990 movement::start_of_excerpt(
11991 map,
11992 selection.head(),
11993 workspace::searchable::Direction::Next,
11994 ),
11995 SelectionGoal::None,
11996 )
11997 });
11998 })
11999 }
12000
12001 pub fn move_to_end_of_excerpt(
12002 &mut self,
12003 _: &MoveToEndOfExcerpt,
12004 window: &mut Window,
12005 cx: &mut Context<Self>,
12006 ) {
12007 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12008 cx.propagate();
12009 return;
12010 }
12011 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12013 s.move_with(|map, selection| {
12014 selection.collapse_to(
12015 movement::end_of_excerpt(
12016 map,
12017 selection.head(),
12018 workspace::searchable::Direction::Next,
12019 ),
12020 SelectionGoal::None,
12021 )
12022 });
12023 })
12024 }
12025
12026 pub fn move_to_end_of_previous_excerpt(
12027 &mut self,
12028 _: &MoveToEndOfPreviousExcerpt,
12029 window: &mut Window,
12030 cx: &mut Context<Self>,
12031 ) {
12032 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12033 cx.propagate();
12034 return;
12035 }
12036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12038 s.move_with(|map, selection| {
12039 selection.collapse_to(
12040 movement::end_of_excerpt(
12041 map,
12042 selection.head(),
12043 workspace::searchable::Direction::Prev,
12044 ),
12045 SelectionGoal::None,
12046 )
12047 });
12048 })
12049 }
12050
12051 pub fn select_to_start_of_excerpt(
12052 &mut self,
12053 _: &SelectToStartOfExcerpt,
12054 window: &mut Window,
12055 cx: &mut Context<Self>,
12056 ) {
12057 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12058 cx.propagate();
12059 return;
12060 }
12061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12063 s.move_heads_with(|map, head, _| {
12064 (
12065 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12066 SelectionGoal::None,
12067 )
12068 });
12069 })
12070 }
12071
12072 pub fn select_to_start_of_next_excerpt(
12073 &mut self,
12074 _: &SelectToStartOfNextExcerpt,
12075 window: &mut Window,
12076 cx: &mut Context<Self>,
12077 ) {
12078 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12079 cx.propagate();
12080 return;
12081 }
12082 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12083 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12084 s.move_heads_with(|map, head, _| {
12085 (
12086 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12087 SelectionGoal::None,
12088 )
12089 });
12090 })
12091 }
12092
12093 pub fn select_to_end_of_excerpt(
12094 &mut self,
12095 _: &SelectToEndOfExcerpt,
12096 window: &mut Window,
12097 cx: &mut Context<Self>,
12098 ) {
12099 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12100 cx.propagate();
12101 return;
12102 }
12103 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12104 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12105 s.move_heads_with(|map, head, _| {
12106 (
12107 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12108 SelectionGoal::None,
12109 )
12110 });
12111 })
12112 }
12113
12114 pub fn select_to_end_of_previous_excerpt(
12115 &mut self,
12116 _: &SelectToEndOfPreviousExcerpt,
12117 window: &mut Window,
12118 cx: &mut Context<Self>,
12119 ) {
12120 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12121 cx.propagate();
12122 return;
12123 }
12124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12126 s.move_heads_with(|map, head, _| {
12127 (
12128 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12129 SelectionGoal::None,
12130 )
12131 });
12132 })
12133 }
12134
12135 pub fn move_to_beginning(
12136 &mut self,
12137 _: &MoveToBeginning,
12138 window: &mut Window,
12139 cx: &mut Context<Self>,
12140 ) {
12141 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12142 cx.propagate();
12143 return;
12144 }
12145 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12147 s.select_ranges(vec![0..0]);
12148 });
12149 }
12150
12151 pub fn select_to_beginning(
12152 &mut self,
12153 _: &SelectToBeginning,
12154 window: &mut Window,
12155 cx: &mut Context<Self>,
12156 ) {
12157 let mut selection = self.selections.last::<Point>(cx);
12158 selection.set_head(Point::zero(), SelectionGoal::None);
12159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12160 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12161 s.select(vec![selection]);
12162 });
12163 }
12164
12165 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12166 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12167 cx.propagate();
12168 return;
12169 }
12170 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12171 let cursor = self.buffer.read(cx).read(cx).len();
12172 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12173 s.select_ranges(vec![cursor..cursor])
12174 });
12175 }
12176
12177 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12178 self.nav_history = nav_history;
12179 }
12180
12181 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12182 self.nav_history.as_ref()
12183 }
12184
12185 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12186 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12187 }
12188
12189 fn push_to_nav_history(
12190 &mut self,
12191 cursor_anchor: Anchor,
12192 new_position: Option<Point>,
12193 is_deactivate: bool,
12194 cx: &mut Context<Self>,
12195 ) {
12196 if let Some(nav_history) = self.nav_history.as_mut() {
12197 let buffer = self.buffer.read(cx).read(cx);
12198 let cursor_position = cursor_anchor.to_point(&buffer);
12199 let scroll_state = self.scroll_manager.anchor();
12200 let scroll_top_row = scroll_state.top_row(&buffer);
12201 drop(buffer);
12202
12203 if let Some(new_position) = new_position {
12204 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12205 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12206 return;
12207 }
12208 }
12209
12210 nav_history.push(
12211 Some(NavigationData {
12212 cursor_anchor,
12213 cursor_position,
12214 scroll_anchor: scroll_state,
12215 scroll_top_row,
12216 }),
12217 cx,
12218 );
12219 cx.emit(EditorEvent::PushedToNavHistory {
12220 anchor: cursor_anchor,
12221 is_deactivate,
12222 })
12223 }
12224 }
12225
12226 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12227 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12228 let buffer = self.buffer.read(cx).snapshot(cx);
12229 let mut selection = self.selections.first::<usize>(cx);
12230 selection.set_head(buffer.len(), SelectionGoal::None);
12231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12232 s.select(vec![selection]);
12233 });
12234 }
12235
12236 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12237 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12238 let end = self.buffer.read(cx).read(cx).len();
12239 self.change_selections(None, window, cx, |s| {
12240 s.select_ranges(vec![0..end]);
12241 });
12242 }
12243
12244 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12245 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12247 let mut selections = self.selections.all::<Point>(cx);
12248 let max_point = display_map.buffer_snapshot.max_point();
12249 for selection in &mut selections {
12250 let rows = selection.spanned_rows(true, &display_map);
12251 selection.start = Point::new(rows.start.0, 0);
12252 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12253 selection.reversed = false;
12254 }
12255 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12256 s.select(selections);
12257 });
12258 }
12259
12260 pub fn split_selection_into_lines(
12261 &mut self,
12262 _: &SplitSelectionIntoLines,
12263 window: &mut Window,
12264 cx: &mut Context<Self>,
12265 ) {
12266 let selections = self
12267 .selections
12268 .all::<Point>(cx)
12269 .into_iter()
12270 .map(|selection| selection.start..selection.end)
12271 .collect::<Vec<_>>();
12272 self.unfold_ranges(&selections, true, true, cx);
12273
12274 let mut new_selection_ranges = Vec::new();
12275 {
12276 let buffer = self.buffer.read(cx).read(cx);
12277 for selection in selections {
12278 for row in selection.start.row..selection.end.row {
12279 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12280 new_selection_ranges.push(cursor..cursor);
12281 }
12282
12283 let is_multiline_selection = selection.start.row != selection.end.row;
12284 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12285 // so this action feels more ergonomic when paired with other selection operations
12286 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12287 if !should_skip_last {
12288 new_selection_ranges.push(selection.end..selection.end);
12289 }
12290 }
12291 }
12292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12293 s.select_ranges(new_selection_ranges);
12294 });
12295 }
12296
12297 pub fn add_selection_above(
12298 &mut self,
12299 _: &AddSelectionAbove,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 self.add_selection(true, window, cx);
12304 }
12305
12306 pub fn add_selection_below(
12307 &mut self,
12308 _: &AddSelectionBelow,
12309 window: &mut Window,
12310 cx: &mut Context<Self>,
12311 ) {
12312 self.add_selection(false, window, cx);
12313 }
12314
12315 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12316 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12317
12318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12319 let mut selections = self.selections.all::<Point>(cx);
12320 let text_layout_details = self.text_layout_details(window);
12321 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12322 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12323 let range = oldest_selection.display_range(&display_map).sorted();
12324
12325 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12326 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12327 let positions = start_x.min(end_x)..start_x.max(end_x);
12328
12329 selections.clear();
12330 let mut stack = Vec::new();
12331 for row in range.start.row().0..=range.end.row().0 {
12332 if let Some(selection) = self.selections.build_columnar_selection(
12333 &display_map,
12334 DisplayRow(row),
12335 &positions,
12336 oldest_selection.reversed,
12337 &text_layout_details,
12338 ) {
12339 stack.push(selection.id);
12340 selections.push(selection);
12341 }
12342 }
12343
12344 if above {
12345 stack.reverse();
12346 }
12347
12348 AddSelectionsState { above, stack }
12349 });
12350
12351 let last_added_selection = *state.stack.last().unwrap();
12352 let mut new_selections = Vec::new();
12353 if above == state.above {
12354 let end_row = if above {
12355 DisplayRow(0)
12356 } else {
12357 display_map.max_point().row()
12358 };
12359
12360 'outer: for selection in selections {
12361 if selection.id == last_added_selection {
12362 let range = selection.display_range(&display_map).sorted();
12363 debug_assert_eq!(range.start.row(), range.end.row());
12364 let mut row = range.start.row();
12365 let positions =
12366 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12367 px(start)..px(end)
12368 } else {
12369 let start_x =
12370 display_map.x_for_display_point(range.start, &text_layout_details);
12371 let end_x =
12372 display_map.x_for_display_point(range.end, &text_layout_details);
12373 start_x.min(end_x)..start_x.max(end_x)
12374 };
12375
12376 while row != end_row {
12377 if above {
12378 row.0 -= 1;
12379 } else {
12380 row.0 += 1;
12381 }
12382
12383 if let Some(new_selection) = self.selections.build_columnar_selection(
12384 &display_map,
12385 row,
12386 &positions,
12387 selection.reversed,
12388 &text_layout_details,
12389 ) {
12390 state.stack.push(new_selection.id);
12391 if above {
12392 new_selections.push(new_selection);
12393 new_selections.push(selection);
12394 } else {
12395 new_selections.push(selection);
12396 new_selections.push(new_selection);
12397 }
12398
12399 continue 'outer;
12400 }
12401 }
12402 }
12403
12404 new_selections.push(selection);
12405 }
12406 } else {
12407 new_selections = selections;
12408 new_selections.retain(|s| s.id != last_added_selection);
12409 state.stack.pop();
12410 }
12411
12412 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12413 s.select(new_selections);
12414 });
12415 if state.stack.len() > 1 {
12416 self.add_selections_state = Some(state);
12417 }
12418 }
12419
12420 fn select_match_ranges(
12421 &mut self,
12422 range: Range<usize>,
12423 reversed: bool,
12424 replace_newest: bool,
12425 auto_scroll: Option<Autoscroll>,
12426 window: &mut Window,
12427 cx: &mut Context<Editor>,
12428 ) {
12429 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12430 self.change_selections(auto_scroll, window, cx, |s| {
12431 if replace_newest {
12432 s.delete(s.newest_anchor().id);
12433 }
12434 if reversed {
12435 s.insert_range(range.end..range.start);
12436 } else {
12437 s.insert_range(range);
12438 }
12439 });
12440 }
12441
12442 pub fn select_next_match_internal(
12443 &mut self,
12444 display_map: &DisplaySnapshot,
12445 replace_newest: bool,
12446 autoscroll: Option<Autoscroll>,
12447 window: &mut Window,
12448 cx: &mut Context<Self>,
12449 ) -> Result<()> {
12450 let buffer = &display_map.buffer_snapshot;
12451 let mut selections = self.selections.all::<usize>(cx);
12452 if let Some(mut select_next_state) = self.select_next_state.take() {
12453 let query = &select_next_state.query;
12454 if !select_next_state.done {
12455 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12456 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12457 let mut next_selected_range = None;
12458
12459 let bytes_after_last_selection =
12460 buffer.bytes_in_range(last_selection.end..buffer.len());
12461 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12462 let query_matches = query
12463 .stream_find_iter(bytes_after_last_selection)
12464 .map(|result| (last_selection.end, result))
12465 .chain(
12466 query
12467 .stream_find_iter(bytes_before_first_selection)
12468 .map(|result| (0, result)),
12469 );
12470
12471 for (start_offset, query_match) in query_matches {
12472 let query_match = query_match.unwrap(); // can only fail due to I/O
12473 let offset_range =
12474 start_offset + query_match.start()..start_offset + query_match.end();
12475 let display_range = offset_range.start.to_display_point(display_map)
12476 ..offset_range.end.to_display_point(display_map);
12477
12478 if !select_next_state.wordwise
12479 || (!movement::is_inside_word(display_map, display_range.start)
12480 && !movement::is_inside_word(display_map, display_range.end))
12481 {
12482 // TODO: This is n^2, because we might check all the selections
12483 if !selections
12484 .iter()
12485 .any(|selection| selection.range().overlaps(&offset_range))
12486 {
12487 next_selected_range = Some(offset_range);
12488 break;
12489 }
12490 }
12491 }
12492
12493 if let Some(next_selected_range) = next_selected_range {
12494 self.select_match_ranges(
12495 next_selected_range,
12496 last_selection.reversed,
12497 replace_newest,
12498 autoscroll,
12499 window,
12500 cx,
12501 );
12502 } else {
12503 select_next_state.done = true;
12504 }
12505 }
12506
12507 self.select_next_state = Some(select_next_state);
12508 } else {
12509 let mut only_carets = true;
12510 let mut same_text_selected = true;
12511 let mut selected_text = None;
12512
12513 let mut selections_iter = selections.iter().peekable();
12514 while let Some(selection) = selections_iter.next() {
12515 if selection.start != selection.end {
12516 only_carets = false;
12517 }
12518
12519 if same_text_selected {
12520 if selected_text.is_none() {
12521 selected_text =
12522 Some(buffer.text_for_range(selection.range()).collect::<String>());
12523 }
12524
12525 if let Some(next_selection) = selections_iter.peek() {
12526 if next_selection.range().len() == selection.range().len() {
12527 let next_selected_text = buffer
12528 .text_for_range(next_selection.range())
12529 .collect::<String>();
12530 if Some(next_selected_text) != selected_text {
12531 same_text_selected = false;
12532 selected_text = None;
12533 }
12534 } else {
12535 same_text_selected = false;
12536 selected_text = None;
12537 }
12538 }
12539 }
12540 }
12541
12542 if only_carets {
12543 for selection in &mut selections {
12544 let word_range = movement::surrounding_word(
12545 display_map,
12546 selection.start.to_display_point(display_map),
12547 );
12548 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12549 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12550 selection.goal = SelectionGoal::None;
12551 selection.reversed = false;
12552 self.select_match_ranges(
12553 selection.start..selection.end,
12554 selection.reversed,
12555 replace_newest,
12556 autoscroll,
12557 window,
12558 cx,
12559 );
12560 }
12561
12562 if selections.len() == 1 {
12563 let selection = selections
12564 .last()
12565 .expect("ensured that there's only one selection");
12566 let query = buffer
12567 .text_for_range(selection.start..selection.end)
12568 .collect::<String>();
12569 let is_empty = query.is_empty();
12570 let select_state = SelectNextState {
12571 query: AhoCorasick::new(&[query])?,
12572 wordwise: true,
12573 done: is_empty,
12574 };
12575 self.select_next_state = Some(select_state);
12576 } else {
12577 self.select_next_state = None;
12578 }
12579 } else if let Some(selected_text) = selected_text {
12580 self.select_next_state = Some(SelectNextState {
12581 query: AhoCorasick::new(&[selected_text])?,
12582 wordwise: false,
12583 done: false,
12584 });
12585 self.select_next_match_internal(
12586 display_map,
12587 replace_newest,
12588 autoscroll,
12589 window,
12590 cx,
12591 )?;
12592 }
12593 }
12594 Ok(())
12595 }
12596
12597 pub fn select_all_matches(
12598 &mut self,
12599 _action: &SelectAllMatches,
12600 window: &mut Window,
12601 cx: &mut Context<Self>,
12602 ) -> Result<()> {
12603 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12604
12605 self.push_to_selection_history();
12606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12607
12608 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12609 let Some(select_next_state) = self.select_next_state.as_mut() else {
12610 return Ok(());
12611 };
12612 if select_next_state.done {
12613 return Ok(());
12614 }
12615
12616 let mut new_selections = Vec::new();
12617
12618 let reversed = self.selections.oldest::<usize>(cx).reversed;
12619 let buffer = &display_map.buffer_snapshot;
12620 let query_matches = select_next_state
12621 .query
12622 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12623
12624 for query_match in query_matches.into_iter() {
12625 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12626 let offset_range = if reversed {
12627 query_match.end()..query_match.start()
12628 } else {
12629 query_match.start()..query_match.end()
12630 };
12631 let display_range = offset_range.start.to_display_point(&display_map)
12632 ..offset_range.end.to_display_point(&display_map);
12633
12634 if !select_next_state.wordwise
12635 || (!movement::is_inside_word(&display_map, display_range.start)
12636 && !movement::is_inside_word(&display_map, display_range.end))
12637 {
12638 new_selections.push(offset_range.start..offset_range.end);
12639 }
12640 }
12641
12642 select_next_state.done = true;
12643 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12644 self.change_selections(None, window, cx, |selections| {
12645 selections.select_ranges(new_selections)
12646 });
12647
12648 Ok(())
12649 }
12650
12651 pub fn select_next(
12652 &mut self,
12653 action: &SelectNext,
12654 window: &mut Window,
12655 cx: &mut Context<Self>,
12656 ) -> Result<()> {
12657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12658 self.push_to_selection_history();
12659 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12660 self.select_next_match_internal(
12661 &display_map,
12662 action.replace_newest,
12663 Some(Autoscroll::newest()),
12664 window,
12665 cx,
12666 )?;
12667 Ok(())
12668 }
12669
12670 pub fn select_previous(
12671 &mut self,
12672 action: &SelectPrevious,
12673 window: &mut Window,
12674 cx: &mut Context<Self>,
12675 ) -> Result<()> {
12676 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12677 self.push_to_selection_history();
12678 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12679 let buffer = &display_map.buffer_snapshot;
12680 let mut selections = self.selections.all::<usize>(cx);
12681 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12682 let query = &select_prev_state.query;
12683 if !select_prev_state.done {
12684 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12685 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12686 let mut next_selected_range = None;
12687 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12688 let bytes_before_last_selection =
12689 buffer.reversed_bytes_in_range(0..last_selection.start);
12690 let bytes_after_first_selection =
12691 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12692 let query_matches = query
12693 .stream_find_iter(bytes_before_last_selection)
12694 .map(|result| (last_selection.start, result))
12695 .chain(
12696 query
12697 .stream_find_iter(bytes_after_first_selection)
12698 .map(|result| (buffer.len(), result)),
12699 );
12700 for (end_offset, query_match) in query_matches {
12701 let query_match = query_match.unwrap(); // can only fail due to I/O
12702 let offset_range =
12703 end_offset - query_match.end()..end_offset - query_match.start();
12704 let display_range = offset_range.start.to_display_point(&display_map)
12705 ..offset_range.end.to_display_point(&display_map);
12706
12707 if !select_prev_state.wordwise
12708 || (!movement::is_inside_word(&display_map, display_range.start)
12709 && !movement::is_inside_word(&display_map, display_range.end))
12710 {
12711 next_selected_range = Some(offset_range);
12712 break;
12713 }
12714 }
12715
12716 if let Some(next_selected_range) = next_selected_range {
12717 self.select_match_ranges(
12718 next_selected_range,
12719 last_selection.reversed,
12720 action.replace_newest,
12721 Some(Autoscroll::newest()),
12722 window,
12723 cx,
12724 );
12725 } else {
12726 select_prev_state.done = true;
12727 }
12728 }
12729
12730 self.select_prev_state = Some(select_prev_state);
12731 } else {
12732 let mut only_carets = true;
12733 let mut same_text_selected = true;
12734 let mut selected_text = None;
12735
12736 let mut selections_iter = selections.iter().peekable();
12737 while let Some(selection) = selections_iter.next() {
12738 if selection.start != selection.end {
12739 only_carets = false;
12740 }
12741
12742 if same_text_selected {
12743 if selected_text.is_none() {
12744 selected_text =
12745 Some(buffer.text_for_range(selection.range()).collect::<String>());
12746 }
12747
12748 if let Some(next_selection) = selections_iter.peek() {
12749 if next_selection.range().len() == selection.range().len() {
12750 let next_selected_text = buffer
12751 .text_for_range(next_selection.range())
12752 .collect::<String>();
12753 if Some(next_selected_text) != selected_text {
12754 same_text_selected = false;
12755 selected_text = None;
12756 }
12757 } else {
12758 same_text_selected = false;
12759 selected_text = None;
12760 }
12761 }
12762 }
12763 }
12764
12765 if only_carets {
12766 for selection in &mut selections {
12767 let word_range = movement::surrounding_word(
12768 &display_map,
12769 selection.start.to_display_point(&display_map),
12770 );
12771 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12772 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12773 selection.goal = SelectionGoal::None;
12774 selection.reversed = false;
12775 self.select_match_ranges(
12776 selection.start..selection.end,
12777 selection.reversed,
12778 action.replace_newest,
12779 Some(Autoscroll::newest()),
12780 window,
12781 cx,
12782 );
12783 }
12784 if selections.len() == 1 {
12785 let selection = selections
12786 .last()
12787 .expect("ensured that there's only one selection");
12788 let query = buffer
12789 .text_for_range(selection.start..selection.end)
12790 .collect::<String>();
12791 let is_empty = query.is_empty();
12792 let select_state = SelectNextState {
12793 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12794 wordwise: true,
12795 done: is_empty,
12796 };
12797 self.select_prev_state = Some(select_state);
12798 } else {
12799 self.select_prev_state = None;
12800 }
12801 } else if let Some(selected_text) = selected_text {
12802 self.select_prev_state = Some(SelectNextState {
12803 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12804 wordwise: false,
12805 done: false,
12806 });
12807 self.select_previous(action, window, cx)?;
12808 }
12809 }
12810 Ok(())
12811 }
12812
12813 pub fn find_next_match(
12814 &mut self,
12815 _: &FindNextMatch,
12816 window: &mut Window,
12817 cx: &mut Context<Self>,
12818 ) -> Result<()> {
12819 let selections = self.selections.disjoint_anchors();
12820 match selections.first() {
12821 Some(first) if selections.len() >= 2 => {
12822 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12823 s.select_ranges([first.range()]);
12824 });
12825 }
12826 _ => self.select_next(
12827 &SelectNext {
12828 replace_newest: true,
12829 },
12830 window,
12831 cx,
12832 )?,
12833 }
12834 Ok(())
12835 }
12836
12837 pub fn find_previous_match(
12838 &mut self,
12839 _: &FindPreviousMatch,
12840 window: &mut Window,
12841 cx: &mut Context<Self>,
12842 ) -> Result<()> {
12843 let selections = self.selections.disjoint_anchors();
12844 match selections.last() {
12845 Some(last) if selections.len() >= 2 => {
12846 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12847 s.select_ranges([last.range()]);
12848 });
12849 }
12850 _ => self.select_previous(
12851 &SelectPrevious {
12852 replace_newest: true,
12853 },
12854 window,
12855 cx,
12856 )?,
12857 }
12858 Ok(())
12859 }
12860
12861 pub fn toggle_comments(
12862 &mut self,
12863 action: &ToggleComments,
12864 window: &mut Window,
12865 cx: &mut Context<Self>,
12866 ) {
12867 if self.read_only(cx) {
12868 return;
12869 }
12870 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12871 let text_layout_details = &self.text_layout_details(window);
12872 self.transact(window, cx, |this, window, cx| {
12873 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12874 let mut edits = Vec::new();
12875 let mut selection_edit_ranges = Vec::new();
12876 let mut last_toggled_row = None;
12877 let snapshot = this.buffer.read(cx).read(cx);
12878 let empty_str: Arc<str> = Arc::default();
12879 let mut suffixes_inserted = Vec::new();
12880 let ignore_indent = action.ignore_indent;
12881
12882 fn comment_prefix_range(
12883 snapshot: &MultiBufferSnapshot,
12884 row: MultiBufferRow,
12885 comment_prefix: &str,
12886 comment_prefix_whitespace: &str,
12887 ignore_indent: bool,
12888 ) -> Range<Point> {
12889 let indent_size = if ignore_indent {
12890 0
12891 } else {
12892 snapshot.indent_size_for_line(row).len
12893 };
12894
12895 let start = Point::new(row.0, indent_size);
12896
12897 let mut line_bytes = snapshot
12898 .bytes_in_range(start..snapshot.max_point())
12899 .flatten()
12900 .copied();
12901
12902 // If this line currently begins with the line comment prefix, then record
12903 // the range containing the prefix.
12904 if line_bytes
12905 .by_ref()
12906 .take(comment_prefix.len())
12907 .eq(comment_prefix.bytes())
12908 {
12909 // Include any whitespace that matches the comment prefix.
12910 let matching_whitespace_len = line_bytes
12911 .zip(comment_prefix_whitespace.bytes())
12912 .take_while(|(a, b)| a == b)
12913 .count() as u32;
12914 let end = Point::new(
12915 start.row,
12916 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12917 );
12918 start..end
12919 } else {
12920 start..start
12921 }
12922 }
12923
12924 fn comment_suffix_range(
12925 snapshot: &MultiBufferSnapshot,
12926 row: MultiBufferRow,
12927 comment_suffix: &str,
12928 comment_suffix_has_leading_space: bool,
12929 ) -> Range<Point> {
12930 let end = Point::new(row.0, snapshot.line_len(row));
12931 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12932
12933 let mut line_end_bytes = snapshot
12934 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12935 .flatten()
12936 .copied();
12937
12938 let leading_space_len = if suffix_start_column > 0
12939 && line_end_bytes.next() == Some(b' ')
12940 && comment_suffix_has_leading_space
12941 {
12942 1
12943 } else {
12944 0
12945 };
12946
12947 // If this line currently begins with the line comment prefix, then record
12948 // the range containing the prefix.
12949 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12950 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12951 start..end
12952 } else {
12953 end..end
12954 }
12955 }
12956
12957 // TODO: Handle selections that cross excerpts
12958 for selection in &mut selections {
12959 let start_column = snapshot
12960 .indent_size_for_line(MultiBufferRow(selection.start.row))
12961 .len;
12962 let language = if let Some(language) =
12963 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12964 {
12965 language
12966 } else {
12967 continue;
12968 };
12969
12970 selection_edit_ranges.clear();
12971
12972 // If multiple selections contain a given row, avoid processing that
12973 // row more than once.
12974 let mut start_row = MultiBufferRow(selection.start.row);
12975 if last_toggled_row == Some(start_row) {
12976 start_row = start_row.next_row();
12977 }
12978 let end_row =
12979 if selection.end.row > selection.start.row && selection.end.column == 0 {
12980 MultiBufferRow(selection.end.row - 1)
12981 } else {
12982 MultiBufferRow(selection.end.row)
12983 };
12984 last_toggled_row = Some(end_row);
12985
12986 if start_row > end_row {
12987 continue;
12988 }
12989
12990 // If the language has line comments, toggle those.
12991 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12992
12993 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12994 if ignore_indent {
12995 full_comment_prefixes = full_comment_prefixes
12996 .into_iter()
12997 .map(|s| Arc::from(s.trim_end()))
12998 .collect();
12999 }
13000
13001 if !full_comment_prefixes.is_empty() {
13002 let first_prefix = full_comment_prefixes
13003 .first()
13004 .expect("prefixes is non-empty");
13005 let prefix_trimmed_lengths = full_comment_prefixes
13006 .iter()
13007 .map(|p| p.trim_end_matches(' ').len())
13008 .collect::<SmallVec<[usize; 4]>>();
13009
13010 let mut all_selection_lines_are_comments = true;
13011
13012 for row in start_row.0..=end_row.0 {
13013 let row = MultiBufferRow(row);
13014 if start_row < end_row && snapshot.is_line_blank(row) {
13015 continue;
13016 }
13017
13018 let prefix_range = full_comment_prefixes
13019 .iter()
13020 .zip(prefix_trimmed_lengths.iter().copied())
13021 .map(|(prefix, trimmed_prefix_len)| {
13022 comment_prefix_range(
13023 snapshot.deref(),
13024 row,
13025 &prefix[..trimmed_prefix_len],
13026 &prefix[trimmed_prefix_len..],
13027 ignore_indent,
13028 )
13029 })
13030 .max_by_key(|range| range.end.column - range.start.column)
13031 .expect("prefixes is non-empty");
13032
13033 if prefix_range.is_empty() {
13034 all_selection_lines_are_comments = false;
13035 }
13036
13037 selection_edit_ranges.push(prefix_range);
13038 }
13039
13040 if all_selection_lines_are_comments {
13041 edits.extend(
13042 selection_edit_ranges
13043 .iter()
13044 .cloned()
13045 .map(|range| (range, empty_str.clone())),
13046 );
13047 } else {
13048 let min_column = selection_edit_ranges
13049 .iter()
13050 .map(|range| range.start.column)
13051 .min()
13052 .unwrap_or(0);
13053 edits.extend(selection_edit_ranges.iter().map(|range| {
13054 let position = Point::new(range.start.row, min_column);
13055 (position..position, first_prefix.clone())
13056 }));
13057 }
13058 } else if let Some((full_comment_prefix, comment_suffix)) =
13059 language.block_comment_delimiters()
13060 {
13061 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13062 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13063 let prefix_range = comment_prefix_range(
13064 snapshot.deref(),
13065 start_row,
13066 comment_prefix,
13067 comment_prefix_whitespace,
13068 ignore_indent,
13069 );
13070 let suffix_range = comment_suffix_range(
13071 snapshot.deref(),
13072 end_row,
13073 comment_suffix.trim_start_matches(' '),
13074 comment_suffix.starts_with(' '),
13075 );
13076
13077 if prefix_range.is_empty() || suffix_range.is_empty() {
13078 edits.push((
13079 prefix_range.start..prefix_range.start,
13080 full_comment_prefix.clone(),
13081 ));
13082 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13083 suffixes_inserted.push((end_row, comment_suffix.len()));
13084 } else {
13085 edits.push((prefix_range, empty_str.clone()));
13086 edits.push((suffix_range, empty_str.clone()));
13087 }
13088 } else {
13089 continue;
13090 }
13091 }
13092
13093 drop(snapshot);
13094 this.buffer.update(cx, |buffer, cx| {
13095 buffer.edit(edits, None, cx);
13096 });
13097
13098 // Adjust selections so that they end before any comment suffixes that
13099 // were inserted.
13100 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13101 let mut selections = this.selections.all::<Point>(cx);
13102 let snapshot = this.buffer.read(cx).read(cx);
13103 for selection in &mut selections {
13104 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13105 match row.cmp(&MultiBufferRow(selection.end.row)) {
13106 Ordering::Less => {
13107 suffixes_inserted.next();
13108 continue;
13109 }
13110 Ordering::Greater => break,
13111 Ordering::Equal => {
13112 if selection.end.column == snapshot.line_len(row) {
13113 if selection.is_empty() {
13114 selection.start.column -= suffix_len as u32;
13115 }
13116 selection.end.column -= suffix_len as u32;
13117 }
13118 break;
13119 }
13120 }
13121 }
13122 }
13123
13124 drop(snapshot);
13125 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13126 s.select(selections)
13127 });
13128
13129 let selections = this.selections.all::<Point>(cx);
13130 let selections_on_single_row = selections.windows(2).all(|selections| {
13131 selections[0].start.row == selections[1].start.row
13132 && selections[0].end.row == selections[1].end.row
13133 && selections[0].start.row == selections[0].end.row
13134 });
13135 let selections_selecting = selections
13136 .iter()
13137 .any(|selection| selection.start != selection.end);
13138 let advance_downwards = action.advance_downwards
13139 && selections_on_single_row
13140 && !selections_selecting
13141 && !matches!(this.mode, EditorMode::SingleLine { .. });
13142
13143 if advance_downwards {
13144 let snapshot = this.buffer.read(cx).snapshot(cx);
13145
13146 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13147 s.move_cursors_with(|display_snapshot, display_point, _| {
13148 let mut point = display_point.to_point(display_snapshot);
13149 point.row += 1;
13150 point = snapshot.clip_point(point, Bias::Left);
13151 let display_point = point.to_display_point(display_snapshot);
13152 let goal = SelectionGoal::HorizontalPosition(
13153 display_snapshot
13154 .x_for_display_point(display_point, text_layout_details)
13155 .into(),
13156 );
13157 (display_point, goal)
13158 })
13159 });
13160 }
13161 });
13162 }
13163
13164 pub fn select_enclosing_symbol(
13165 &mut self,
13166 _: &SelectEnclosingSymbol,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13171
13172 let buffer = self.buffer.read(cx).snapshot(cx);
13173 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13174
13175 fn update_selection(
13176 selection: &Selection<usize>,
13177 buffer_snap: &MultiBufferSnapshot,
13178 ) -> Option<Selection<usize>> {
13179 let cursor = selection.head();
13180 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13181 for symbol in symbols.iter().rev() {
13182 let start = symbol.range.start.to_offset(buffer_snap);
13183 let end = symbol.range.end.to_offset(buffer_snap);
13184 let new_range = start..end;
13185 if start < selection.start || end > selection.end {
13186 return Some(Selection {
13187 id: selection.id,
13188 start: new_range.start,
13189 end: new_range.end,
13190 goal: SelectionGoal::None,
13191 reversed: selection.reversed,
13192 });
13193 }
13194 }
13195 None
13196 }
13197
13198 let mut selected_larger_symbol = false;
13199 let new_selections = old_selections
13200 .iter()
13201 .map(|selection| match update_selection(selection, &buffer) {
13202 Some(new_selection) => {
13203 if new_selection.range() != selection.range() {
13204 selected_larger_symbol = true;
13205 }
13206 new_selection
13207 }
13208 None => selection.clone(),
13209 })
13210 .collect::<Vec<_>>();
13211
13212 if selected_larger_symbol {
13213 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13214 s.select(new_selections);
13215 });
13216 }
13217 }
13218
13219 pub fn select_larger_syntax_node(
13220 &mut self,
13221 _: &SelectLargerSyntaxNode,
13222 window: &mut Window,
13223 cx: &mut Context<Self>,
13224 ) {
13225 let Some(visible_row_count) = self.visible_row_count() else {
13226 return;
13227 };
13228 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13229 if old_selections.is_empty() {
13230 return;
13231 }
13232
13233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13234
13235 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13236 let buffer = self.buffer.read(cx).snapshot(cx);
13237
13238 let mut selected_larger_node = false;
13239 let mut new_selections = old_selections
13240 .iter()
13241 .map(|selection| {
13242 let old_range = selection.start..selection.end;
13243
13244 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13245 // manually select word at selection
13246 if ["string_content", "inline"].contains(&node.kind()) {
13247 let word_range = {
13248 let display_point = buffer
13249 .offset_to_point(old_range.start)
13250 .to_display_point(&display_map);
13251 let Range { start, end } =
13252 movement::surrounding_word(&display_map, display_point);
13253 start.to_point(&display_map).to_offset(&buffer)
13254 ..end.to_point(&display_map).to_offset(&buffer)
13255 };
13256 // ignore if word is already selected
13257 if !word_range.is_empty() && old_range != word_range {
13258 let last_word_range = {
13259 let display_point = buffer
13260 .offset_to_point(old_range.end)
13261 .to_display_point(&display_map);
13262 let Range { start, end } =
13263 movement::surrounding_word(&display_map, display_point);
13264 start.to_point(&display_map).to_offset(&buffer)
13265 ..end.to_point(&display_map).to_offset(&buffer)
13266 };
13267 // only select word if start and end point belongs to same word
13268 if word_range == last_word_range {
13269 selected_larger_node = true;
13270 return Selection {
13271 id: selection.id,
13272 start: word_range.start,
13273 end: word_range.end,
13274 goal: SelectionGoal::None,
13275 reversed: selection.reversed,
13276 };
13277 }
13278 }
13279 }
13280 }
13281
13282 let mut new_range = old_range.clone();
13283 while let Some((_node, containing_range)) =
13284 buffer.syntax_ancestor(new_range.clone())
13285 {
13286 new_range = match containing_range {
13287 MultiOrSingleBufferOffsetRange::Single(_) => break,
13288 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13289 };
13290 if !display_map.intersects_fold(new_range.start)
13291 && !display_map.intersects_fold(new_range.end)
13292 {
13293 break;
13294 }
13295 }
13296
13297 selected_larger_node |= new_range != old_range;
13298 Selection {
13299 id: selection.id,
13300 start: new_range.start,
13301 end: new_range.end,
13302 goal: SelectionGoal::None,
13303 reversed: selection.reversed,
13304 }
13305 })
13306 .collect::<Vec<_>>();
13307
13308 if !selected_larger_node {
13309 return; // don't put this call in the history
13310 }
13311
13312 // scroll based on transformation done to the last selection created by the user
13313 let (last_old, last_new) = old_selections
13314 .last()
13315 .zip(new_selections.last().cloned())
13316 .expect("old_selections isn't empty");
13317
13318 // revert selection
13319 let is_selection_reversed = {
13320 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13321 new_selections.last_mut().expect("checked above").reversed =
13322 should_newest_selection_be_reversed;
13323 should_newest_selection_be_reversed
13324 };
13325
13326 if selected_larger_node {
13327 self.select_syntax_node_history.disable_clearing = true;
13328 self.change_selections(None, window, cx, |s| {
13329 s.select(new_selections.clone());
13330 });
13331 self.select_syntax_node_history.disable_clearing = false;
13332 }
13333
13334 let start_row = last_new.start.to_display_point(&display_map).row().0;
13335 let end_row = last_new.end.to_display_point(&display_map).row().0;
13336 let selection_height = end_row - start_row + 1;
13337 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13338
13339 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13340 let scroll_behavior = if fits_on_the_screen {
13341 self.request_autoscroll(Autoscroll::fit(), cx);
13342 SelectSyntaxNodeScrollBehavior::FitSelection
13343 } else if is_selection_reversed {
13344 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13345 SelectSyntaxNodeScrollBehavior::CursorTop
13346 } else {
13347 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13348 SelectSyntaxNodeScrollBehavior::CursorBottom
13349 };
13350
13351 self.select_syntax_node_history.push((
13352 old_selections,
13353 scroll_behavior,
13354 is_selection_reversed,
13355 ));
13356 }
13357
13358 pub fn select_smaller_syntax_node(
13359 &mut self,
13360 _: &SelectSmallerSyntaxNode,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13365
13366 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13367 self.select_syntax_node_history.pop()
13368 {
13369 if let Some(selection) = selections.last_mut() {
13370 selection.reversed = is_selection_reversed;
13371 }
13372
13373 self.select_syntax_node_history.disable_clearing = true;
13374 self.change_selections(None, window, cx, |s| {
13375 s.select(selections.to_vec());
13376 });
13377 self.select_syntax_node_history.disable_clearing = false;
13378
13379 match scroll_behavior {
13380 SelectSyntaxNodeScrollBehavior::CursorTop => {
13381 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13382 }
13383 SelectSyntaxNodeScrollBehavior::FitSelection => {
13384 self.request_autoscroll(Autoscroll::fit(), cx);
13385 }
13386 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13387 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13388 }
13389 }
13390 }
13391 }
13392
13393 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13394 if !EditorSettings::get_global(cx).gutter.runnables {
13395 self.clear_tasks();
13396 return Task::ready(());
13397 }
13398 let project = self.project.as_ref().map(Entity::downgrade);
13399 let task_sources = self.lsp_task_sources(cx);
13400 cx.spawn_in(window, async move |editor, cx| {
13401 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13402 let Some(project) = project.and_then(|p| p.upgrade()) else {
13403 return;
13404 };
13405 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13406 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13407 }) else {
13408 return;
13409 };
13410
13411 let hide_runnables = project
13412 .update(cx, |project, cx| {
13413 // Do not display any test indicators in non-dev server remote projects.
13414 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13415 })
13416 .unwrap_or(true);
13417 if hide_runnables {
13418 return;
13419 }
13420 let new_rows =
13421 cx.background_spawn({
13422 let snapshot = display_snapshot.clone();
13423 async move {
13424 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13425 }
13426 })
13427 .await;
13428 let Ok(lsp_tasks) =
13429 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13430 else {
13431 return;
13432 };
13433 let lsp_tasks = lsp_tasks.await;
13434
13435 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13436 lsp_tasks
13437 .into_iter()
13438 .flat_map(|(kind, tasks)| {
13439 tasks.into_iter().filter_map(move |(location, task)| {
13440 Some((kind.clone(), location?, task))
13441 })
13442 })
13443 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13444 let buffer = location.target.buffer;
13445 let buffer_snapshot = buffer.read(cx).snapshot();
13446 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13447 |(excerpt_id, snapshot, _)| {
13448 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13449 display_snapshot
13450 .buffer_snapshot
13451 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13452 } else {
13453 None
13454 }
13455 },
13456 );
13457 if let Some(offset) = offset {
13458 let task_buffer_range =
13459 location.target.range.to_point(&buffer_snapshot);
13460 let context_buffer_range =
13461 task_buffer_range.to_offset(&buffer_snapshot);
13462 let context_range = BufferOffset(context_buffer_range.start)
13463 ..BufferOffset(context_buffer_range.end);
13464
13465 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13466 .or_insert_with(|| RunnableTasks {
13467 templates: Vec::new(),
13468 offset,
13469 column: task_buffer_range.start.column,
13470 extra_variables: HashMap::default(),
13471 context_range,
13472 })
13473 .templates
13474 .push((kind, task.original_task().clone()));
13475 }
13476
13477 acc
13478 })
13479 }) else {
13480 return;
13481 };
13482
13483 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13484 editor
13485 .update(cx, |editor, _| {
13486 editor.clear_tasks();
13487 for (key, mut value) in rows {
13488 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13489 value.templates.extend(lsp_tasks.templates);
13490 }
13491
13492 editor.insert_tasks(key, value);
13493 }
13494 for (key, value) in lsp_tasks_by_rows {
13495 editor.insert_tasks(key, value);
13496 }
13497 })
13498 .ok();
13499 })
13500 }
13501 fn fetch_runnable_ranges(
13502 snapshot: &DisplaySnapshot,
13503 range: Range<Anchor>,
13504 ) -> Vec<language::RunnableRange> {
13505 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13506 }
13507
13508 fn runnable_rows(
13509 project: Entity<Project>,
13510 snapshot: DisplaySnapshot,
13511 runnable_ranges: Vec<RunnableRange>,
13512 mut cx: AsyncWindowContext,
13513 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13514 runnable_ranges
13515 .into_iter()
13516 .filter_map(|mut runnable| {
13517 let tasks = cx
13518 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13519 .ok()?;
13520 if tasks.is_empty() {
13521 return None;
13522 }
13523
13524 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13525
13526 let row = snapshot
13527 .buffer_snapshot
13528 .buffer_line_for_row(MultiBufferRow(point.row))?
13529 .1
13530 .start
13531 .row;
13532
13533 let context_range =
13534 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13535 Some((
13536 (runnable.buffer_id, row),
13537 RunnableTasks {
13538 templates: tasks,
13539 offset: snapshot
13540 .buffer_snapshot
13541 .anchor_before(runnable.run_range.start),
13542 context_range,
13543 column: point.column,
13544 extra_variables: runnable.extra_captures,
13545 },
13546 ))
13547 })
13548 .collect()
13549 }
13550
13551 fn templates_with_tags(
13552 project: &Entity<Project>,
13553 runnable: &mut Runnable,
13554 cx: &mut App,
13555 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13556 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13557 let (worktree_id, file) = project
13558 .buffer_for_id(runnable.buffer, cx)
13559 .and_then(|buffer| buffer.read(cx).file())
13560 .map(|file| (file.worktree_id(cx), file.clone()))
13561 .unzip();
13562
13563 (
13564 project.task_store().read(cx).task_inventory().cloned(),
13565 worktree_id,
13566 file,
13567 )
13568 });
13569
13570 let mut templates_with_tags = mem::take(&mut runnable.tags)
13571 .into_iter()
13572 .flat_map(|RunnableTag(tag)| {
13573 inventory
13574 .as_ref()
13575 .into_iter()
13576 .flat_map(|inventory| {
13577 inventory.read(cx).list_tasks(
13578 file.clone(),
13579 Some(runnable.language.clone()),
13580 worktree_id,
13581 cx,
13582 )
13583 })
13584 .filter(move |(_, template)| {
13585 template.tags.iter().any(|source_tag| source_tag == &tag)
13586 })
13587 })
13588 .sorted_by_key(|(kind, _)| kind.to_owned())
13589 .collect::<Vec<_>>();
13590 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13591 // Strongest source wins; if we have worktree tag binding, prefer that to
13592 // global and language bindings;
13593 // if we have a global binding, prefer that to language binding.
13594 let first_mismatch = templates_with_tags
13595 .iter()
13596 .position(|(tag_source, _)| tag_source != leading_tag_source);
13597 if let Some(index) = first_mismatch {
13598 templates_with_tags.truncate(index);
13599 }
13600 }
13601
13602 templates_with_tags
13603 }
13604
13605 pub fn move_to_enclosing_bracket(
13606 &mut self,
13607 _: &MoveToEnclosingBracket,
13608 window: &mut Window,
13609 cx: &mut Context<Self>,
13610 ) {
13611 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13612 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13613 s.move_offsets_with(|snapshot, selection| {
13614 let Some(enclosing_bracket_ranges) =
13615 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13616 else {
13617 return;
13618 };
13619
13620 let mut best_length = usize::MAX;
13621 let mut best_inside = false;
13622 let mut best_in_bracket_range = false;
13623 let mut best_destination = None;
13624 for (open, close) in enclosing_bracket_ranges {
13625 let close = close.to_inclusive();
13626 let length = close.end() - open.start;
13627 let inside = selection.start >= open.end && selection.end <= *close.start();
13628 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13629 || close.contains(&selection.head());
13630
13631 // If best is next to a bracket and current isn't, skip
13632 if !in_bracket_range && best_in_bracket_range {
13633 continue;
13634 }
13635
13636 // Prefer smaller lengths unless best is inside and current isn't
13637 if length > best_length && (best_inside || !inside) {
13638 continue;
13639 }
13640
13641 best_length = length;
13642 best_inside = inside;
13643 best_in_bracket_range = in_bracket_range;
13644 best_destination = Some(
13645 if close.contains(&selection.start) && close.contains(&selection.end) {
13646 if inside { open.end } else { open.start }
13647 } else if inside {
13648 *close.start()
13649 } else {
13650 *close.end()
13651 },
13652 );
13653 }
13654
13655 if let Some(destination) = best_destination {
13656 selection.collapse_to(destination, SelectionGoal::None);
13657 }
13658 })
13659 });
13660 }
13661
13662 pub fn undo_selection(
13663 &mut self,
13664 _: &UndoSelection,
13665 window: &mut Window,
13666 cx: &mut Context<Self>,
13667 ) {
13668 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13669 self.end_selection(window, cx);
13670 self.selection_history.mode = SelectionHistoryMode::Undoing;
13671 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13672 self.change_selections(None, window, cx, |s| {
13673 s.select_anchors(entry.selections.to_vec())
13674 });
13675 self.select_next_state = entry.select_next_state;
13676 self.select_prev_state = entry.select_prev_state;
13677 self.add_selections_state = entry.add_selections_state;
13678 self.request_autoscroll(Autoscroll::newest(), cx);
13679 }
13680 self.selection_history.mode = SelectionHistoryMode::Normal;
13681 }
13682
13683 pub fn redo_selection(
13684 &mut self,
13685 _: &RedoSelection,
13686 window: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13690 self.end_selection(window, cx);
13691 self.selection_history.mode = SelectionHistoryMode::Redoing;
13692 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13693 self.change_selections(None, window, cx, |s| {
13694 s.select_anchors(entry.selections.to_vec())
13695 });
13696 self.select_next_state = entry.select_next_state;
13697 self.select_prev_state = entry.select_prev_state;
13698 self.add_selections_state = entry.add_selections_state;
13699 self.request_autoscroll(Autoscroll::newest(), cx);
13700 }
13701 self.selection_history.mode = SelectionHistoryMode::Normal;
13702 }
13703
13704 pub fn expand_excerpts(
13705 &mut self,
13706 action: &ExpandExcerpts,
13707 _: &mut Window,
13708 cx: &mut Context<Self>,
13709 ) {
13710 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13711 }
13712
13713 pub fn expand_excerpts_down(
13714 &mut self,
13715 action: &ExpandExcerptsDown,
13716 _: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13720 }
13721
13722 pub fn expand_excerpts_up(
13723 &mut self,
13724 action: &ExpandExcerptsUp,
13725 _: &mut Window,
13726 cx: &mut Context<Self>,
13727 ) {
13728 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13729 }
13730
13731 pub fn expand_excerpts_for_direction(
13732 &mut self,
13733 lines: u32,
13734 direction: ExpandExcerptDirection,
13735
13736 cx: &mut Context<Self>,
13737 ) {
13738 let selections = self.selections.disjoint_anchors();
13739
13740 let lines = if lines == 0 {
13741 EditorSettings::get_global(cx).expand_excerpt_lines
13742 } else {
13743 lines
13744 };
13745
13746 self.buffer.update(cx, |buffer, cx| {
13747 let snapshot = buffer.snapshot(cx);
13748 let mut excerpt_ids = selections
13749 .iter()
13750 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13751 .collect::<Vec<_>>();
13752 excerpt_ids.sort();
13753 excerpt_ids.dedup();
13754 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13755 })
13756 }
13757
13758 pub fn expand_excerpt(
13759 &mut self,
13760 excerpt: ExcerptId,
13761 direction: ExpandExcerptDirection,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) {
13765 let current_scroll_position = self.scroll_position(cx);
13766 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13767 let mut should_scroll_up = false;
13768
13769 if direction == ExpandExcerptDirection::Down {
13770 let multi_buffer = self.buffer.read(cx);
13771 let snapshot = multi_buffer.snapshot(cx);
13772 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13773 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13774 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13775 let buffer_snapshot = buffer.read(cx).snapshot();
13776 let excerpt_end_row =
13777 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13778 let last_row = buffer_snapshot.max_point().row;
13779 let lines_below = last_row.saturating_sub(excerpt_end_row);
13780 should_scroll_up = lines_below >= lines_to_expand;
13781 }
13782 }
13783 }
13784 }
13785
13786 self.buffer.update(cx, |buffer, cx| {
13787 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13788 });
13789
13790 if should_scroll_up {
13791 let new_scroll_position =
13792 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13793 self.set_scroll_position(new_scroll_position, window, cx);
13794 }
13795 }
13796
13797 pub fn go_to_singleton_buffer_point(
13798 &mut self,
13799 point: Point,
13800 window: &mut Window,
13801 cx: &mut Context<Self>,
13802 ) {
13803 self.go_to_singleton_buffer_range(point..point, window, cx);
13804 }
13805
13806 pub fn go_to_singleton_buffer_range(
13807 &mut self,
13808 range: Range<Point>,
13809 window: &mut Window,
13810 cx: &mut Context<Self>,
13811 ) {
13812 let multibuffer = self.buffer().read(cx);
13813 let Some(buffer) = multibuffer.as_singleton() else {
13814 return;
13815 };
13816 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13817 return;
13818 };
13819 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13820 return;
13821 };
13822 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13823 s.select_anchor_ranges([start..end])
13824 });
13825 }
13826
13827 pub fn go_to_diagnostic(
13828 &mut self,
13829 _: &GoToDiagnostic,
13830 window: &mut Window,
13831 cx: &mut Context<Self>,
13832 ) {
13833 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13834 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13835 }
13836
13837 pub fn go_to_prev_diagnostic(
13838 &mut self,
13839 _: &GoToPreviousDiagnostic,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13844 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13845 }
13846
13847 pub fn go_to_diagnostic_impl(
13848 &mut self,
13849 direction: Direction,
13850 window: &mut Window,
13851 cx: &mut Context<Self>,
13852 ) {
13853 let buffer = self.buffer.read(cx).snapshot(cx);
13854 let selection = self.selections.newest::<usize>(cx);
13855
13856 let mut active_group_id = None;
13857 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13858 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13859 active_group_id = Some(active_group.group_id);
13860 }
13861 }
13862
13863 fn filtered(
13864 snapshot: EditorSnapshot,
13865 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13866 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13867 diagnostics
13868 .filter(|entry| entry.range.start != entry.range.end)
13869 .filter(|entry| !entry.diagnostic.is_unnecessary)
13870 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13871 }
13872
13873 let snapshot = self.snapshot(window, cx);
13874 let before = filtered(
13875 snapshot.clone(),
13876 buffer
13877 .diagnostics_in_range(0..selection.start)
13878 .filter(|entry| entry.range.start <= selection.start),
13879 );
13880 let after = filtered(
13881 snapshot,
13882 buffer
13883 .diagnostics_in_range(selection.start..buffer.len())
13884 .filter(|entry| entry.range.start >= selection.start),
13885 );
13886
13887 let mut found: Option<DiagnosticEntry<usize>> = None;
13888 if direction == Direction::Prev {
13889 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13890 {
13891 for diagnostic in prev_diagnostics.into_iter().rev() {
13892 if diagnostic.range.start != selection.start
13893 || active_group_id
13894 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13895 {
13896 found = Some(diagnostic);
13897 break 'outer;
13898 }
13899 }
13900 }
13901 } else {
13902 for diagnostic in after.chain(before) {
13903 if diagnostic.range.start != selection.start
13904 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13905 {
13906 found = Some(diagnostic);
13907 break;
13908 }
13909 }
13910 }
13911 let Some(next_diagnostic) = found else {
13912 return;
13913 };
13914
13915 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13916 return;
13917 };
13918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13919 s.select_ranges(vec![
13920 next_diagnostic.range.start..next_diagnostic.range.start,
13921 ])
13922 });
13923 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13924 self.refresh_inline_completion(false, true, window, cx);
13925 }
13926
13927 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13928 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13929 let snapshot = self.snapshot(window, cx);
13930 let selection = self.selections.newest::<Point>(cx);
13931 self.go_to_hunk_before_or_after_position(
13932 &snapshot,
13933 selection.head(),
13934 Direction::Next,
13935 window,
13936 cx,
13937 );
13938 }
13939
13940 pub fn go_to_hunk_before_or_after_position(
13941 &mut self,
13942 snapshot: &EditorSnapshot,
13943 position: Point,
13944 direction: Direction,
13945 window: &mut Window,
13946 cx: &mut Context<Editor>,
13947 ) {
13948 let row = if direction == Direction::Next {
13949 self.hunk_after_position(snapshot, position)
13950 .map(|hunk| hunk.row_range.start)
13951 } else {
13952 self.hunk_before_position(snapshot, position)
13953 };
13954
13955 if let Some(row) = row {
13956 let destination = Point::new(row.0, 0);
13957 let autoscroll = Autoscroll::center();
13958
13959 self.unfold_ranges(&[destination..destination], false, false, cx);
13960 self.change_selections(Some(autoscroll), window, cx, |s| {
13961 s.select_ranges([destination..destination]);
13962 });
13963 }
13964 }
13965
13966 fn hunk_after_position(
13967 &mut self,
13968 snapshot: &EditorSnapshot,
13969 position: Point,
13970 ) -> Option<MultiBufferDiffHunk> {
13971 snapshot
13972 .buffer_snapshot
13973 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13974 .find(|hunk| hunk.row_range.start.0 > position.row)
13975 .or_else(|| {
13976 snapshot
13977 .buffer_snapshot
13978 .diff_hunks_in_range(Point::zero()..position)
13979 .find(|hunk| hunk.row_range.end.0 < position.row)
13980 })
13981 }
13982
13983 fn go_to_prev_hunk(
13984 &mut self,
13985 _: &GoToPreviousHunk,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13990 let snapshot = self.snapshot(window, cx);
13991 let selection = self.selections.newest::<Point>(cx);
13992 self.go_to_hunk_before_or_after_position(
13993 &snapshot,
13994 selection.head(),
13995 Direction::Prev,
13996 window,
13997 cx,
13998 );
13999 }
14000
14001 fn hunk_before_position(
14002 &mut self,
14003 snapshot: &EditorSnapshot,
14004 position: Point,
14005 ) -> Option<MultiBufferRow> {
14006 snapshot
14007 .buffer_snapshot
14008 .diff_hunk_before(position)
14009 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14010 }
14011
14012 fn go_to_next_change(
14013 &mut self,
14014 _: &GoToNextChange,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 if let Some(selections) = self
14019 .change_list
14020 .next_change(1, Direction::Next)
14021 .map(|s| s.to_vec())
14022 {
14023 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14024 let map = s.display_map();
14025 s.select_display_ranges(selections.iter().map(|a| {
14026 let point = a.to_display_point(&map);
14027 point..point
14028 }))
14029 })
14030 }
14031 }
14032
14033 fn go_to_previous_change(
14034 &mut self,
14035 _: &GoToPreviousChange,
14036 window: &mut Window,
14037 cx: &mut Context<Self>,
14038 ) {
14039 if let Some(selections) = self
14040 .change_list
14041 .next_change(1, Direction::Prev)
14042 .map(|s| s.to_vec())
14043 {
14044 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14045 let map = s.display_map();
14046 s.select_display_ranges(selections.iter().map(|a| {
14047 let point = a.to_display_point(&map);
14048 point..point
14049 }))
14050 })
14051 }
14052 }
14053
14054 fn go_to_line<T: 'static>(
14055 &mut self,
14056 position: Anchor,
14057 highlight_color: Option<Hsla>,
14058 window: &mut Window,
14059 cx: &mut Context<Self>,
14060 ) {
14061 let snapshot = self.snapshot(window, cx).display_snapshot;
14062 let position = position.to_point(&snapshot.buffer_snapshot);
14063 let start = snapshot
14064 .buffer_snapshot
14065 .clip_point(Point::new(position.row, 0), Bias::Left);
14066 let end = start + Point::new(1, 0);
14067 let start = snapshot.buffer_snapshot.anchor_before(start);
14068 let end = snapshot.buffer_snapshot.anchor_before(end);
14069
14070 self.highlight_rows::<T>(
14071 start..end,
14072 highlight_color
14073 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14074 Default::default(),
14075 cx,
14076 );
14077
14078 if self.buffer.read(cx).is_singleton() {
14079 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14080 }
14081 }
14082
14083 pub fn go_to_definition(
14084 &mut self,
14085 _: &GoToDefinition,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) -> Task<Result<Navigated>> {
14089 let definition =
14090 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14091 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14092 cx.spawn_in(window, async move |editor, cx| {
14093 if definition.await? == Navigated::Yes {
14094 return Ok(Navigated::Yes);
14095 }
14096 match fallback_strategy {
14097 GoToDefinitionFallback::None => Ok(Navigated::No),
14098 GoToDefinitionFallback::FindAllReferences => {
14099 match editor.update_in(cx, |editor, window, cx| {
14100 editor.find_all_references(&FindAllReferences, window, cx)
14101 })? {
14102 Some(references) => references.await,
14103 None => Ok(Navigated::No),
14104 }
14105 }
14106 }
14107 })
14108 }
14109
14110 pub fn go_to_declaration(
14111 &mut self,
14112 _: &GoToDeclaration,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) -> Task<Result<Navigated>> {
14116 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14117 }
14118
14119 pub fn go_to_declaration_split(
14120 &mut self,
14121 _: &GoToDeclaration,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) -> Task<Result<Navigated>> {
14125 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14126 }
14127
14128 pub fn go_to_implementation(
14129 &mut self,
14130 _: &GoToImplementation,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) -> Task<Result<Navigated>> {
14134 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14135 }
14136
14137 pub fn go_to_implementation_split(
14138 &mut self,
14139 _: &GoToImplementationSplit,
14140 window: &mut Window,
14141 cx: &mut Context<Self>,
14142 ) -> Task<Result<Navigated>> {
14143 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14144 }
14145
14146 pub fn go_to_type_definition(
14147 &mut self,
14148 _: &GoToTypeDefinition,
14149 window: &mut Window,
14150 cx: &mut Context<Self>,
14151 ) -> Task<Result<Navigated>> {
14152 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14153 }
14154
14155 pub fn go_to_definition_split(
14156 &mut self,
14157 _: &GoToDefinitionSplit,
14158 window: &mut Window,
14159 cx: &mut Context<Self>,
14160 ) -> Task<Result<Navigated>> {
14161 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14162 }
14163
14164 pub fn go_to_type_definition_split(
14165 &mut self,
14166 _: &GoToTypeDefinitionSplit,
14167 window: &mut Window,
14168 cx: &mut Context<Self>,
14169 ) -> Task<Result<Navigated>> {
14170 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14171 }
14172
14173 fn go_to_definition_of_kind(
14174 &mut self,
14175 kind: GotoDefinitionKind,
14176 split: bool,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) -> Task<Result<Navigated>> {
14180 let Some(provider) = self.semantics_provider.clone() else {
14181 return Task::ready(Ok(Navigated::No));
14182 };
14183 let head = self.selections.newest::<usize>(cx).head();
14184 let buffer = self.buffer.read(cx);
14185 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14186 text_anchor
14187 } else {
14188 return Task::ready(Ok(Navigated::No));
14189 };
14190
14191 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14192 return Task::ready(Ok(Navigated::No));
14193 };
14194
14195 cx.spawn_in(window, async move |editor, cx| {
14196 let definitions = definitions.await?;
14197 let navigated = editor
14198 .update_in(cx, |editor, window, cx| {
14199 editor.navigate_to_hover_links(
14200 Some(kind),
14201 definitions
14202 .into_iter()
14203 .filter(|location| {
14204 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14205 })
14206 .map(HoverLink::Text)
14207 .collect::<Vec<_>>(),
14208 split,
14209 window,
14210 cx,
14211 )
14212 })?
14213 .await?;
14214 anyhow::Ok(navigated)
14215 })
14216 }
14217
14218 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14219 let selection = self.selections.newest_anchor();
14220 let head = selection.head();
14221 let tail = selection.tail();
14222
14223 let Some((buffer, start_position)) =
14224 self.buffer.read(cx).text_anchor_for_position(head, cx)
14225 else {
14226 return;
14227 };
14228
14229 let end_position = if head != tail {
14230 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14231 return;
14232 };
14233 Some(pos)
14234 } else {
14235 None
14236 };
14237
14238 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14239 let url = if let Some(end_pos) = end_position {
14240 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14241 } else {
14242 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14243 };
14244
14245 if let Some(url) = url {
14246 editor.update(cx, |_, cx| {
14247 cx.open_url(&url);
14248 })
14249 } else {
14250 Ok(())
14251 }
14252 });
14253
14254 url_finder.detach();
14255 }
14256
14257 pub fn open_selected_filename(
14258 &mut self,
14259 _: &OpenSelectedFilename,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 let Some(workspace) = self.workspace() else {
14264 return;
14265 };
14266
14267 let position = self.selections.newest_anchor().head();
14268
14269 let Some((buffer, buffer_position)) =
14270 self.buffer.read(cx).text_anchor_for_position(position, cx)
14271 else {
14272 return;
14273 };
14274
14275 let project = self.project.clone();
14276
14277 cx.spawn_in(window, async move |_, cx| {
14278 let result = find_file(&buffer, project, buffer_position, cx).await;
14279
14280 if let Some((_, path)) = result {
14281 workspace
14282 .update_in(cx, |workspace, window, cx| {
14283 workspace.open_resolved_path(path, window, cx)
14284 })?
14285 .await?;
14286 }
14287 anyhow::Ok(())
14288 })
14289 .detach();
14290 }
14291
14292 pub(crate) fn navigate_to_hover_links(
14293 &mut self,
14294 kind: Option<GotoDefinitionKind>,
14295 mut definitions: Vec<HoverLink>,
14296 split: bool,
14297 window: &mut Window,
14298 cx: &mut Context<Editor>,
14299 ) -> Task<Result<Navigated>> {
14300 // If there is one definition, just open it directly
14301 if definitions.len() == 1 {
14302 let definition = definitions.pop().unwrap();
14303
14304 enum TargetTaskResult {
14305 Location(Option<Location>),
14306 AlreadyNavigated,
14307 }
14308
14309 let target_task = match definition {
14310 HoverLink::Text(link) => {
14311 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14312 }
14313 HoverLink::InlayHint(lsp_location, server_id) => {
14314 let computation =
14315 self.compute_target_location(lsp_location, server_id, window, cx);
14316 cx.background_spawn(async move {
14317 let location = computation.await?;
14318 Ok(TargetTaskResult::Location(location))
14319 })
14320 }
14321 HoverLink::Url(url) => {
14322 cx.open_url(&url);
14323 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14324 }
14325 HoverLink::File(path) => {
14326 if let Some(workspace) = self.workspace() {
14327 cx.spawn_in(window, async move |_, cx| {
14328 workspace
14329 .update_in(cx, |workspace, window, cx| {
14330 workspace.open_resolved_path(path, window, cx)
14331 })?
14332 .await
14333 .map(|_| TargetTaskResult::AlreadyNavigated)
14334 })
14335 } else {
14336 Task::ready(Ok(TargetTaskResult::Location(None)))
14337 }
14338 }
14339 };
14340 cx.spawn_in(window, async move |editor, cx| {
14341 let target = match target_task.await.context("target resolution task")? {
14342 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14343 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14344 TargetTaskResult::Location(Some(target)) => target,
14345 };
14346
14347 editor.update_in(cx, |editor, window, cx| {
14348 let Some(workspace) = editor.workspace() else {
14349 return Navigated::No;
14350 };
14351 let pane = workspace.read(cx).active_pane().clone();
14352
14353 let range = target.range.to_point(target.buffer.read(cx));
14354 let range = editor.range_for_match(&range);
14355 let range = collapse_multiline_range(range);
14356
14357 if !split
14358 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14359 {
14360 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14361 } else {
14362 window.defer(cx, move |window, cx| {
14363 let target_editor: Entity<Self> =
14364 workspace.update(cx, |workspace, cx| {
14365 let pane = if split {
14366 workspace.adjacent_pane(window, cx)
14367 } else {
14368 workspace.active_pane().clone()
14369 };
14370
14371 workspace.open_project_item(
14372 pane,
14373 target.buffer.clone(),
14374 true,
14375 true,
14376 window,
14377 cx,
14378 )
14379 });
14380 target_editor.update(cx, |target_editor, cx| {
14381 // When selecting a definition in a different buffer, disable the nav history
14382 // to avoid creating a history entry at the previous cursor location.
14383 pane.update(cx, |pane, _| pane.disable_history());
14384 target_editor.go_to_singleton_buffer_range(range, window, cx);
14385 pane.update(cx, |pane, _| pane.enable_history());
14386 });
14387 });
14388 }
14389 Navigated::Yes
14390 })
14391 })
14392 } else if !definitions.is_empty() {
14393 cx.spawn_in(window, async move |editor, cx| {
14394 let (title, location_tasks, workspace) = editor
14395 .update_in(cx, |editor, window, cx| {
14396 let tab_kind = match kind {
14397 Some(GotoDefinitionKind::Implementation) => "Implementations",
14398 _ => "Definitions",
14399 };
14400 let title = definitions
14401 .iter()
14402 .find_map(|definition| match definition {
14403 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14404 let buffer = origin.buffer.read(cx);
14405 format!(
14406 "{} for {}",
14407 tab_kind,
14408 buffer
14409 .text_for_range(origin.range.clone())
14410 .collect::<String>()
14411 )
14412 }),
14413 HoverLink::InlayHint(_, _) => None,
14414 HoverLink::Url(_) => None,
14415 HoverLink::File(_) => None,
14416 })
14417 .unwrap_or(tab_kind.to_string());
14418 let location_tasks = definitions
14419 .into_iter()
14420 .map(|definition| match definition {
14421 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14422 HoverLink::InlayHint(lsp_location, server_id) => editor
14423 .compute_target_location(lsp_location, server_id, window, cx),
14424 HoverLink::Url(_) => Task::ready(Ok(None)),
14425 HoverLink::File(_) => Task::ready(Ok(None)),
14426 })
14427 .collect::<Vec<_>>();
14428 (title, location_tasks, editor.workspace().clone())
14429 })
14430 .context("location tasks preparation")?;
14431
14432 let locations = future::join_all(location_tasks)
14433 .await
14434 .into_iter()
14435 .filter_map(|location| location.transpose())
14436 .collect::<Result<_>>()
14437 .context("location tasks")?;
14438
14439 let Some(workspace) = workspace else {
14440 return Ok(Navigated::No);
14441 };
14442 let opened = workspace
14443 .update_in(cx, |workspace, window, cx| {
14444 Self::open_locations_in_multibuffer(
14445 workspace,
14446 locations,
14447 title,
14448 split,
14449 MultibufferSelectionMode::First,
14450 window,
14451 cx,
14452 )
14453 })
14454 .ok();
14455
14456 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14457 })
14458 } else {
14459 Task::ready(Ok(Navigated::No))
14460 }
14461 }
14462
14463 fn compute_target_location(
14464 &self,
14465 lsp_location: lsp::Location,
14466 server_id: LanguageServerId,
14467 window: &mut Window,
14468 cx: &mut Context<Self>,
14469 ) -> Task<anyhow::Result<Option<Location>>> {
14470 let Some(project) = self.project.clone() else {
14471 return Task::ready(Ok(None));
14472 };
14473
14474 cx.spawn_in(window, async move |editor, cx| {
14475 let location_task = editor.update(cx, |_, cx| {
14476 project.update(cx, |project, cx| {
14477 let language_server_name = project
14478 .language_server_statuses(cx)
14479 .find(|(id, _)| server_id == *id)
14480 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14481 language_server_name.map(|language_server_name| {
14482 project.open_local_buffer_via_lsp(
14483 lsp_location.uri.clone(),
14484 server_id,
14485 language_server_name,
14486 cx,
14487 )
14488 })
14489 })
14490 })?;
14491 let location = match location_task {
14492 Some(task) => Some({
14493 let target_buffer_handle = task.await.context("open local buffer")?;
14494 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14495 let target_start = target_buffer
14496 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14497 let target_end = target_buffer
14498 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14499 target_buffer.anchor_after(target_start)
14500 ..target_buffer.anchor_before(target_end)
14501 })?;
14502 Location {
14503 buffer: target_buffer_handle,
14504 range,
14505 }
14506 }),
14507 None => None,
14508 };
14509 Ok(location)
14510 })
14511 }
14512
14513 pub fn find_all_references(
14514 &mut self,
14515 _: &FindAllReferences,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) -> Option<Task<Result<Navigated>>> {
14519 let selection = self.selections.newest::<usize>(cx);
14520 let multi_buffer = self.buffer.read(cx);
14521 let head = selection.head();
14522
14523 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14524 let head_anchor = multi_buffer_snapshot.anchor_at(
14525 head,
14526 if head < selection.tail() {
14527 Bias::Right
14528 } else {
14529 Bias::Left
14530 },
14531 );
14532
14533 match self
14534 .find_all_references_task_sources
14535 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14536 {
14537 Ok(_) => {
14538 log::info!(
14539 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14540 );
14541 return None;
14542 }
14543 Err(i) => {
14544 self.find_all_references_task_sources.insert(i, head_anchor);
14545 }
14546 }
14547
14548 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14549 let workspace = self.workspace()?;
14550 let project = workspace.read(cx).project().clone();
14551 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14552 Some(cx.spawn_in(window, async move |editor, cx| {
14553 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14554 if let Ok(i) = editor
14555 .find_all_references_task_sources
14556 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14557 {
14558 editor.find_all_references_task_sources.remove(i);
14559 }
14560 });
14561
14562 let locations = references.await?;
14563 if locations.is_empty() {
14564 return anyhow::Ok(Navigated::No);
14565 }
14566
14567 workspace.update_in(cx, |workspace, window, cx| {
14568 let title = locations
14569 .first()
14570 .as_ref()
14571 .map(|location| {
14572 let buffer = location.buffer.read(cx);
14573 format!(
14574 "References to `{}`",
14575 buffer
14576 .text_for_range(location.range.clone())
14577 .collect::<String>()
14578 )
14579 })
14580 .unwrap();
14581 Self::open_locations_in_multibuffer(
14582 workspace,
14583 locations,
14584 title,
14585 false,
14586 MultibufferSelectionMode::First,
14587 window,
14588 cx,
14589 );
14590 Navigated::Yes
14591 })
14592 }))
14593 }
14594
14595 /// Opens a multibuffer with the given project locations in it
14596 pub fn open_locations_in_multibuffer(
14597 workspace: &mut Workspace,
14598 mut locations: Vec<Location>,
14599 title: String,
14600 split: bool,
14601 multibuffer_selection_mode: MultibufferSelectionMode,
14602 window: &mut Window,
14603 cx: &mut Context<Workspace>,
14604 ) {
14605 // If there are multiple definitions, open them in a multibuffer
14606 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14607 let mut locations = locations.into_iter().peekable();
14608 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14609 let capability = workspace.project().read(cx).capability();
14610
14611 let excerpt_buffer = cx.new(|cx| {
14612 let mut multibuffer = MultiBuffer::new(capability);
14613 while let Some(location) = locations.next() {
14614 let buffer = location.buffer.read(cx);
14615 let mut ranges_for_buffer = Vec::new();
14616 let range = location.range.to_point(buffer);
14617 ranges_for_buffer.push(range.clone());
14618
14619 while let Some(next_location) = locations.peek() {
14620 if next_location.buffer == location.buffer {
14621 ranges_for_buffer.push(next_location.range.to_point(buffer));
14622 locations.next();
14623 } else {
14624 break;
14625 }
14626 }
14627
14628 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14629 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14630 PathKey::for_buffer(&location.buffer, cx),
14631 location.buffer.clone(),
14632 ranges_for_buffer,
14633 DEFAULT_MULTIBUFFER_CONTEXT,
14634 cx,
14635 );
14636 ranges.extend(new_ranges)
14637 }
14638
14639 multibuffer.with_title(title)
14640 });
14641
14642 let editor = cx.new(|cx| {
14643 Editor::for_multibuffer(
14644 excerpt_buffer,
14645 Some(workspace.project().clone()),
14646 window,
14647 cx,
14648 )
14649 });
14650 editor.update(cx, |editor, cx| {
14651 match multibuffer_selection_mode {
14652 MultibufferSelectionMode::First => {
14653 if let Some(first_range) = ranges.first() {
14654 editor.change_selections(None, window, cx, |selections| {
14655 selections.clear_disjoint();
14656 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14657 });
14658 }
14659 editor.highlight_background::<Self>(
14660 &ranges,
14661 |theme| theme.editor_highlighted_line_background,
14662 cx,
14663 );
14664 }
14665 MultibufferSelectionMode::All => {
14666 editor.change_selections(None, window, cx, |selections| {
14667 selections.clear_disjoint();
14668 selections.select_anchor_ranges(ranges);
14669 });
14670 }
14671 }
14672 editor.register_buffers_with_language_servers(cx);
14673 });
14674
14675 let item = Box::new(editor);
14676 let item_id = item.item_id();
14677
14678 if split {
14679 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14680 } else {
14681 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14682 let (preview_item_id, preview_item_idx) =
14683 workspace.active_pane().update(cx, |pane, _| {
14684 (pane.preview_item_id(), pane.preview_item_idx())
14685 });
14686
14687 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14688
14689 if let Some(preview_item_id) = preview_item_id {
14690 workspace.active_pane().update(cx, |pane, cx| {
14691 pane.remove_item(preview_item_id, false, false, window, cx);
14692 });
14693 }
14694 } else {
14695 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14696 }
14697 }
14698 workspace.active_pane().update(cx, |pane, cx| {
14699 pane.set_preview_item_id(Some(item_id), cx);
14700 });
14701 }
14702
14703 pub fn rename(
14704 &mut self,
14705 _: &Rename,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) -> Option<Task<Result<()>>> {
14709 use language::ToOffset as _;
14710
14711 let provider = self.semantics_provider.clone()?;
14712 let selection = self.selections.newest_anchor().clone();
14713 let (cursor_buffer, cursor_buffer_position) = self
14714 .buffer
14715 .read(cx)
14716 .text_anchor_for_position(selection.head(), cx)?;
14717 let (tail_buffer, cursor_buffer_position_end) = self
14718 .buffer
14719 .read(cx)
14720 .text_anchor_for_position(selection.tail(), cx)?;
14721 if tail_buffer != cursor_buffer {
14722 return None;
14723 }
14724
14725 let snapshot = cursor_buffer.read(cx).snapshot();
14726 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14727 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14728 let prepare_rename = provider
14729 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14730 .unwrap_or_else(|| Task::ready(Ok(None)));
14731 drop(snapshot);
14732
14733 Some(cx.spawn_in(window, async move |this, cx| {
14734 let rename_range = if let Some(range) = prepare_rename.await? {
14735 Some(range)
14736 } else {
14737 this.update(cx, |this, cx| {
14738 let buffer = this.buffer.read(cx).snapshot(cx);
14739 let mut buffer_highlights = this
14740 .document_highlights_for_position(selection.head(), &buffer)
14741 .filter(|highlight| {
14742 highlight.start.excerpt_id == selection.head().excerpt_id
14743 && highlight.end.excerpt_id == selection.head().excerpt_id
14744 });
14745 buffer_highlights
14746 .next()
14747 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14748 })?
14749 };
14750 if let Some(rename_range) = rename_range {
14751 this.update_in(cx, |this, window, cx| {
14752 let snapshot = cursor_buffer.read(cx).snapshot();
14753 let rename_buffer_range = rename_range.to_offset(&snapshot);
14754 let cursor_offset_in_rename_range =
14755 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14756 let cursor_offset_in_rename_range_end =
14757 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14758
14759 this.take_rename(false, window, cx);
14760 let buffer = this.buffer.read(cx).read(cx);
14761 let cursor_offset = selection.head().to_offset(&buffer);
14762 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14763 let rename_end = rename_start + rename_buffer_range.len();
14764 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14765 let mut old_highlight_id = None;
14766 let old_name: Arc<str> = buffer
14767 .chunks(rename_start..rename_end, true)
14768 .map(|chunk| {
14769 if old_highlight_id.is_none() {
14770 old_highlight_id = chunk.syntax_highlight_id;
14771 }
14772 chunk.text
14773 })
14774 .collect::<String>()
14775 .into();
14776
14777 drop(buffer);
14778
14779 // Position the selection in the rename editor so that it matches the current selection.
14780 this.show_local_selections = false;
14781 let rename_editor = cx.new(|cx| {
14782 let mut editor = Editor::single_line(window, cx);
14783 editor.buffer.update(cx, |buffer, cx| {
14784 buffer.edit([(0..0, old_name.clone())], None, cx)
14785 });
14786 let rename_selection_range = match cursor_offset_in_rename_range
14787 .cmp(&cursor_offset_in_rename_range_end)
14788 {
14789 Ordering::Equal => {
14790 editor.select_all(&SelectAll, window, cx);
14791 return editor;
14792 }
14793 Ordering::Less => {
14794 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14795 }
14796 Ordering::Greater => {
14797 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14798 }
14799 };
14800 if rename_selection_range.end > old_name.len() {
14801 editor.select_all(&SelectAll, window, cx);
14802 } else {
14803 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14804 s.select_ranges([rename_selection_range]);
14805 });
14806 }
14807 editor
14808 });
14809 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14810 if e == &EditorEvent::Focused {
14811 cx.emit(EditorEvent::FocusedIn)
14812 }
14813 })
14814 .detach();
14815
14816 let write_highlights =
14817 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14818 let read_highlights =
14819 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14820 let ranges = write_highlights
14821 .iter()
14822 .flat_map(|(_, ranges)| ranges.iter())
14823 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14824 .cloned()
14825 .collect();
14826
14827 this.highlight_text::<Rename>(
14828 ranges,
14829 HighlightStyle {
14830 fade_out: Some(0.6),
14831 ..Default::default()
14832 },
14833 cx,
14834 );
14835 let rename_focus_handle = rename_editor.focus_handle(cx);
14836 window.focus(&rename_focus_handle);
14837 let block_id = this.insert_blocks(
14838 [BlockProperties {
14839 style: BlockStyle::Flex,
14840 placement: BlockPlacement::Below(range.start),
14841 height: Some(1),
14842 render: Arc::new({
14843 let rename_editor = rename_editor.clone();
14844 move |cx: &mut BlockContext| {
14845 let mut text_style = cx.editor_style.text.clone();
14846 if let Some(highlight_style) = old_highlight_id
14847 .and_then(|h| h.style(&cx.editor_style.syntax))
14848 {
14849 text_style = text_style.highlight(highlight_style);
14850 }
14851 div()
14852 .block_mouse_down()
14853 .pl(cx.anchor_x)
14854 .child(EditorElement::new(
14855 &rename_editor,
14856 EditorStyle {
14857 background: cx.theme().system().transparent,
14858 local_player: cx.editor_style.local_player,
14859 text: text_style,
14860 scrollbar_width: cx.editor_style.scrollbar_width,
14861 syntax: cx.editor_style.syntax.clone(),
14862 status: cx.editor_style.status.clone(),
14863 inlay_hints_style: HighlightStyle {
14864 font_weight: Some(FontWeight::BOLD),
14865 ..make_inlay_hints_style(cx.app)
14866 },
14867 inline_completion_styles: make_suggestion_styles(
14868 cx.app,
14869 ),
14870 ..EditorStyle::default()
14871 },
14872 ))
14873 .into_any_element()
14874 }
14875 }),
14876 priority: 0,
14877 render_in_minimap: true,
14878 }],
14879 Some(Autoscroll::fit()),
14880 cx,
14881 )[0];
14882 this.pending_rename = Some(RenameState {
14883 range,
14884 old_name,
14885 editor: rename_editor,
14886 block_id,
14887 });
14888 })?;
14889 }
14890
14891 Ok(())
14892 }))
14893 }
14894
14895 pub fn confirm_rename(
14896 &mut self,
14897 _: &ConfirmRename,
14898 window: &mut Window,
14899 cx: &mut Context<Self>,
14900 ) -> Option<Task<Result<()>>> {
14901 let rename = self.take_rename(false, window, cx)?;
14902 let workspace = self.workspace()?.downgrade();
14903 let (buffer, start) = self
14904 .buffer
14905 .read(cx)
14906 .text_anchor_for_position(rename.range.start, cx)?;
14907 let (end_buffer, _) = self
14908 .buffer
14909 .read(cx)
14910 .text_anchor_for_position(rename.range.end, cx)?;
14911 if buffer != end_buffer {
14912 return None;
14913 }
14914
14915 let old_name = rename.old_name;
14916 let new_name = rename.editor.read(cx).text(cx);
14917
14918 let rename = self.semantics_provider.as_ref()?.perform_rename(
14919 &buffer,
14920 start,
14921 new_name.clone(),
14922 cx,
14923 )?;
14924
14925 Some(cx.spawn_in(window, async move |editor, cx| {
14926 let project_transaction = rename.await?;
14927 Self::open_project_transaction(
14928 &editor,
14929 workspace,
14930 project_transaction,
14931 format!("Rename: {} → {}", old_name, new_name),
14932 cx,
14933 )
14934 .await?;
14935
14936 editor.update(cx, |editor, cx| {
14937 editor.refresh_document_highlights(cx);
14938 })?;
14939 Ok(())
14940 }))
14941 }
14942
14943 fn take_rename(
14944 &mut self,
14945 moving_cursor: bool,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) -> Option<RenameState> {
14949 let rename = self.pending_rename.take()?;
14950 if rename.editor.focus_handle(cx).is_focused(window) {
14951 window.focus(&self.focus_handle);
14952 }
14953
14954 self.remove_blocks(
14955 [rename.block_id].into_iter().collect(),
14956 Some(Autoscroll::fit()),
14957 cx,
14958 );
14959 self.clear_highlights::<Rename>(cx);
14960 self.show_local_selections = true;
14961
14962 if moving_cursor {
14963 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14964 editor.selections.newest::<usize>(cx).head()
14965 });
14966
14967 // Update the selection to match the position of the selection inside
14968 // the rename editor.
14969 let snapshot = self.buffer.read(cx).read(cx);
14970 let rename_range = rename.range.to_offset(&snapshot);
14971 let cursor_in_editor = snapshot
14972 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14973 .min(rename_range.end);
14974 drop(snapshot);
14975
14976 self.change_selections(None, window, cx, |s| {
14977 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14978 });
14979 } else {
14980 self.refresh_document_highlights(cx);
14981 }
14982
14983 Some(rename)
14984 }
14985
14986 pub fn pending_rename(&self) -> Option<&RenameState> {
14987 self.pending_rename.as_ref()
14988 }
14989
14990 fn format(
14991 &mut self,
14992 _: &Format,
14993 window: &mut Window,
14994 cx: &mut Context<Self>,
14995 ) -> Option<Task<Result<()>>> {
14996 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14997
14998 let project = match &self.project {
14999 Some(project) => project.clone(),
15000 None => return None,
15001 };
15002
15003 Some(self.perform_format(
15004 project,
15005 FormatTrigger::Manual,
15006 FormatTarget::Buffers,
15007 window,
15008 cx,
15009 ))
15010 }
15011
15012 fn format_selections(
15013 &mut self,
15014 _: &FormatSelections,
15015 window: &mut Window,
15016 cx: &mut Context<Self>,
15017 ) -> Option<Task<Result<()>>> {
15018 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15019
15020 let project = match &self.project {
15021 Some(project) => project.clone(),
15022 None => return None,
15023 };
15024
15025 let ranges = self
15026 .selections
15027 .all_adjusted(cx)
15028 .into_iter()
15029 .map(|selection| selection.range())
15030 .collect_vec();
15031
15032 Some(self.perform_format(
15033 project,
15034 FormatTrigger::Manual,
15035 FormatTarget::Ranges(ranges),
15036 window,
15037 cx,
15038 ))
15039 }
15040
15041 fn perform_format(
15042 &mut self,
15043 project: Entity<Project>,
15044 trigger: FormatTrigger,
15045 target: FormatTarget,
15046 window: &mut Window,
15047 cx: &mut Context<Self>,
15048 ) -> Task<Result<()>> {
15049 let buffer = self.buffer.clone();
15050 let (buffers, target) = match target {
15051 FormatTarget::Buffers => {
15052 let mut buffers = buffer.read(cx).all_buffers();
15053 if trigger == FormatTrigger::Save {
15054 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15055 }
15056 (buffers, LspFormatTarget::Buffers)
15057 }
15058 FormatTarget::Ranges(selection_ranges) => {
15059 let multi_buffer = buffer.read(cx);
15060 let snapshot = multi_buffer.read(cx);
15061 let mut buffers = HashSet::default();
15062 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15063 BTreeMap::new();
15064 for selection_range in selection_ranges {
15065 for (buffer, buffer_range, _) in
15066 snapshot.range_to_buffer_ranges(selection_range)
15067 {
15068 let buffer_id = buffer.remote_id();
15069 let start = buffer.anchor_before(buffer_range.start);
15070 let end = buffer.anchor_after(buffer_range.end);
15071 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15072 buffer_id_to_ranges
15073 .entry(buffer_id)
15074 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15075 .or_insert_with(|| vec![start..end]);
15076 }
15077 }
15078 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15079 }
15080 };
15081
15082 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15083 let selections_prev = transaction_id_prev
15084 .and_then(|transaction_id_prev| {
15085 // default to selections as they were after the last edit, if we have them,
15086 // instead of how they are now.
15087 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15088 // will take you back to where you made the last edit, instead of staying where you scrolled
15089 self.selection_history
15090 .transaction(transaction_id_prev)
15091 .map(|t| t.0.clone())
15092 })
15093 .unwrap_or_else(|| {
15094 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15095 self.selections.disjoint_anchors()
15096 });
15097
15098 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15099 let format = project.update(cx, |project, cx| {
15100 project.format(buffers, target, true, trigger, cx)
15101 });
15102
15103 cx.spawn_in(window, async move |editor, cx| {
15104 let transaction = futures::select_biased! {
15105 transaction = format.log_err().fuse() => transaction,
15106 () = timeout => {
15107 log::warn!("timed out waiting for formatting");
15108 None
15109 }
15110 };
15111
15112 buffer
15113 .update(cx, |buffer, cx| {
15114 if let Some(transaction) = transaction {
15115 if !buffer.is_singleton() {
15116 buffer.push_transaction(&transaction.0, cx);
15117 }
15118 }
15119 cx.notify();
15120 })
15121 .ok();
15122
15123 if let Some(transaction_id_now) =
15124 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15125 {
15126 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15127 if has_new_transaction {
15128 _ = editor.update(cx, |editor, _| {
15129 editor
15130 .selection_history
15131 .insert_transaction(transaction_id_now, selections_prev);
15132 });
15133 }
15134 }
15135
15136 Ok(())
15137 })
15138 }
15139
15140 fn organize_imports(
15141 &mut self,
15142 _: &OrganizeImports,
15143 window: &mut Window,
15144 cx: &mut Context<Self>,
15145 ) -> Option<Task<Result<()>>> {
15146 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15147 let project = match &self.project {
15148 Some(project) => project.clone(),
15149 None => return None,
15150 };
15151 Some(self.perform_code_action_kind(
15152 project,
15153 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15154 window,
15155 cx,
15156 ))
15157 }
15158
15159 fn perform_code_action_kind(
15160 &mut self,
15161 project: Entity<Project>,
15162 kind: CodeActionKind,
15163 window: &mut Window,
15164 cx: &mut Context<Self>,
15165 ) -> Task<Result<()>> {
15166 let buffer = self.buffer.clone();
15167 let buffers = buffer.read(cx).all_buffers();
15168 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15169 let apply_action = project.update(cx, |project, cx| {
15170 project.apply_code_action_kind(buffers, kind, true, cx)
15171 });
15172 cx.spawn_in(window, async move |_, cx| {
15173 let transaction = futures::select_biased! {
15174 () = timeout => {
15175 log::warn!("timed out waiting for executing code action");
15176 None
15177 }
15178 transaction = apply_action.log_err().fuse() => transaction,
15179 };
15180 buffer
15181 .update(cx, |buffer, cx| {
15182 // check if we need this
15183 if let Some(transaction) = transaction {
15184 if !buffer.is_singleton() {
15185 buffer.push_transaction(&transaction.0, cx);
15186 }
15187 }
15188 cx.notify();
15189 })
15190 .ok();
15191 Ok(())
15192 })
15193 }
15194
15195 fn restart_language_server(
15196 &mut self,
15197 _: &RestartLanguageServer,
15198 _: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 if let Some(project) = self.project.clone() {
15202 self.buffer.update(cx, |multi_buffer, cx| {
15203 project.update(cx, |project, cx| {
15204 project.restart_language_servers_for_buffers(
15205 multi_buffer.all_buffers().into_iter().collect(),
15206 cx,
15207 );
15208 });
15209 })
15210 }
15211 }
15212
15213 fn stop_language_server(
15214 &mut self,
15215 _: &StopLanguageServer,
15216 _: &mut Window,
15217 cx: &mut Context<Self>,
15218 ) {
15219 if let Some(project) = self.project.clone() {
15220 self.buffer.update(cx, |multi_buffer, cx| {
15221 project.update(cx, |project, cx| {
15222 project.stop_language_servers_for_buffers(
15223 multi_buffer.all_buffers().into_iter().collect(),
15224 cx,
15225 );
15226 cx.emit(project::Event::RefreshInlayHints);
15227 });
15228 });
15229 }
15230 }
15231
15232 fn cancel_language_server_work(
15233 workspace: &mut Workspace,
15234 _: &actions::CancelLanguageServerWork,
15235 _: &mut Window,
15236 cx: &mut Context<Workspace>,
15237 ) {
15238 let project = workspace.project();
15239 let buffers = workspace
15240 .active_item(cx)
15241 .and_then(|item| item.act_as::<Editor>(cx))
15242 .map_or(HashSet::default(), |editor| {
15243 editor.read(cx).buffer.read(cx).all_buffers()
15244 });
15245 project.update(cx, |project, cx| {
15246 project.cancel_language_server_work_for_buffers(buffers, cx);
15247 });
15248 }
15249
15250 fn show_character_palette(
15251 &mut self,
15252 _: &ShowCharacterPalette,
15253 window: &mut Window,
15254 _: &mut Context<Self>,
15255 ) {
15256 window.show_character_palette();
15257 }
15258
15259 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15260 if self.mode.is_minimap() {
15261 return;
15262 }
15263
15264 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15265 let buffer = self.buffer.read(cx).snapshot(cx);
15266 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15267 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15268 let is_valid = buffer
15269 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15270 .any(|entry| {
15271 entry.diagnostic.is_primary
15272 && !entry.range.is_empty()
15273 && entry.range.start == primary_range_start
15274 && entry.diagnostic.message == active_diagnostics.active_message
15275 });
15276
15277 if !is_valid {
15278 self.dismiss_diagnostics(cx);
15279 }
15280 }
15281 }
15282
15283 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15284 match &self.active_diagnostics {
15285 ActiveDiagnostic::Group(group) => Some(group),
15286 _ => None,
15287 }
15288 }
15289
15290 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15291 self.dismiss_diagnostics(cx);
15292 self.active_diagnostics = ActiveDiagnostic::All;
15293 }
15294
15295 fn activate_diagnostics(
15296 &mut self,
15297 buffer_id: BufferId,
15298 diagnostic: DiagnosticEntry<usize>,
15299 window: &mut Window,
15300 cx: &mut Context<Self>,
15301 ) {
15302 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15303 return;
15304 }
15305 self.dismiss_diagnostics(cx);
15306 let snapshot = self.snapshot(window, cx);
15307 let buffer = self.buffer.read(cx).snapshot(cx);
15308 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15309 return;
15310 };
15311
15312 let diagnostic_group = buffer
15313 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15314 .collect::<Vec<_>>();
15315
15316 let blocks =
15317 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15318
15319 let blocks = self.display_map.update(cx, |display_map, cx| {
15320 display_map.insert_blocks(blocks, cx).into_iter().collect()
15321 });
15322 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15323 active_range: buffer.anchor_before(diagnostic.range.start)
15324 ..buffer.anchor_after(diagnostic.range.end),
15325 active_message: diagnostic.diagnostic.message.clone(),
15326 group_id: diagnostic.diagnostic.group_id,
15327 blocks,
15328 });
15329 cx.notify();
15330 }
15331
15332 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15333 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15334 return;
15335 };
15336
15337 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15338 if let ActiveDiagnostic::Group(group) = prev {
15339 self.display_map.update(cx, |display_map, cx| {
15340 display_map.remove_blocks(group.blocks, cx);
15341 });
15342 cx.notify();
15343 }
15344 }
15345
15346 /// Disable inline diagnostics rendering for this editor.
15347 pub fn disable_inline_diagnostics(&mut self) {
15348 self.inline_diagnostics_enabled = false;
15349 self.inline_diagnostics_update = Task::ready(());
15350 self.inline_diagnostics.clear();
15351 }
15352
15353 pub fn diagnostics_enabled(&self) -> bool {
15354 self.mode.is_full()
15355 }
15356
15357 pub fn inline_diagnostics_enabled(&self) -> bool {
15358 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15359 }
15360
15361 pub fn show_inline_diagnostics(&self) -> bool {
15362 self.show_inline_diagnostics
15363 }
15364
15365 pub fn toggle_inline_diagnostics(
15366 &mut self,
15367 _: &ToggleInlineDiagnostics,
15368 window: &mut Window,
15369 cx: &mut Context<Editor>,
15370 ) {
15371 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15372 self.refresh_inline_diagnostics(false, window, cx);
15373 }
15374
15375 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15376 self.diagnostics_max_severity = severity;
15377 self.display_map.update(cx, |display_map, _| {
15378 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15379 });
15380 }
15381
15382 pub fn toggle_diagnostics(
15383 &mut self,
15384 _: &ToggleDiagnostics,
15385 window: &mut Window,
15386 cx: &mut Context<Editor>,
15387 ) {
15388 if !self.diagnostics_enabled() {
15389 return;
15390 }
15391
15392 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15393 EditorSettings::get_global(cx)
15394 .diagnostics_max_severity
15395 .filter(|severity| severity != &DiagnosticSeverity::Off)
15396 .unwrap_or(DiagnosticSeverity::Hint)
15397 } else {
15398 DiagnosticSeverity::Off
15399 };
15400 self.set_max_diagnostics_severity(new_severity, cx);
15401 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15402 self.active_diagnostics = ActiveDiagnostic::None;
15403 self.inline_diagnostics_update = Task::ready(());
15404 self.inline_diagnostics.clear();
15405 } else {
15406 self.refresh_inline_diagnostics(false, window, cx);
15407 }
15408
15409 cx.notify();
15410 }
15411
15412 pub fn toggle_minimap(
15413 &mut self,
15414 _: &ToggleMinimap,
15415 window: &mut Window,
15416 cx: &mut Context<Editor>,
15417 ) {
15418 if self.supports_minimap(cx) {
15419 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15420 }
15421 }
15422
15423 fn refresh_inline_diagnostics(
15424 &mut self,
15425 debounce: bool,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 let max_severity = ProjectSettings::get_global(cx)
15430 .diagnostics
15431 .inline
15432 .max_severity
15433 .unwrap_or(self.diagnostics_max_severity);
15434
15435 if self.mode.is_minimap()
15436 || !self.inline_diagnostics_enabled()
15437 || !self.show_inline_diagnostics
15438 || max_severity == DiagnosticSeverity::Off
15439 {
15440 self.inline_diagnostics_update = Task::ready(());
15441 self.inline_diagnostics.clear();
15442 return;
15443 }
15444
15445 let debounce_ms = ProjectSettings::get_global(cx)
15446 .diagnostics
15447 .inline
15448 .update_debounce_ms;
15449 let debounce = if debounce && debounce_ms > 0 {
15450 Some(Duration::from_millis(debounce_ms))
15451 } else {
15452 None
15453 };
15454 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15455 let editor = editor.upgrade().unwrap();
15456
15457 if let Some(debounce) = debounce {
15458 cx.background_executor().timer(debounce).await;
15459 }
15460 let Some(snapshot) = editor
15461 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15462 .ok()
15463 else {
15464 return;
15465 };
15466
15467 let new_inline_diagnostics = cx
15468 .background_spawn(async move {
15469 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15470 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15471 let message = diagnostic_entry
15472 .diagnostic
15473 .message
15474 .split_once('\n')
15475 .map(|(line, _)| line)
15476 .map(SharedString::new)
15477 .unwrap_or_else(|| {
15478 SharedString::from(diagnostic_entry.diagnostic.message)
15479 });
15480 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15481 let (Ok(i) | Err(i)) = inline_diagnostics
15482 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15483 inline_diagnostics.insert(
15484 i,
15485 (
15486 start_anchor,
15487 InlineDiagnostic {
15488 message,
15489 group_id: diagnostic_entry.diagnostic.group_id,
15490 start: diagnostic_entry.range.start.to_point(&snapshot),
15491 is_primary: diagnostic_entry.diagnostic.is_primary,
15492 severity: diagnostic_entry.diagnostic.severity,
15493 },
15494 ),
15495 );
15496 }
15497 inline_diagnostics
15498 })
15499 .await;
15500
15501 editor
15502 .update(cx, |editor, cx| {
15503 editor.inline_diagnostics = new_inline_diagnostics;
15504 cx.notify();
15505 })
15506 .ok();
15507 });
15508 }
15509
15510 pub fn set_selections_from_remote(
15511 &mut self,
15512 selections: Vec<Selection<Anchor>>,
15513 pending_selection: Option<Selection<Anchor>>,
15514 window: &mut Window,
15515 cx: &mut Context<Self>,
15516 ) {
15517 let old_cursor_position = self.selections.newest_anchor().head();
15518 self.selections.change_with(cx, |s| {
15519 s.select_anchors(selections);
15520 if let Some(pending_selection) = pending_selection {
15521 s.set_pending(pending_selection, SelectMode::Character);
15522 } else {
15523 s.clear_pending();
15524 }
15525 });
15526 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15527 }
15528
15529 fn push_to_selection_history(&mut self) {
15530 self.selection_history.push(SelectionHistoryEntry {
15531 selections: self.selections.disjoint_anchors(),
15532 select_next_state: self.select_next_state.clone(),
15533 select_prev_state: self.select_prev_state.clone(),
15534 add_selections_state: self.add_selections_state.clone(),
15535 });
15536 }
15537
15538 pub fn transact(
15539 &mut self,
15540 window: &mut Window,
15541 cx: &mut Context<Self>,
15542 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15543 ) -> Option<TransactionId> {
15544 self.start_transaction_at(Instant::now(), window, cx);
15545 update(self, window, cx);
15546 self.end_transaction_at(Instant::now(), cx)
15547 }
15548
15549 pub fn start_transaction_at(
15550 &mut self,
15551 now: Instant,
15552 window: &mut Window,
15553 cx: &mut Context<Self>,
15554 ) {
15555 self.end_selection(window, cx);
15556 if let Some(tx_id) = self
15557 .buffer
15558 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15559 {
15560 self.selection_history
15561 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15562 cx.emit(EditorEvent::TransactionBegun {
15563 transaction_id: tx_id,
15564 })
15565 }
15566 }
15567
15568 pub fn end_transaction_at(
15569 &mut self,
15570 now: Instant,
15571 cx: &mut Context<Self>,
15572 ) -> Option<TransactionId> {
15573 if let Some(transaction_id) = self
15574 .buffer
15575 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15576 {
15577 if let Some((_, end_selections)) =
15578 self.selection_history.transaction_mut(transaction_id)
15579 {
15580 *end_selections = Some(self.selections.disjoint_anchors());
15581 } else {
15582 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15583 }
15584
15585 cx.emit(EditorEvent::Edited { transaction_id });
15586 Some(transaction_id)
15587 } else {
15588 None
15589 }
15590 }
15591
15592 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15593 if self.selection_mark_mode {
15594 self.change_selections(None, window, cx, |s| {
15595 s.move_with(|_, sel| {
15596 sel.collapse_to(sel.head(), SelectionGoal::None);
15597 });
15598 })
15599 }
15600 self.selection_mark_mode = true;
15601 cx.notify();
15602 }
15603
15604 pub fn swap_selection_ends(
15605 &mut self,
15606 _: &actions::SwapSelectionEnds,
15607 window: &mut Window,
15608 cx: &mut Context<Self>,
15609 ) {
15610 self.change_selections(None, window, cx, |s| {
15611 s.move_with(|_, sel| {
15612 if sel.start != sel.end {
15613 sel.reversed = !sel.reversed
15614 }
15615 });
15616 });
15617 self.request_autoscroll(Autoscroll::newest(), cx);
15618 cx.notify();
15619 }
15620
15621 pub fn toggle_fold(
15622 &mut self,
15623 _: &actions::ToggleFold,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) {
15627 if self.is_singleton(cx) {
15628 let selection = self.selections.newest::<Point>(cx);
15629
15630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15631 let range = if selection.is_empty() {
15632 let point = selection.head().to_display_point(&display_map);
15633 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15634 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15635 .to_point(&display_map);
15636 start..end
15637 } else {
15638 selection.range()
15639 };
15640 if display_map.folds_in_range(range).next().is_some() {
15641 self.unfold_lines(&Default::default(), window, cx)
15642 } else {
15643 self.fold(&Default::default(), window, cx)
15644 }
15645 } else {
15646 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15647 let buffer_ids: HashSet<_> = self
15648 .selections
15649 .disjoint_anchor_ranges()
15650 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15651 .collect();
15652
15653 let should_unfold = buffer_ids
15654 .iter()
15655 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15656
15657 for buffer_id in buffer_ids {
15658 if should_unfold {
15659 self.unfold_buffer(buffer_id, cx);
15660 } else {
15661 self.fold_buffer(buffer_id, cx);
15662 }
15663 }
15664 }
15665 }
15666
15667 pub fn toggle_fold_recursive(
15668 &mut self,
15669 _: &actions::ToggleFoldRecursive,
15670 window: &mut Window,
15671 cx: &mut Context<Self>,
15672 ) {
15673 let selection = self.selections.newest::<Point>(cx);
15674
15675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15676 let range = if selection.is_empty() {
15677 let point = selection.head().to_display_point(&display_map);
15678 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15679 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15680 .to_point(&display_map);
15681 start..end
15682 } else {
15683 selection.range()
15684 };
15685 if display_map.folds_in_range(range).next().is_some() {
15686 self.unfold_recursive(&Default::default(), window, cx)
15687 } else {
15688 self.fold_recursive(&Default::default(), window, cx)
15689 }
15690 }
15691
15692 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15693 if self.is_singleton(cx) {
15694 let mut to_fold = Vec::new();
15695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15696 let selections = self.selections.all_adjusted(cx);
15697
15698 for selection in selections {
15699 let range = selection.range().sorted();
15700 let buffer_start_row = range.start.row;
15701
15702 if range.start.row != range.end.row {
15703 let mut found = false;
15704 let mut row = range.start.row;
15705 while row <= range.end.row {
15706 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15707 {
15708 found = true;
15709 row = crease.range().end.row + 1;
15710 to_fold.push(crease);
15711 } else {
15712 row += 1
15713 }
15714 }
15715 if found {
15716 continue;
15717 }
15718 }
15719
15720 for row in (0..=range.start.row).rev() {
15721 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15722 if crease.range().end.row >= buffer_start_row {
15723 to_fold.push(crease);
15724 if row <= range.start.row {
15725 break;
15726 }
15727 }
15728 }
15729 }
15730 }
15731
15732 self.fold_creases(to_fold, true, window, cx);
15733 } else {
15734 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15735 let buffer_ids = self
15736 .selections
15737 .disjoint_anchor_ranges()
15738 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15739 .collect::<HashSet<_>>();
15740 for buffer_id in buffer_ids {
15741 self.fold_buffer(buffer_id, cx);
15742 }
15743 }
15744 }
15745
15746 fn fold_at_level(
15747 &mut self,
15748 fold_at: &FoldAtLevel,
15749 window: &mut Window,
15750 cx: &mut Context<Self>,
15751 ) {
15752 if !self.buffer.read(cx).is_singleton() {
15753 return;
15754 }
15755
15756 let fold_at_level = fold_at.0;
15757 let snapshot = self.buffer.read(cx).snapshot(cx);
15758 let mut to_fold = Vec::new();
15759 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15760
15761 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15762 while start_row < end_row {
15763 match self
15764 .snapshot(window, cx)
15765 .crease_for_buffer_row(MultiBufferRow(start_row))
15766 {
15767 Some(crease) => {
15768 let nested_start_row = crease.range().start.row + 1;
15769 let nested_end_row = crease.range().end.row;
15770
15771 if current_level < fold_at_level {
15772 stack.push((nested_start_row, nested_end_row, current_level + 1));
15773 } else if current_level == fold_at_level {
15774 to_fold.push(crease);
15775 }
15776
15777 start_row = nested_end_row + 1;
15778 }
15779 None => start_row += 1,
15780 }
15781 }
15782 }
15783
15784 self.fold_creases(to_fold, true, window, cx);
15785 }
15786
15787 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15788 if self.buffer.read(cx).is_singleton() {
15789 let mut fold_ranges = Vec::new();
15790 let snapshot = self.buffer.read(cx).snapshot(cx);
15791
15792 for row in 0..snapshot.max_row().0 {
15793 if let Some(foldable_range) = self
15794 .snapshot(window, cx)
15795 .crease_for_buffer_row(MultiBufferRow(row))
15796 {
15797 fold_ranges.push(foldable_range);
15798 }
15799 }
15800
15801 self.fold_creases(fold_ranges, true, window, cx);
15802 } else {
15803 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15804 editor
15805 .update_in(cx, |editor, _, cx| {
15806 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15807 editor.fold_buffer(buffer_id, cx);
15808 }
15809 })
15810 .ok();
15811 });
15812 }
15813 }
15814
15815 pub fn fold_function_bodies(
15816 &mut self,
15817 _: &actions::FoldFunctionBodies,
15818 window: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 let snapshot = self.buffer.read(cx).snapshot(cx);
15822
15823 let ranges = snapshot
15824 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15825 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15826 .collect::<Vec<_>>();
15827
15828 let creases = ranges
15829 .into_iter()
15830 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15831 .collect();
15832
15833 self.fold_creases(creases, true, window, cx);
15834 }
15835
15836 pub fn fold_recursive(
15837 &mut self,
15838 _: &actions::FoldRecursive,
15839 window: &mut Window,
15840 cx: &mut Context<Self>,
15841 ) {
15842 let mut to_fold = Vec::new();
15843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15844 let selections = self.selections.all_adjusted(cx);
15845
15846 for selection in selections {
15847 let range = selection.range().sorted();
15848 let buffer_start_row = range.start.row;
15849
15850 if range.start.row != range.end.row {
15851 let mut found = false;
15852 for row in range.start.row..=range.end.row {
15853 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15854 found = true;
15855 to_fold.push(crease);
15856 }
15857 }
15858 if found {
15859 continue;
15860 }
15861 }
15862
15863 for row in (0..=range.start.row).rev() {
15864 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15865 if crease.range().end.row >= buffer_start_row {
15866 to_fold.push(crease);
15867 } else {
15868 break;
15869 }
15870 }
15871 }
15872 }
15873
15874 self.fold_creases(to_fold, true, window, cx);
15875 }
15876
15877 pub fn fold_at(
15878 &mut self,
15879 buffer_row: MultiBufferRow,
15880 window: &mut Window,
15881 cx: &mut Context<Self>,
15882 ) {
15883 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15884
15885 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15886 let autoscroll = self
15887 .selections
15888 .all::<Point>(cx)
15889 .iter()
15890 .any(|selection| crease.range().overlaps(&selection.range()));
15891
15892 self.fold_creases(vec![crease], autoscroll, window, cx);
15893 }
15894 }
15895
15896 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15897 if self.is_singleton(cx) {
15898 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15899 let buffer = &display_map.buffer_snapshot;
15900 let selections = self.selections.all::<Point>(cx);
15901 let ranges = selections
15902 .iter()
15903 .map(|s| {
15904 let range = s.display_range(&display_map).sorted();
15905 let mut start = range.start.to_point(&display_map);
15906 let mut end = range.end.to_point(&display_map);
15907 start.column = 0;
15908 end.column = buffer.line_len(MultiBufferRow(end.row));
15909 start..end
15910 })
15911 .collect::<Vec<_>>();
15912
15913 self.unfold_ranges(&ranges, true, true, cx);
15914 } else {
15915 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15916 let buffer_ids = self
15917 .selections
15918 .disjoint_anchor_ranges()
15919 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15920 .collect::<HashSet<_>>();
15921 for buffer_id in buffer_ids {
15922 self.unfold_buffer(buffer_id, cx);
15923 }
15924 }
15925 }
15926
15927 pub fn unfold_recursive(
15928 &mut self,
15929 _: &UnfoldRecursive,
15930 _window: &mut Window,
15931 cx: &mut Context<Self>,
15932 ) {
15933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15934 let selections = self.selections.all::<Point>(cx);
15935 let ranges = selections
15936 .iter()
15937 .map(|s| {
15938 let mut range = s.display_range(&display_map).sorted();
15939 *range.start.column_mut() = 0;
15940 *range.end.column_mut() = display_map.line_len(range.end.row());
15941 let start = range.start.to_point(&display_map);
15942 let end = range.end.to_point(&display_map);
15943 start..end
15944 })
15945 .collect::<Vec<_>>();
15946
15947 self.unfold_ranges(&ranges, true, true, cx);
15948 }
15949
15950 pub fn unfold_at(
15951 &mut self,
15952 buffer_row: MultiBufferRow,
15953 _window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15957
15958 let intersection_range = Point::new(buffer_row.0, 0)
15959 ..Point::new(
15960 buffer_row.0,
15961 display_map.buffer_snapshot.line_len(buffer_row),
15962 );
15963
15964 let autoscroll = self
15965 .selections
15966 .all::<Point>(cx)
15967 .iter()
15968 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15969
15970 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15971 }
15972
15973 pub fn unfold_all(
15974 &mut self,
15975 _: &actions::UnfoldAll,
15976 _window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 if self.buffer.read(cx).is_singleton() {
15980 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15981 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15982 } else {
15983 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15984 editor
15985 .update(cx, |editor, cx| {
15986 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15987 editor.unfold_buffer(buffer_id, cx);
15988 }
15989 })
15990 .ok();
15991 });
15992 }
15993 }
15994
15995 pub fn fold_selected_ranges(
15996 &mut self,
15997 _: &FoldSelectedRanges,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 let selections = self.selections.all_adjusted(cx);
16002 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16003 let ranges = selections
16004 .into_iter()
16005 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16006 .collect::<Vec<_>>();
16007 self.fold_creases(ranges, true, window, cx);
16008 }
16009
16010 pub fn fold_ranges<T: ToOffset + Clone>(
16011 &mut self,
16012 ranges: Vec<Range<T>>,
16013 auto_scroll: bool,
16014 window: &mut Window,
16015 cx: &mut Context<Self>,
16016 ) {
16017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16018 let ranges = ranges
16019 .into_iter()
16020 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16021 .collect::<Vec<_>>();
16022 self.fold_creases(ranges, auto_scroll, window, cx);
16023 }
16024
16025 pub fn fold_creases<T: ToOffset + Clone>(
16026 &mut self,
16027 creases: Vec<Crease<T>>,
16028 auto_scroll: bool,
16029 _window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) {
16032 if creases.is_empty() {
16033 return;
16034 }
16035
16036 let mut buffers_affected = HashSet::default();
16037 let multi_buffer = self.buffer().read(cx);
16038 for crease in &creases {
16039 if let Some((_, buffer, _)) =
16040 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16041 {
16042 buffers_affected.insert(buffer.read(cx).remote_id());
16043 };
16044 }
16045
16046 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16047
16048 if auto_scroll {
16049 self.request_autoscroll(Autoscroll::fit(), cx);
16050 }
16051
16052 cx.notify();
16053
16054 self.scrollbar_marker_state.dirty = true;
16055 self.folds_did_change(cx);
16056 }
16057
16058 /// Removes any folds whose ranges intersect any of the given ranges.
16059 pub fn unfold_ranges<T: ToOffset + Clone>(
16060 &mut self,
16061 ranges: &[Range<T>],
16062 inclusive: bool,
16063 auto_scroll: bool,
16064 cx: &mut Context<Self>,
16065 ) {
16066 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16067 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16068 });
16069 self.folds_did_change(cx);
16070 }
16071
16072 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16073 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16074 return;
16075 }
16076 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16077 self.display_map.update(cx, |display_map, cx| {
16078 display_map.fold_buffers([buffer_id], cx)
16079 });
16080 cx.emit(EditorEvent::BufferFoldToggled {
16081 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16082 folded: true,
16083 });
16084 cx.notify();
16085 }
16086
16087 pub fn unfold_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 unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16092 self.display_map.update(cx, |display_map, cx| {
16093 display_map.unfold_buffers([buffer_id], cx);
16094 });
16095 cx.emit(EditorEvent::BufferFoldToggled {
16096 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16097 folded: false,
16098 });
16099 cx.notify();
16100 }
16101
16102 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16103 self.display_map.read(cx).is_buffer_folded(buffer)
16104 }
16105
16106 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16107 self.display_map.read(cx).folded_buffers()
16108 }
16109
16110 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16111 self.display_map.update(cx, |display_map, cx| {
16112 display_map.disable_header_for_buffer(buffer_id, cx);
16113 });
16114 cx.notify();
16115 }
16116
16117 /// Removes any folds with the given ranges.
16118 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16119 &mut self,
16120 ranges: &[Range<T>],
16121 type_id: TypeId,
16122 auto_scroll: bool,
16123 cx: &mut Context<Self>,
16124 ) {
16125 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16126 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16127 });
16128 self.folds_did_change(cx);
16129 }
16130
16131 fn remove_folds_with<T: ToOffset + Clone>(
16132 &mut self,
16133 ranges: &[Range<T>],
16134 auto_scroll: bool,
16135 cx: &mut Context<Self>,
16136 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16137 ) {
16138 if ranges.is_empty() {
16139 return;
16140 }
16141
16142 let mut buffers_affected = HashSet::default();
16143 let multi_buffer = self.buffer().read(cx);
16144 for range in ranges {
16145 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16146 buffers_affected.insert(buffer.read(cx).remote_id());
16147 };
16148 }
16149
16150 self.display_map.update(cx, update);
16151
16152 if auto_scroll {
16153 self.request_autoscroll(Autoscroll::fit(), cx);
16154 }
16155
16156 cx.notify();
16157 self.scrollbar_marker_state.dirty = true;
16158 self.active_indent_guides_state.dirty = true;
16159 }
16160
16161 pub fn update_fold_widths(
16162 &mut self,
16163 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16164 cx: &mut Context<Self>,
16165 ) -> bool {
16166 self.display_map
16167 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16168 }
16169
16170 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16171 self.display_map.read(cx).fold_placeholder.clone()
16172 }
16173
16174 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16175 self.buffer.update(cx, |buffer, cx| {
16176 buffer.set_all_diff_hunks_expanded(cx);
16177 });
16178 }
16179
16180 pub fn expand_all_diff_hunks(
16181 &mut self,
16182 _: &ExpandAllDiffHunks,
16183 _window: &mut Window,
16184 cx: &mut Context<Self>,
16185 ) {
16186 self.buffer.update(cx, |buffer, cx| {
16187 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16188 });
16189 }
16190
16191 pub fn toggle_selected_diff_hunks(
16192 &mut self,
16193 _: &ToggleSelectedDiffHunks,
16194 _window: &mut Window,
16195 cx: &mut Context<Self>,
16196 ) {
16197 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16198 self.toggle_diff_hunks_in_ranges(ranges, cx);
16199 }
16200
16201 pub fn diff_hunks_in_ranges<'a>(
16202 &'a self,
16203 ranges: &'a [Range<Anchor>],
16204 buffer: &'a MultiBufferSnapshot,
16205 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16206 ranges.iter().flat_map(move |range| {
16207 let end_excerpt_id = range.end.excerpt_id;
16208 let range = range.to_point(buffer);
16209 let mut peek_end = range.end;
16210 if range.end.row < buffer.max_row().0 {
16211 peek_end = Point::new(range.end.row + 1, 0);
16212 }
16213 buffer
16214 .diff_hunks_in_range(range.start..peek_end)
16215 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16216 })
16217 }
16218
16219 pub fn has_stageable_diff_hunks_in_ranges(
16220 &self,
16221 ranges: &[Range<Anchor>],
16222 snapshot: &MultiBufferSnapshot,
16223 ) -> bool {
16224 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16225 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16226 }
16227
16228 pub fn toggle_staged_selected_diff_hunks(
16229 &mut self,
16230 _: &::git::ToggleStaged,
16231 _: &mut Window,
16232 cx: &mut Context<Self>,
16233 ) {
16234 let snapshot = self.buffer.read(cx).snapshot(cx);
16235 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16236 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16237 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16238 }
16239
16240 pub fn set_render_diff_hunk_controls(
16241 &mut self,
16242 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16243 cx: &mut Context<Self>,
16244 ) {
16245 self.render_diff_hunk_controls = render_diff_hunk_controls;
16246 cx.notify();
16247 }
16248
16249 pub fn stage_and_next(
16250 &mut self,
16251 _: &::git::StageAndNext,
16252 window: &mut Window,
16253 cx: &mut Context<Self>,
16254 ) {
16255 self.do_stage_or_unstage_and_next(true, window, cx);
16256 }
16257
16258 pub fn unstage_and_next(
16259 &mut self,
16260 _: &::git::UnstageAndNext,
16261 window: &mut Window,
16262 cx: &mut Context<Self>,
16263 ) {
16264 self.do_stage_or_unstage_and_next(false, window, cx);
16265 }
16266
16267 pub fn stage_or_unstage_diff_hunks(
16268 &mut self,
16269 stage: bool,
16270 ranges: Vec<Range<Anchor>>,
16271 cx: &mut Context<Self>,
16272 ) {
16273 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16274 cx.spawn(async move |this, cx| {
16275 task.await?;
16276 this.update(cx, |this, cx| {
16277 let snapshot = this.buffer.read(cx).snapshot(cx);
16278 let chunk_by = this
16279 .diff_hunks_in_ranges(&ranges, &snapshot)
16280 .chunk_by(|hunk| hunk.buffer_id);
16281 for (buffer_id, hunks) in &chunk_by {
16282 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16283 }
16284 })
16285 })
16286 .detach_and_log_err(cx);
16287 }
16288
16289 fn save_buffers_for_ranges_if_needed(
16290 &mut self,
16291 ranges: &[Range<Anchor>],
16292 cx: &mut Context<Editor>,
16293 ) -> Task<Result<()>> {
16294 let multibuffer = self.buffer.read(cx);
16295 let snapshot = multibuffer.read(cx);
16296 let buffer_ids: HashSet<_> = ranges
16297 .iter()
16298 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16299 .collect();
16300 drop(snapshot);
16301
16302 let mut buffers = HashSet::default();
16303 for buffer_id in buffer_ids {
16304 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16305 let buffer = buffer_entity.read(cx);
16306 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16307 {
16308 buffers.insert(buffer_entity);
16309 }
16310 }
16311 }
16312
16313 if let Some(project) = &self.project {
16314 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16315 } else {
16316 Task::ready(Ok(()))
16317 }
16318 }
16319
16320 fn do_stage_or_unstage_and_next(
16321 &mut self,
16322 stage: bool,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16327
16328 if ranges.iter().any(|range| range.start != range.end) {
16329 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16330 return;
16331 }
16332
16333 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16334 let snapshot = self.snapshot(window, cx);
16335 let position = self.selections.newest::<Point>(cx).head();
16336 let mut row = snapshot
16337 .buffer_snapshot
16338 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16339 .find(|hunk| hunk.row_range.start.0 > position.row)
16340 .map(|hunk| hunk.row_range.start);
16341
16342 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16343 // Outside of the project diff editor, wrap around to the beginning.
16344 if !all_diff_hunks_expanded {
16345 row = row.or_else(|| {
16346 snapshot
16347 .buffer_snapshot
16348 .diff_hunks_in_range(Point::zero()..position)
16349 .find(|hunk| hunk.row_range.end.0 < position.row)
16350 .map(|hunk| hunk.row_range.start)
16351 });
16352 }
16353
16354 if let Some(row) = row {
16355 let destination = Point::new(row.0, 0);
16356 let autoscroll = Autoscroll::center();
16357
16358 self.unfold_ranges(&[destination..destination], false, false, cx);
16359 self.change_selections(Some(autoscroll), window, cx, |s| {
16360 s.select_ranges([destination..destination]);
16361 });
16362 }
16363 }
16364
16365 fn do_stage_or_unstage(
16366 &self,
16367 stage: bool,
16368 buffer_id: BufferId,
16369 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16370 cx: &mut App,
16371 ) -> Option<()> {
16372 let project = self.project.as_ref()?;
16373 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16374 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16375 let buffer_snapshot = buffer.read(cx).snapshot();
16376 let file_exists = buffer_snapshot
16377 .file()
16378 .is_some_and(|file| file.disk_state().exists());
16379 diff.update(cx, |diff, cx| {
16380 diff.stage_or_unstage_hunks(
16381 stage,
16382 &hunks
16383 .map(|hunk| buffer_diff::DiffHunk {
16384 buffer_range: hunk.buffer_range,
16385 diff_base_byte_range: hunk.diff_base_byte_range,
16386 secondary_status: hunk.secondary_status,
16387 range: Point::zero()..Point::zero(), // unused
16388 })
16389 .collect::<Vec<_>>(),
16390 &buffer_snapshot,
16391 file_exists,
16392 cx,
16393 )
16394 });
16395 None
16396 }
16397
16398 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16399 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16400 self.buffer
16401 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16402 }
16403
16404 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16405 self.buffer.update(cx, |buffer, cx| {
16406 let ranges = vec![Anchor::min()..Anchor::max()];
16407 if !buffer.all_diff_hunks_expanded()
16408 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16409 {
16410 buffer.collapse_diff_hunks(ranges, cx);
16411 true
16412 } else {
16413 false
16414 }
16415 })
16416 }
16417
16418 fn toggle_diff_hunks_in_ranges(
16419 &mut self,
16420 ranges: Vec<Range<Anchor>>,
16421 cx: &mut Context<Editor>,
16422 ) {
16423 self.buffer.update(cx, |buffer, cx| {
16424 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16425 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16426 })
16427 }
16428
16429 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16430 self.buffer.update(cx, |buffer, cx| {
16431 let snapshot = buffer.snapshot(cx);
16432 let excerpt_id = range.end.excerpt_id;
16433 let point_range = range.to_point(&snapshot);
16434 let expand = !buffer.single_hunk_is_expanded(range, cx);
16435 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16436 })
16437 }
16438
16439 pub(crate) fn apply_all_diff_hunks(
16440 &mut self,
16441 _: &ApplyAllDiffHunks,
16442 window: &mut Window,
16443 cx: &mut Context<Self>,
16444 ) {
16445 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16446
16447 let buffers = self.buffer.read(cx).all_buffers();
16448 for branch_buffer in buffers {
16449 branch_buffer.update(cx, |branch_buffer, cx| {
16450 branch_buffer.merge_into_base(Vec::new(), cx);
16451 });
16452 }
16453
16454 if let Some(project) = self.project.clone() {
16455 self.save(true, project, window, cx).detach_and_log_err(cx);
16456 }
16457 }
16458
16459 pub(crate) fn apply_selected_diff_hunks(
16460 &mut self,
16461 _: &ApplyDiffHunk,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) {
16465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16466 let snapshot = self.snapshot(window, cx);
16467 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16468 let mut ranges_by_buffer = HashMap::default();
16469 self.transact(window, cx, |editor, _window, cx| {
16470 for hunk in hunks {
16471 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16472 ranges_by_buffer
16473 .entry(buffer.clone())
16474 .or_insert_with(Vec::new)
16475 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16476 }
16477 }
16478
16479 for (buffer, ranges) in ranges_by_buffer {
16480 buffer.update(cx, |buffer, cx| {
16481 buffer.merge_into_base(ranges, cx);
16482 });
16483 }
16484 });
16485
16486 if let Some(project) = self.project.clone() {
16487 self.save(true, project, window, cx).detach_and_log_err(cx);
16488 }
16489 }
16490
16491 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16492 if hovered != self.gutter_hovered {
16493 self.gutter_hovered = hovered;
16494 cx.notify();
16495 }
16496 }
16497
16498 pub fn insert_blocks(
16499 &mut self,
16500 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16501 autoscroll: Option<Autoscroll>,
16502 cx: &mut Context<Self>,
16503 ) -> Vec<CustomBlockId> {
16504 let blocks = self
16505 .display_map
16506 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16507 if let Some(autoscroll) = autoscroll {
16508 self.request_autoscroll(autoscroll, cx);
16509 }
16510 cx.notify();
16511 blocks
16512 }
16513
16514 pub fn resize_blocks(
16515 &mut self,
16516 heights: HashMap<CustomBlockId, u32>,
16517 autoscroll: Option<Autoscroll>,
16518 cx: &mut Context<Self>,
16519 ) {
16520 self.display_map
16521 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16522 if let Some(autoscroll) = autoscroll {
16523 self.request_autoscroll(autoscroll, cx);
16524 }
16525 cx.notify();
16526 }
16527
16528 pub fn replace_blocks(
16529 &mut self,
16530 renderers: HashMap<CustomBlockId, RenderBlock>,
16531 autoscroll: Option<Autoscroll>,
16532 cx: &mut Context<Self>,
16533 ) {
16534 self.display_map
16535 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16536 if let Some(autoscroll) = autoscroll {
16537 self.request_autoscroll(autoscroll, cx);
16538 }
16539 cx.notify();
16540 }
16541
16542 pub fn remove_blocks(
16543 &mut self,
16544 block_ids: HashSet<CustomBlockId>,
16545 autoscroll: Option<Autoscroll>,
16546 cx: &mut Context<Self>,
16547 ) {
16548 self.display_map.update(cx, |display_map, cx| {
16549 display_map.remove_blocks(block_ids, cx)
16550 });
16551 if let Some(autoscroll) = autoscroll {
16552 self.request_autoscroll(autoscroll, cx);
16553 }
16554 cx.notify();
16555 }
16556
16557 pub fn row_for_block(
16558 &self,
16559 block_id: CustomBlockId,
16560 cx: &mut Context<Self>,
16561 ) -> Option<DisplayRow> {
16562 self.display_map
16563 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16564 }
16565
16566 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16567 self.focused_block = Some(focused_block);
16568 }
16569
16570 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16571 self.focused_block.take()
16572 }
16573
16574 pub fn insert_creases(
16575 &mut self,
16576 creases: impl IntoIterator<Item = Crease<Anchor>>,
16577 cx: &mut Context<Self>,
16578 ) -> Vec<CreaseId> {
16579 self.display_map
16580 .update(cx, |map, cx| map.insert_creases(creases, cx))
16581 }
16582
16583 pub fn remove_creases(
16584 &mut self,
16585 ids: impl IntoIterator<Item = CreaseId>,
16586 cx: &mut Context<Self>,
16587 ) -> Vec<(CreaseId, Range<Anchor>)> {
16588 self.display_map
16589 .update(cx, |map, cx| map.remove_creases(ids, cx))
16590 }
16591
16592 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16593 self.display_map
16594 .update(cx, |map, cx| map.snapshot(cx))
16595 .longest_row()
16596 }
16597
16598 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16599 self.display_map
16600 .update(cx, |map, cx| map.snapshot(cx))
16601 .max_point()
16602 }
16603
16604 pub fn text(&self, cx: &App) -> String {
16605 self.buffer.read(cx).read(cx).text()
16606 }
16607
16608 pub fn is_empty(&self, cx: &App) -> bool {
16609 self.buffer.read(cx).read(cx).is_empty()
16610 }
16611
16612 pub fn text_option(&self, cx: &App) -> Option<String> {
16613 let text = self.text(cx);
16614 let text = text.trim();
16615
16616 if text.is_empty() {
16617 return None;
16618 }
16619
16620 Some(text.to_string())
16621 }
16622
16623 pub fn set_text(
16624 &mut self,
16625 text: impl Into<Arc<str>>,
16626 window: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) {
16629 self.transact(window, cx, |this, _, cx| {
16630 this.buffer
16631 .read(cx)
16632 .as_singleton()
16633 .expect("you can only call set_text on editors for singleton buffers")
16634 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16635 });
16636 }
16637
16638 pub fn display_text(&self, cx: &mut App) -> String {
16639 self.display_map
16640 .update(cx, |map, cx| map.snapshot(cx))
16641 .text()
16642 }
16643
16644 fn create_minimap(
16645 &self,
16646 minimap_settings: MinimapSettings,
16647 window: &mut Window,
16648 cx: &mut Context<Self>,
16649 ) -> Option<Entity<Self>> {
16650 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16651 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16652 }
16653
16654 fn initialize_new_minimap(
16655 &self,
16656 minimap_settings: MinimapSettings,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) -> Entity<Self> {
16660 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16661
16662 let mut minimap = Editor::new_internal(
16663 EditorMode::Minimap {
16664 parent: cx.weak_entity(),
16665 },
16666 self.buffer.clone(),
16667 self.project.clone(),
16668 Some(self.display_map.clone()),
16669 window,
16670 cx,
16671 );
16672 minimap.scroll_manager.clone_state(&self.scroll_manager);
16673 minimap.set_text_style_refinement(TextStyleRefinement {
16674 font_size: Some(MINIMAP_FONT_SIZE),
16675 font_weight: Some(MINIMAP_FONT_WEIGHT),
16676 ..Default::default()
16677 });
16678 minimap.update_minimap_configuration(minimap_settings, cx);
16679 cx.new(|_| minimap)
16680 }
16681
16682 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16683 let current_line_highlight = minimap_settings
16684 .current_line_highlight
16685 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16686 self.set_current_line_highlight(Some(current_line_highlight));
16687 }
16688
16689 pub fn minimap(&self) -> Option<&Entity<Self>> {
16690 self.minimap
16691 .as_ref()
16692 .filter(|_| self.minimap_visibility.visible())
16693 }
16694
16695 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16696 let mut wrap_guides = smallvec::smallvec![];
16697
16698 if self.show_wrap_guides == Some(false) {
16699 return wrap_guides;
16700 }
16701
16702 let settings = self.buffer.read(cx).language_settings(cx);
16703 if settings.show_wrap_guides {
16704 match self.soft_wrap_mode(cx) {
16705 SoftWrap::Column(soft_wrap) => {
16706 wrap_guides.push((soft_wrap as usize, true));
16707 }
16708 SoftWrap::Bounded(soft_wrap) => {
16709 wrap_guides.push((soft_wrap as usize, true));
16710 }
16711 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16712 }
16713 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16714 }
16715
16716 wrap_guides
16717 }
16718
16719 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16720 let settings = self.buffer.read(cx).language_settings(cx);
16721 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16722 match mode {
16723 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16724 SoftWrap::None
16725 }
16726 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16727 language_settings::SoftWrap::PreferredLineLength => {
16728 SoftWrap::Column(settings.preferred_line_length)
16729 }
16730 language_settings::SoftWrap::Bounded => {
16731 SoftWrap::Bounded(settings.preferred_line_length)
16732 }
16733 }
16734 }
16735
16736 pub fn set_soft_wrap_mode(
16737 &mut self,
16738 mode: language_settings::SoftWrap,
16739
16740 cx: &mut Context<Self>,
16741 ) {
16742 self.soft_wrap_mode_override = Some(mode);
16743 cx.notify();
16744 }
16745
16746 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16747 self.hard_wrap = hard_wrap;
16748 cx.notify();
16749 }
16750
16751 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16752 self.text_style_refinement = Some(style);
16753 }
16754
16755 /// called by the Element so we know what style we were most recently rendered with.
16756 pub(crate) fn set_style(
16757 &mut self,
16758 style: EditorStyle,
16759 window: &mut Window,
16760 cx: &mut Context<Self>,
16761 ) {
16762 // We intentionally do not inform the display map about the minimap style
16763 // so that wrapping is not recalculated and stays consistent for the editor
16764 // and its linked minimap.
16765 if !self.mode.is_minimap() {
16766 let rem_size = window.rem_size();
16767 self.display_map.update(cx, |map, cx| {
16768 map.set_font(
16769 style.text.font(),
16770 style.text.font_size.to_pixels(rem_size),
16771 cx,
16772 )
16773 });
16774 }
16775 self.style = Some(style);
16776 }
16777
16778 pub fn style(&self) -> Option<&EditorStyle> {
16779 self.style.as_ref()
16780 }
16781
16782 // Called by the element. This method is not designed to be called outside of the editor
16783 // element's layout code because it does not notify when rewrapping is computed synchronously.
16784 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16785 self.display_map
16786 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16787 }
16788
16789 pub fn set_soft_wrap(&mut self) {
16790 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16791 }
16792
16793 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16794 if self.soft_wrap_mode_override.is_some() {
16795 self.soft_wrap_mode_override.take();
16796 } else {
16797 let soft_wrap = match self.soft_wrap_mode(cx) {
16798 SoftWrap::GitDiff => return,
16799 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16800 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16801 language_settings::SoftWrap::None
16802 }
16803 };
16804 self.soft_wrap_mode_override = Some(soft_wrap);
16805 }
16806 cx.notify();
16807 }
16808
16809 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16810 let Some(workspace) = self.workspace() else {
16811 return;
16812 };
16813 let fs = workspace.read(cx).app_state().fs.clone();
16814 let current_show = TabBarSettings::get_global(cx).show;
16815 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16816 setting.show = Some(!current_show);
16817 });
16818 }
16819
16820 pub fn toggle_indent_guides(
16821 &mut self,
16822 _: &ToggleIndentGuides,
16823 _: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16827 self.buffer
16828 .read(cx)
16829 .language_settings(cx)
16830 .indent_guides
16831 .enabled
16832 });
16833 self.show_indent_guides = Some(!currently_enabled);
16834 cx.notify();
16835 }
16836
16837 fn should_show_indent_guides(&self) -> Option<bool> {
16838 self.show_indent_guides
16839 }
16840
16841 pub fn toggle_line_numbers(
16842 &mut self,
16843 _: &ToggleLineNumbers,
16844 _: &mut Window,
16845 cx: &mut Context<Self>,
16846 ) {
16847 let mut editor_settings = EditorSettings::get_global(cx).clone();
16848 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16849 EditorSettings::override_global(editor_settings, cx);
16850 }
16851
16852 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16853 if let Some(show_line_numbers) = self.show_line_numbers {
16854 return show_line_numbers;
16855 }
16856 EditorSettings::get_global(cx).gutter.line_numbers
16857 }
16858
16859 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16860 self.use_relative_line_numbers
16861 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16862 }
16863
16864 pub fn toggle_relative_line_numbers(
16865 &mut self,
16866 _: &ToggleRelativeLineNumbers,
16867 _: &mut Window,
16868 cx: &mut Context<Self>,
16869 ) {
16870 let is_relative = self.should_use_relative_line_numbers(cx);
16871 self.set_relative_line_number(Some(!is_relative), cx)
16872 }
16873
16874 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16875 self.use_relative_line_numbers = is_relative;
16876 cx.notify();
16877 }
16878
16879 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16880 self.show_gutter = show_gutter;
16881 cx.notify();
16882 }
16883
16884 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16885 self.show_scrollbars = show_scrollbars;
16886 cx.notify();
16887 }
16888
16889 pub fn set_minimap_visibility(
16890 &mut self,
16891 minimap_visibility: MinimapVisibility,
16892 window: &mut Window,
16893 cx: &mut Context<Self>,
16894 ) {
16895 if self.minimap_visibility != minimap_visibility {
16896 if minimap_visibility.visible() && self.minimap.is_none() {
16897 let minimap_settings = EditorSettings::get_global(cx).minimap;
16898 self.minimap =
16899 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16900 }
16901 self.minimap_visibility = minimap_visibility;
16902 cx.notify();
16903 }
16904 }
16905
16906 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16907 self.set_show_scrollbars(false, cx);
16908 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16909 }
16910
16911 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16912 self.show_line_numbers = Some(show_line_numbers);
16913 cx.notify();
16914 }
16915
16916 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16917 self.disable_expand_excerpt_buttons = true;
16918 cx.notify();
16919 }
16920
16921 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16922 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16923 cx.notify();
16924 }
16925
16926 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16927 self.show_code_actions = Some(show_code_actions);
16928 cx.notify();
16929 }
16930
16931 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16932 self.show_runnables = Some(show_runnables);
16933 cx.notify();
16934 }
16935
16936 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16937 self.show_breakpoints = Some(show_breakpoints);
16938 cx.notify();
16939 }
16940
16941 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16942 if self.display_map.read(cx).masked != masked {
16943 self.display_map.update(cx, |map, _| map.masked = masked);
16944 }
16945 cx.notify()
16946 }
16947
16948 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16949 self.show_wrap_guides = Some(show_wrap_guides);
16950 cx.notify();
16951 }
16952
16953 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16954 self.show_indent_guides = Some(show_indent_guides);
16955 cx.notify();
16956 }
16957
16958 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16959 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16960 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16961 if let Some(dir) = file.abs_path(cx).parent() {
16962 return Some(dir.to_owned());
16963 }
16964 }
16965
16966 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16967 return Some(project_path.path.to_path_buf());
16968 }
16969 }
16970
16971 None
16972 }
16973
16974 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16975 self.active_excerpt(cx)?
16976 .1
16977 .read(cx)
16978 .file()
16979 .and_then(|f| f.as_local())
16980 }
16981
16982 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16983 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16984 let buffer = buffer.read(cx);
16985 if let Some(project_path) = buffer.project_path(cx) {
16986 let project = self.project.as_ref()?.read(cx);
16987 project.absolute_path(&project_path, cx)
16988 } else {
16989 buffer
16990 .file()
16991 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16992 }
16993 })
16994 }
16995
16996 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16997 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16998 let project_path = buffer.read(cx).project_path(cx)?;
16999 let project = self.project.as_ref()?.read(cx);
17000 let entry = project.entry_for_path(&project_path, cx)?;
17001 let path = entry.path.to_path_buf();
17002 Some(path)
17003 })
17004 }
17005
17006 pub fn reveal_in_finder(
17007 &mut self,
17008 _: &RevealInFileManager,
17009 _window: &mut Window,
17010 cx: &mut Context<Self>,
17011 ) {
17012 if let Some(target) = self.target_file(cx) {
17013 cx.reveal_path(&target.abs_path(cx));
17014 }
17015 }
17016
17017 pub fn copy_path(
17018 &mut self,
17019 _: &zed_actions::workspace::CopyPath,
17020 _window: &mut Window,
17021 cx: &mut Context<Self>,
17022 ) {
17023 if let Some(path) = self.target_file_abs_path(cx) {
17024 if let Some(path) = path.to_str() {
17025 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17026 }
17027 }
17028 }
17029
17030 pub fn copy_relative_path(
17031 &mut self,
17032 _: &zed_actions::workspace::CopyRelativePath,
17033 _window: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) {
17036 if let Some(path) = self.target_file_path(cx) {
17037 if let Some(path) = path.to_str() {
17038 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17039 }
17040 }
17041 }
17042
17043 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17044 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17045 buffer.read(cx).project_path(cx)
17046 } else {
17047 None
17048 }
17049 }
17050
17051 // Returns true if the editor handled a go-to-line request
17052 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17053 maybe!({
17054 let breakpoint_store = self.breakpoint_store.as_ref()?;
17055
17056 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17057 else {
17058 self.clear_row_highlights::<ActiveDebugLine>();
17059 return None;
17060 };
17061
17062 let position = active_stack_frame.position;
17063 let buffer_id = position.buffer_id?;
17064 let snapshot = self
17065 .project
17066 .as_ref()?
17067 .read(cx)
17068 .buffer_for_id(buffer_id, cx)?
17069 .read(cx)
17070 .snapshot();
17071
17072 let mut handled = false;
17073 for (id, ExcerptRange { context, .. }) in
17074 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17075 {
17076 if context.start.cmp(&position, &snapshot).is_ge()
17077 || context.end.cmp(&position, &snapshot).is_lt()
17078 {
17079 continue;
17080 }
17081 let snapshot = self.buffer.read(cx).snapshot(cx);
17082 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17083
17084 handled = true;
17085 self.clear_row_highlights::<ActiveDebugLine>();
17086
17087 self.go_to_line::<ActiveDebugLine>(
17088 multibuffer_anchor,
17089 Some(cx.theme().colors().editor_debugger_active_line_background),
17090 window,
17091 cx,
17092 );
17093
17094 cx.notify();
17095 }
17096
17097 handled.then_some(())
17098 })
17099 .is_some()
17100 }
17101
17102 pub fn copy_file_name_without_extension(
17103 &mut self,
17104 _: &CopyFileNameWithoutExtension,
17105 _: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) {
17108 if let Some(file) = self.target_file(cx) {
17109 if let Some(file_stem) = file.path().file_stem() {
17110 if let Some(name) = file_stem.to_str() {
17111 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17112 }
17113 }
17114 }
17115 }
17116
17117 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17118 if let Some(file) = self.target_file(cx) {
17119 if let Some(file_name) = file.path().file_name() {
17120 if let Some(name) = file_name.to_str() {
17121 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17122 }
17123 }
17124 }
17125 }
17126
17127 pub fn toggle_git_blame(
17128 &mut self,
17129 _: &::git::Blame,
17130 window: &mut Window,
17131 cx: &mut Context<Self>,
17132 ) {
17133 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17134
17135 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17136 self.start_git_blame(true, window, cx);
17137 }
17138
17139 cx.notify();
17140 }
17141
17142 pub fn toggle_git_blame_inline(
17143 &mut self,
17144 _: &ToggleGitBlameInline,
17145 window: &mut Window,
17146 cx: &mut Context<Self>,
17147 ) {
17148 self.toggle_git_blame_inline_internal(true, window, cx);
17149 cx.notify();
17150 }
17151
17152 pub fn open_git_blame_commit(
17153 &mut self,
17154 _: &OpenGitBlameCommit,
17155 window: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) {
17158 self.open_git_blame_commit_internal(window, cx);
17159 }
17160
17161 fn open_git_blame_commit_internal(
17162 &mut self,
17163 window: &mut Window,
17164 cx: &mut Context<Self>,
17165 ) -> Option<()> {
17166 let blame = self.blame.as_ref()?;
17167 let snapshot = self.snapshot(window, cx);
17168 let cursor = self.selections.newest::<Point>(cx).head();
17169 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17170 let blame_entry = blame
17171 .update(cx, |blame, cx| {
17172 blame
17173 .blame_for_rows(
17174 &[RowInfo {
17175 buffer_id: Some(buffer.remote_id()),
17176 buffer_row: Some(point.row),
17177 ..Default::default()
17178 }],
17179 cx,
17180 )
17181 .next()
17182 })
17183 .flatten()?;
17184 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17185 let repo = blame.read(cx).repository(cx)?;
17186 let workspace = self.workspace()?.downgrade();
17187 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17188 None
17189 }
17190
17191 pub fn git_blame_inline_enabled(&self) -> bool {
17192 self.git_blame_inline_enabled
17193 }
17194
17195 pub fn toggle_selection_menu(
17196 &mut self,
17197 _: &ToggleSelectionMenu,
17198 _: &mut Window,
17199 cx: &mut Context<Self>,
17200 ) {
17201 self.show_selection_menu = self
17202 .show_selection_menu
17203 .map(|show_selections_menu| !show_selections_menu)
17204 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17205
17206 cx.notify();
17207 }
17208
17209 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17210 self.show_selection_menu
17211 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17212 }
17213
17214 fn start_git_blame(
17215 &mut self,
17216 user_triggered: bool,
17217 window: &mut Window,
17218 cx: &mut Context<Self>,
17219 ) {
17220 if let Some(project) = self.project.as_ref() {
17221 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17222 return;
17223 };
17224
17225 if buffer.read(cx).file().is_none() {
17226 return;
17227 }
17228
17229 let focused = self.focus_handle(cx).contains_focused(window, cx);
17230
17231 let project = project.clone();
17232 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17233 self.blame_subscription =
17234 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17235 self.blame = Some(blame);
17236 }
17237 }
17238
17239 fn toggle_git_blame_inline_internal(
17240 &mut self,
17241 user_triggered: bool,
17242 window: &mut Window,
17243 cx: &mut Context<Self>,
17244 ) {
17245 if self.git_blame_inline_enabled {
17246 self.git_blame_inline_enabled = false;
17247 self.show_git_blame_inline = false;
17248 self.show_git_blame_inline_delay_task.take();
17249 } else {
17250 self.git_blame_inline_enabled = true;
17251 self.start_git_blame_inline(user_triggered, window, cx);
17252 }
17253
17254 cx.notify();
17255 }
17256
17257 fn start_git_blame_inline(
17258 &mut self,
17259 user_triggered: bool,
17260 window: &mut Window,
17261 cx: &mut Context<Self>,
17262 ) {
17263 self.start_git_blame(user_triggered, window, cx);
17264
17265 if ProjectSettings::get_global(cx)
17266 .git
17267 .inline_blame_delay()
17268 .is_some()
17269 {
17270 self.start_inline_blame_timer(window, cx);
17271 } else {
17272 self.show_git_blame_inline = true
17273 }
17274 }
17275
17276 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17277 self.blame.as_ref()
17278 }
17279
17280 pub fn show_git_blame_gutter(&self) -> bool {
17281 self.show_git_blame_gutter
17282 }
17283
17284 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17285 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17286 }
17287
17288 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17289 self.show_git_blame_inline
17290 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17291 && !self.newest_selection_head_on_empty_line(cx)
17292 && self.has_blame_entries(cx)
17293 }
17294
17295 fn has_blame_entries(&self, cx: &App) -> bool {
17296 self.blame()
17297 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17298 }
17299
17300 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17301 let cursor_anchor = self.selections.newest_anchor().head();
17302
17303 let snapshot = self.buffer.read(cx).snapshot(cx);
17304 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17305
17306 snapshot.line_len(buffer_row) == 0
17307 }
17308
17309 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17310 let buffer_and_selection = maybe!({
17311 let selection = self.selections.newest::<Point>(cx);
17312 let selection_range = selection.range();
17313
17314 let multi_buffer = self.buffer().read(cx);
17315 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17316 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17317
17318 let (buffer, range, _) = if selection.reversed {
17319 buffer_ranges.first()
17320 } else {
17321 buffer_ranges.last()
17322 }?;
17323
17324 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17325 ..text::ToPoint::to_point(&range.end, &buffer).row;
17326 Some((
17327 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17328 selection,
17329 ))
17330 });
17331
17332 let Some((buffer, selection)) = buffer_and_selection else {
17333 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17334 };
17335
17336 let Some(project) = self.project.as_ref() else {
17337 return Task::ready(Err(anyhow!("editor does not have project")));
17338 };
17339
17340 project.update(cx, |project, cx| {
17341 project.get_permalink_to_line(&buffer, selection, cx)
17342 })
17343 }
17344
17345 pub fn copy_permalink_to_line(
17346 &mut self,
17347 _: &CopyPermalinkToLine,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) {
17351 let permalink_task = self.get_permalink_to_line(cx);
17352 let workspace = self.workspace();
17353
17354 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17355 Ok(permalink) => {
17356 cx.update(|_, cx| {
17357 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17358 })
17359 .ok();
17360 }
17361 Err(err) => {
17362 let message = format!("Failed to copy permalink: {err}");
17363
17364 anyhow::Result::<()>::Err(err).log_err();
17365
17366 if let Some(workspace) = workspace {
17367 workspace
17368 .update_in(cx, |workspace, _, cx| {
17369 struct CopyPermalinkToLine;
17370
17371 workspace.show_toast(
17372 Toast::new(
17373 NotificationId::unique::<CopyPermalinkToLine>(),
17374 message,
17375 ),
17376 cx,
17377 )
17378 })
17379 .ok();
17380 }
17381 }
17382 })
17383 .detach();
17384 }
17385
17386 pub fn copy_file_location(
17387 &mut self,
17388 _: &CopyFileLocation,
17389 _: &mut Window,
17390 cx: &mut Context<Self>,
17391 ) {
17392 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17393 if let Some(file) = self.target_file(cx) {
17394 if let Some(path) = file.path().to_str() {
17395 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17396 }
17397 }
17398 }
17399
17400 pub fn open_permalink_to_line(
17401 &mut self,
17402 _: &OpenPermalinkToLine,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) {
17406 let permalink_task = self.get_permalink_to_line(cx);
17407 let workspace = self.workspace();
17408
17409 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17410 Ok(permalink) => {
17411 cx.update(|_, cx| {
17412 cx.open_url(permalink.as_ref());
17413 })
17414 .ok();
17415 }
17416 Err(err) => {
17417 let message = format!("Failed to open permalink: {err}");
17418
17419 anyhow::Result::<()>::Err(err).log_err();
17420
17421 if let Some(workspace) = workspace {
17422 workspace
17423 .update(cx, |workspace, cx| {
17424 struct OpenPermalinkToLine;
17425
17426 workspace.show_toast(
17427 Toast::new(
17428 NotificationId::unique::<OpenPermalinkToLine>(),
17429 message,
17430 ),
17431 cx,
17432 )
17433 })
17434 .ok();
17435 }
17436 }
17437 })
17438 .detach();
17439 }
17440
17441 pub fn insert_uuid_v4(
17442 &mut self,
17443 _: &InsertUuidV4,
17444 window: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) {
17447 self.insert_uuid(UuidVersion::V4, window, cx);
17448 }
17449
17450 pub fn insert_uuid_v7(
17451 &mut self,
17452 _: &InsertUuidV7,
17453 window: &mut Window,
17454 cx: &mut Context<Self>,
17455 ) {
17456 self.insert_uuid(UuidVersion::V7, window, cx);
17457 }
17458
17459 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17460 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17461 self.transact(window, cx, |this, window, cx| {
17462 let edits = this
17463 .selections
17464 .all::<Point>(cx)
17465 .into_iter()
17466 .map(|selection| {
17467 let uuid = match version {
17468 UuidVersion::V4 => uuid::Uuid::new_v4(),
17469 UuidVersion::V7 => uuid::Uuid::now_v7(),
17470 };
17471
17472 (selection.range(), uuid.to_string())
17473 });
17474 this.edit(edits, cx);
17475 this.refresh_inline_completion(true, false, window, cx);
17476 });
17477 }
17478
17479 pub fn open_selections_in_multibuffer(
17480 &mut self,
17481 _: &OpenSelectionsInMultibuffer,
17482 window: &mut Window,
17483 cx: &mut Context<Self>,
17484 ) {
17485 let multibuffer = self.buffer.read(cx);
17486
17487 let Some(buffer) = multibuffer.as_singleton() else {
17488 return;
17489 };
17490
17491 let Some(workspace) = self.workspace() else {
17492 return;
17493 };
17494
17495 let locations = self
17496 .selections
17497 .disjoint_anchors()
17498 .iter()
17499 .map(|range| Location {
17500 buffer: buffer.clone(),
17501 range: range.start.text_anchor..range.end.text_anchor,
17502 })
17503 .collect::<Vec<_>>();
17504
17505 let title = multibuffer.title(cx).to_string();
17506
17507 cx.spawn_in(window, async move |_, cx| {
17508 workspace.update_in(cx, |workspace, window, cx| {
17509 Self::open_locations_in_multibuffer(
17510 workspace,
17511 locations,
17512 format!("Selections for '{title}'"),
17513 false,
17514 MultibufferSelectionMode::All,
17515 window,
17516 cx,
17517 );
17518 })
17519 })
17520 .detach();
17521 }
17522
17523 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17524 /// last highlight added will be used.
17525 ///
17526 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17527 pub fn highlight_rows<T: 'static>(
17528 &mut self,
17529 range: Range<Anchor>,
17530 color: Hsla,
17531 options: RowHighlightOptions,
17532 cx: &mut Context<Self>,
17533 ) {
17534 let snapshot = self.buffer().read(cx).snapshot(cx);
17535 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17536 let ix = row_highlights.binary_search_by(|highlight| {
17537 Ordering::Equal
17538 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17539 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17540 });
17541
17542 if let Err(mut ix) = ix {
17543 let index = post_inc(&mut self.highlight_order);
17544
17545 // If this range intersects with the preceding highlight, then merge it with
17546 // the preceding highlight. Otherwise insert a new highlight.
17547 let mut merged = false;
17548 if ix > 0 {
17549 let prev_highlight = &mut row_highlights[ix - 1];
17550 if prev_highlight
17551 .range
17552 .end
17553 .cmp(&range.start, &snapshot)
17554 .is_ge()
17555 {
17556 ix -= 1;
17557 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17558 prev_highlight.range.end = range.end;
17559 }
17560 merged = true;
17561 prev_highlight.index = index;
17562 prev_highlight.color = color;
17563 prev_highlight.options = options;
17564 }
17565 }
17566
17567 if !merged {
17568 row_highlights.insert(
17569 ix,
17570 RowHighlight {
17571 range: range.clone(),
17572 index,
17573 color,
17574 options,
17575 type_id: TypeId::of::<T>(),
17576 },
17577 );
17578 }
17579
17580 // If any of the following highlights intersect with this one, merge them.
17581 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17582 let highlight = &row_highlights[ix];
17583 if next_highlight
17584 .range
17585 .start
17586 .cmp(&highlight.range.end, &snapshot)
17587 .is_le()
17588 {
17589 if next_highlight
17590 .range
17591 .end
17592 .cmp(&highlight.range.end, &snapshot)
17593 .is_gt()
17594 {
17595 row_highlights[ix].range.end = next_highlight.range.end;
17596 }
17597 row_highlights.remove(ix + 1);
17598 } else {
17599 break;
17600 }
17601 }
17602 }
17603 }
17604
17605 /// Remove any highlighted row ranges of the given type that intersect the
17606 /// given ranges.
17607 pub fn remove_highlighted_rows<T: 'static>(
17608 &mut self,
17609 ranges_to_remove: Vec<Range<Anchor>>,
17610 cx: &mut Context<Self>,
17611 ) {
17612 let snapshot = self.buffer().read(cx).snapshot(cx);
17613 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17614 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17615 row_highlights.retain(|highlight| {
17616 while let Some(range_to_remove) = ranges_to_remove.peek() {
17617 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17618 Ordering::Less | Ordering::Equal => {
17619 ranges_to_remove.next();
17620 }
17621 Ordering::Greater => {
17622 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17623 Ordering::Less | Ordering::Equal => {
17624 return false;
17625 }
17626 Ordering::Greater => break,
17627 }
17628 }
17629 }
17630 }
17631
17632 true
17633 })
17634 }
17635
17636 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17637 pub fn clear_row_highlights<T: 'static>(&mut self) {
17638 self.highlighted_rows.remove(&TypeId::of::<T>());
17639 }
17640
17641 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17642 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17643 self.highlighted_rows
17644 .get(&TypeId::of::<T>())
17645 .map_or(&[] as &[_], |vec| vec.as_slice())
17646 .iter()
17647 .map(|highlight| (highlight.range.clone(), highlight.color))
17648 }
17649
17650 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17651 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17652 /// Allows to ignore certain kinds of highlights.
17653 pub fn highlighted_display_rows(
17654 &self,
17655 window: &mut Window,
17656 cx: &mut App,
17657 ) -> BTreeMap<DisplayRow, LineHighlight> {
17658 let snapshot = self.snapshot(window, cx);
17659 let mut used_highlight_orders = HashMap::default();
17660 self.highlighted_rows
17661 .iter()
17662 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17663 .fold(
17664 BTreeMap::<DisplayRow, LineHighlight>::new(),
17665 |mut unique_rows, highlight| {
17666 let start = highlight.range.start.to_display_point(&snapshot);
17667 let end = highlight.range.end.to_display_point(&snapshot);
17668 let start_row = start.row().0;
17669 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17670 && end.column() == 0
17671 {
17672 end.row().0.saturating_sub(1)
17673 } else {
17674 end.row().0
17675 };
17676 for row in start_row..=end_row {
17677 let used_index =
17678 used_highlight_orders.entry(row).or_insert(highlight.index);
17679 if highlight.index >= *used_index {
17680 *used_index = highlight.index;
17681 unique_rows.insert(
17682 DisplayRow(row),
17683 LineHighlight {
17684 include_gutter: highlight.options.include_gutter,
17685 border: None,
17686 background: highlight.color.into(),
17687 type_id: Some(highlight.type_id),
17688 },
17689 );
17690 }
17691 }
17692 unique_rows
17693 },
17694 )
17695 }
17696
17697 pub fn highlighted_display_row_for_autoscroll(
17698 &self,
17699 snapshot: &DisplaySnapshot,
17700 ) -> Option<DisplayRow> {
17701 self.highlighted_rows
17702 .values()
17703 .flat_map(|highlighted_rows| highlighted_rows.iter())
17704 .filter_map(|highlight| {
17705 if highlight.options.autoscroll {
17706 Some(highlight.range.start.to_display_point(snapshot).row())
17707 } else {
17708 None
17709 }
17710 })
17711 .min()
17712 }
17713
17714 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17715 self.highlight_background::<SearchWithinRange>(
17716 ranges,
17717 |colors| colors.editor_document_highlight_read_background,
17718 cx,
17719 )
17720 }
17721
17722 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17723 self.breadcrumb_header = Some(new_header);
17724 }
17725
17726 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17727 self.clear_background_highlights::<SearchWithinRange>(cx);
17728 }
17729
17730 pub fn highlight_background<T: 'static>(
17731 &mut self,
17732 ranges: &[Range<Anchor>],
17733 color_fetcher: fn(&ThemeColors) -> Hsla,
17734 cx: &mut Context<Self>,
17735 ) {
17736 self.background_highlights
17737 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17738 self.scrollbar_marker_state.dirty = true;
17739 cx.notify();
17740 }
17741
17742 pub fn clear_background_highlights<T: 'static>(
17743 &mut self,
17744 cx: &mut Context<Self>,
17745 ) -> Option<BackgroundHighlight> {
17746 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17747 if !text_highlights.1.is_empty() {
17748 self.scrollbar_marker_state.dirty = true;
17749 cx.notify();
17750 }
17751 Some(text_highlights)
17752 }
17753
17754 pub fn highlight_gutter<T: 'static>(
17755 &mut self,
17756 ranges: &[Range<Anchor>],
17757 color_fetcher: fn(&App) -> Hsla,
17758 cx: &mut Context<Self>,
17759 ) {
17760 self.gutter_highlights
17761 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17762 cx.notify();
17763 }
17764
17765 pub fn clear_gutter_highlights<T: 'static>(
17766 &mut self,
17767 cx: &mut Context<Self>,
17768 ) -> Option<GutterHighlight> {
17769 cx.notify();
17770 self.gutter_highlights.remove(&TypeId::of::<T>())
17771 }
17772
17773 #[cfg(feature = "test-support")]
17774 pub fn all_text_background_highlights(
17775 &self,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17779 let snapshot = self.snapshot(window, cx);
17780 let buffer = &snapshot.buffer_snapshot;
17781 let start = buffer.anchor_before(0);
17782 let end = buffer.anchor_after(buffer.len());
17783 let theme = cx.theme().colors();
17784 self.background_highlights_in_range(start..end, &snapshot, theme)
17785 }
17786
17787 #[cfg(feature = "test-support")]
17788 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17789 let snapshot = self.buffer().read(cx).snapshot(cx);
17790
17791 let highlights = self
17792 .background_highlights
17793 .get(&TypeId::of::<items::BufferSearchHighlights>());
17794
17795 if let Some((_color, ranges)) = highlights {
17796 ranges
17797 .iter()
17798 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17799 .collect_vec()
17800 } else {
17801 vec![]
17802 }
17803 }
17804
17805 fn document_highlights_for_position<'a>(
17806 &'a self,
17807 position: Anchor,
17808 buffer: &'a MultiBufferSnapshot,
17809 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17810 let read_highlights = self
17811 .background_highlights
17812 .get(&TypeId::of::<DocumentHighlightRead>())
17813 .map(|h| &h.1);
17814 let write_highlights = self
17815 .background_highlights
17816 .get(&TypeId::of::<DocumentHighlightWrite>())
17817 .map(|h| &h.1);
17818 let left_position = position.bias_left(buffer);
17819 let right_position = position.bias_right(buffer);
17820 read_highlights
17821 .into_iter()
17822 .chain(write_highlights)
17823 .flat_map(move |ranges| {
17824 let start_ix = match ranges.binary_search_by(|probe| {
17825 let cmp = probe.end.cmp(&left_position, buffer);
17826 if cmp.is_ge() {
17827 Ordering::Greater
17828 } else {
17829 Ordering::Less
17830 }
17831 }) {
17832 Ok(i) | Err(i) => i,
17833 };
17834
17835 ranges[start_ix..]
17836 .iter()
17837 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17838 })
17839 }
17840
17841 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17842 self.background_highlights
17843 .get(&TypeId::of::<T>())
17844 .map_or(false, |(_, highlights)| !highlights.is_empty())
17845 }
17846
17847 pub fn background_highlights_in_range(
17848 &self,
17849 search_range: Range<Anchor>,
17850 display_snapshot: &DisplaySnapshot,
17851 theme: &ThemeColors,
17852 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17853 let mut results = Vec::new();
17854 for (color_fetcher, ranges) in self.background_highlights.values() {
17855 let color = color_fetcher(theme);
17856 let start_ix = match ranges.binary_search_by(|probe| {
17857 let cmp = probe
17858 .end
17859 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17860 if cmp.is_gt() {
17861 Ordering::Greater
17862 } else {
17863 Ordering::Less
17864 }
17865 }) {
17866 Ok(i) | Err(i) => i,
17867 };
17868 for range in &ranges[start_ix..] {
17869 if range
17870 .start
17871 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17872 .is_ge()
17873 {
17874 break;
17875 }
17876
17877 let start = range.start.to_display_point(display_snapshot);
17878 let end = range.end.to_display_point(display_snapshot);
17879 results.push((start..end, color))
17880 }
17881 }
17882 results
17883 }
17884
17885 pub fn background_highlight_row_ranges<T: 'static>(
17886 &self,
17887 search_range: Range<Anchor>,
17888 display_snapshot: &DisplaySnapshot,
17889 count: usize,
17890 ) -> Vec<RangeInclusive<DisplayPoint>> {
17891 let mut results = Vec::new();
17892 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17893 return vec![];
17894 };
17895
17896 let start_ix = match ranges.binary_search_by(|probe| {
17897 let cmp = probe
17898 .end
17899 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17900 if cmp.is_gt() {
17901 Ordering::Greater
17902 } else {
17903 Ordering::Less
17904 }
17905 }) {
17906 Ok(i) | Err(i) => i,
17907 };
17908 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17909 if let (Some(start_display), Some(end_display)) = (start, end) {
17910 results.push(
17911 start_display.to_display_point(display_snapshot)
17912 ..=end_display.to_display_point(display_snapshot),
17913 );
17914 }
17915 };
17916 let mut start_row: Option<Point> = None;
17917 let mut end_row: Option<Point> = None;
17918 if ranges.len() > count {
17919 return Vec::new();
17920 }
17921 for range in &ranges[start_ix..] {
17922 if range
17923 .start
17924 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17925 .is_ge()
17926 {
17927 break;
17928 }
17929 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17930 if let Some(current_row) = &end_row {
17931 if end.row == current_row.row {
17932 continue;
17933 }
17934 }
17935 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17936 if start_row.is_none() {
17937 assert_eq!(end_row, None);
17938 start_row = Some(start);
17939 end_row = Some(end);
17940 continue;
17941 }
17942 if let Some(current_end) = end_row.as_mut() {
17943 if start.row > current_end.row + 1 {
17944 push_region(start_row, end_row);
17945 start_row = Some(start);
17946 end_row = Some(end);
17947 } else {
17948 // Merge two hunks.
17949 *current_end = end;
17950 }
17951 } else {
17952 unreachable!();
17953 }
17954 }
17955 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17956 push_region(start_row, end_row);
17957 results
17958 }
17959
17960 pub fn gutter_highlights_in_range(
17961 &self,
17962 search_range: Range<Anchor>,
17963 display_snapshot: &DisplaySnapshot,
17964 cx: &App,
17965 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17966 let mut results = Vec::new();
17967 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17968 let color = color_fetcher(cx);
17969 let start_ix = match ranges.binary_search_by(|probe| {
17970 let cmp = probe
17971 .end
17972 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17973 if cmp.is_gt() {
17974 Ordering::Greater
17975 } else {
17976 Ordering::Less
17977 }
17978 }) {
17979 Ok(i) | Err(i) => i,
17980 };
17981 for range in &ranges[start_ix..] {
17982 if range
17983 .start
17984 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17985 .is_ge()
17986 {
17987 break;
17988 }
17989
17990 let start = range.start.to_display_point(display_snapshot);
17991 let end = range.end.to_display_point(display_snapshot);
17992 results.push((start..end, color))
17993 }
17994 }
17995 results
17996 }
17997
17998 /// Get the text ranges corresponding to the redaction query
17999 pub fn redacted_ranges(
18000 &self,
18001 search_range: Range<Anchor>,
18002 display_snapshot: &DisplaySnapshot,
18003 cx: &App,
18004 ) -> Vec<Range<DisplayPoint>> {
18005 display_snapshot
18006 .buffer_snapshot
18007 .redacted_ranges(search_range, |file| {
18008 if let Some(file) = file {
18009 file.is_private()
18010 && EditorSettings::get(
18011 Some(SettingsLocation {
18012 worktree_id: file.worktree_id(cx),
18013 path: file.path().as_ref(),
18014 }),
18015 cx,
18016 )
18017 .redact_private_values
18018 } else {
18019 false
18020 }
18021 })
18022 .map(|range| {
18023 range.start.to_display_point(display_snapshot)
18024 ..range.end.to_display_point(display_snapshot)
18025 })
18026 .collect()
18027 }
18028
18029 pub fn highlight_text<T: 'static>(
18030 &mut self,
18031 ranges: Vec<Range<Anchor>>,
18032 style: HighlightStyle,
18033 cx: &mut Context<Self>,
18034 ) {
18035 self.display_map.update(cx, |map, _| {
18036 map.highlight_text(TypeId::of::<T>(), ranges, style)
18037 });
18038 cx.notify();
18039 }
18040
18041 pub(crate) fn highlight_inlays<T: 'static>(
18042 &mut self,
18043 highlights: Vec<InlayHighlight>,
18044 style: HighlightStyle,
18045 cx: &mut Context<Self>,
18046 ) {
18047 self.display_map.update(cx, |map, _| {
18048 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18049 });
18050 cx.notify();
18051 }
18052
18053 pub fn text_highlights<'a, T: 'static>(
18054 &'a self,
18055 cx: &'a App,
18056 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18057 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18058 }
18059
18060 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18061 let cleared = self
18062 .display_map
18063 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18064 if cleared {
18065 cx.notify();
18066 }
18067 }
18068
18069 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18070 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18071 && self.focus_handle.is_focused(window)
18072 }
18073
18074 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18075 self.show_cursor_when_unfocused = is_enabled;
18076 cx.notify();
18077 }
18078
18079 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18080 cx.notify();
18081 }
18082
18083 fn on_debug_session_event(
18084 &mut self,
18085 _session: Entity<Session>,
18086 event: &SessionEvent,
18087 cx: &mut Context<Self>,
18088 ) {
18089 match event {
18090 SessionEvent::InvalidateInlineValue => {
18091 self.refresh_inline_values(cx);
18092 }
18093 _ => {}
18094 }
18095 }
18096
18097 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18098 let Some(project) = self.project.clone() else {
18099 return;
18100 };
18101
18102 if !self.inline_value_cache.enabled {
18103 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18104 self.splice_inlays(&inlays, Vec::new(), cx);
18105 return;
18106 }
18107
18108 let current_execution_position = self
18109 .highlighted_rows
18110 .get(&TypeId::of::<ActiveDebugLine>())
18111 .and_then(|lines| lines.last().map(|line| line.range.start));
18112
18113 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18114 let inline_values = editor
18115 .update(cx, |editor, cx| {
18116 let Some(current_execution_position) = current_execution_position else {
18117 return Some(Task::ready(Ok(Vec::new())));
18118 };
18119
18120 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18121 let snapshot = buffer.snapshot(cx);
18122
18123 let excerpt = snapshot.excerpt_containing(
18124 current_execution_position..current_execution_position,
18125 )?;
18126
18127 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18128 })?;
18129
18130 let range =
18131 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18132
18133 project.inline_values(buffer, range, cx)
18134 })
18135 .ok()
18136 .flatten()?
18137 .await
18138 .context("refreshing debugger inlays")
18139 .log_err()?;
18140
18141 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18142
18143 for (buffer_id, inline_value) in inline_values
18144 .into_iter()
18145 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18146 {
18147 buffer_inline_values
18148 .entry(buffer_id)
18149 .or_default()
18150 .push(inline_value);
18151 }
18152
18153 editor
18154 .update(cx, |editor, cx| {
18155 let snapshot = editor.buffer.read(cx).snapshot(cx);
18156 let mut new_inlays = Vec::default();
18157
18158 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18159 let buffer_id = buffer_snapshot.remote_id();
18160 buffer_inline_values
18161 .get(&buffer_id)
18162 .into_iter()
18163 .flatten()
18164 .for_each(|hint| {
18165 let inlay = Inlay::debugger_hint(
18166 post_inc(&mut editor.next_inlay_id),
18167 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18168 hint.text(),
18169 );
18170
18171 new_inlays.push(inlay);
18172 });
18173 }
18174
18175 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18176 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18177
18178 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18179 })
18180 .ok()?;
18181 Some(())
18182 });
18183 }
18184
18185 fn on_buffer_event(
18186 &mut self,
18187 multibuffer: &Entity<MultiBuffer>,
18188 event: &multi_buffer::Event,
18189 window: &mut Window,
18190 cx: &mut Context<Self>,
18191 ) {
18192 match event {
18193 multi_buffer::Event::Edited {
18194 singleton_buffer_edited,
18195 edited_buffer: buffer_edited,
18196 } => {
18197 self.scrollbar_marker_state.dirty = true;
18198 self.active_indent_guides_state.dirty = true;
18199 self.refresh_active_diagnostics(cx);
18200 self.refresh_code_actions(window, cx);
18201 self.refresh_selected_text_highlights(true, window, cx);
18202 refresh_matching_bracket_highlights(self, window, cx);
18203 if self.has_active_inline_completion() {
18204 self.update_visible_inline_completion(window, cx);
18205 }
18206 if let Some(buffer) = buffer_edited {
18207 let buffer_id = buffer.read(cx).remote_id();
18208 if !self.registered_buffers.contains_key(&buffer_id) {
18209 if let Some(project) = self.project.as_ref() {
18210 project.update(cx, |project, cx| {
18211 self.registered_buffers.insert(
18212 buffer_id,
18213 project.register_buffer_with_language_servers(&buffer, cx),
18214 );
18215 })
18216 }
18217 }
18218 }
18219 cx.emit(EditorEvent::BufferEdited);
18220 cx.emit(SearchEvent::MatchesInvalidated);
18221 if *singleton_buffer_edited {
18222 if let Some(project) = &self.project {
18223 #[allow(clippy::mutable_key_type)]
18224 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18225 multibuffer
18226 .all_buffers()
18227 .into_iter()
18228 .filter_map(|buffer| {
18229 buffer.update(cx, |buffer, cx| {
18230 let language = buffer.language()?;
18231 let should_discard = project.update(cx, |project, cx| {
18232 project.is_local()
18233 && !project.has_language_servers_for(buffer, cx)
18234 });
18235 should_discard.not().then_some(language.clone())
18236 })
18237 })
18238 .collect::<HashSet<_>>()
18239 });
18240 if !languages_affected.is_empty() {
18241 self.refresh_inlay_hints(
18242 InlayHintRefreshReason::BufferEdited(languages_affected),
18243 cx,
18244 );
18245 }
18246 }
18247 }
18248
18249 let Some(project) = &self.project else { return };
18250 let (telemetry, is_via_ssh) = {
18251 let project = project.read(cx);
18252 let telemetry = project.client().telemetry().clone();
18253 let is_via_ssh = project.is_via_ssh();
18254 (telemetry, is_via_ssh)
18255 };
18256 refresh_linked_ranges(self, window, cx);
18257 telemetry.log_edit_event("editor", is_via_ssh);
18258 }
18259 multi_buffer::Event::ExcerptsAdded {
18260 buffer,
18261 predecessor,
18262 excerpts,
18263 } => {
18264 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18265 let buffer_id = buffer.read(cx).remote_id();
18266 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18267 if let Some(project) = &self.project {
18268 update_uncommitted_diff_for_buffer(
18269 cx.entity(),
18270 project,
18271 [buffer.clone()],
18272 self.buffer.clone(),
18273 cx,
18274 )
18275 .detach();
18276 }
18277 }
18278 cx.emit(EditorEvent::ExcerptsAdded {
18279 buffer: buffer.clone(),
18280 predecessor: *predecessor,
18281 excerpts: excerpts.clone(),
18282 });
18283 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18284 }
18285 multi_buffer::Event::ExcerptsRemoved {
18286 ids,
18287 removed_buffer_ids,
18288 } => {
18289 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18290 let buffer = self.buffer.read(cx);
18291 self.registered_buffers
18292 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18293 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18294 cx.emit(EditorEvent::ExcerptsRemoved {
18295 ids: ids.clone(),
18296 removed_buffer_ids: removed_buffer_ids.clone(),
18297 })
18298 }
18299 multi_buffer::Event::ExcerptsEdited {
18300 excerpt_ids,
18301 buffer_ids,
18302 } => {
18303 self.display_map.update(cx, |map, cx| {
18304 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18305 });
18306 cx.emit(EditorEvent::ExcerptsEdited {
18307 ids: excerpt_ids.clone(),
18308 })
18309 }
18310 multi_buffer::Event::ExcerptsExpanded { ids } => {
18311 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18312 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18313 }
18314 multi_buffer::Event::Reparsed(buffer_id) => {
18315 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18316 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18317
18318 cx.emit(EditorEvent::Reparsed(*buffer_id));
18319 }
18320 multi_buffer::Event::DiffHunksToggled => {
18321 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18322 }
18323 multi_buffer::Event::LanguageChanged(buffer_id) => {
18324 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18325 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18326 cx.emit(EditorEvent::Reparsed(*buffer_id));
18327 cx.notify();
18328 }
18329 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18330 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18331 multi_buffer::Event::FileHandleChanged
18332 | multi_buffer::Event::Reloaded
18333 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18334 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18335 multi_buffer::Event::DiagnosticsUpdated => {
18336 self.refresh_active_diagnostics(cx);
18337 self.refresh_inline_diagnostics(true, window, cx);
18338 self.scrollbar_marker_state.dirty = true;
18339 cx.notify();
18340 }
18341 _ => {}
18342 };
18343 }
18344
18345 pub fn start_temporary_diff_override(&mut self) {
18346 self.load_diff_task.take();
18347 self.temporary_diff_override = true;
18348 }
18349
18350 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18351 self.temporary_diff_override = false;
18352 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18353 self.buffer.update(cx, |buffer, cx| {
18354 buffer.set_all_diff_hunks_collapsed(cx);
18355 });
18356
18357 if let Some(project) = self.project.clone() {
18358 self.load_diff_task = Some(
18359 update_uncommitted_diff_for_buffer(
18360 cx.entity(),
18361 &project,
18362 self.buffer.read(cx).all_buffers(),
18363 self.buffer.clone(),
18364 cx,
18365 )
18366 .shared(),
18367 );
18368 }
18369 }
18370
18371 fn on_display_map_changed(
18372 &mut self,
18373 _: Entity<DisplayMap>,
18374 _: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) {
18377 cx.notify();
18378 }
18379
18380 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18381 let new_severity = if self.diagnostics_enabled() {
18382 EditorSettings::get_global(cx)
18383 .diagnostics_max_severity
18384 .unwrap_or(DiagnosticSeverity::Hint)
18385 } else {
18386 DiagnosticSeverity::Off
18387 };
18388 self.set_max_diagnostics_severity(new_severity, cx);
18389 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18390 self.update_edit_prediction_settings(cx);
18391 self.refresh_inline_completion(true, false, window, cx);
18392 self.refresh_inlay_hints(
18393 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18394 self.selections.newest_anchor().head(),
18395 &self.buffer.read(cx).snapshot(cx),
18396 cx,
18397 )),
18398 cx,
18399 );
18400
18401 let old_cursor_shape = self.cursor_shape;
18402
18403 {
18404 let editor_settings = EditorSettings::get_global(cx);
18405 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18406 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18407 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18408 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18409 }
18410
18411 if old_cursor_shape != self.cursor_shape {
18412 cx.emit(EditorEvent::CursorShapeChanged);
18413 }
18414
18415 let project_settings = ProjectSettings::get_global(cx);
18416 self.serialize_dirty_buffers =
18417 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18418
18419 if self.mode.is_full() {
18420 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18421 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18422 if self.show_inline_diagnostics != show_inline_diagnostics {
18423 self.show_inline_diagnostics = show_inline_diagnostics;
18424 self.refresh_inline_diagnostics(false, window, cx);
18425 }
18426
18427 if self.git_blame_inline_enabled != inline_blame_enabled {
18428 self.toggle_git_blame_inline_internal(false, window, cx);
18429 }
18430
18431 let minimap_settings = EditorSettings::get_global(cx).minimap;
18432 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18433 self.set_minimap_visibility(
18434 self.minimap_visibility.toggle_visibility(),
18435 window,
18436 cx,
18437 );
18438 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18439 minimap_entity.update(cx, |minimap_editor, cx| {
18440 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18441 })
18442 }
18443 }
18444
18445 cx.notify();
18446 }
18447
18448 pub fn set_searchable(&mut self, searchable: bool) {
18449 self.searchable = searchable;
18450 }
18451
18452 pub fn searchable(&self) -> bool {
18453 self.searchable
18454 }
18455
18456 fn open_proposed_changes_editor(
18457 &mut self,
18458 _: &OpenProposedChangesEditor,
18459 window: &mut Window,
18460 cx: &mut Context<Self>,
18461 ) {
18462 let Some(workspace) = self.workspace() else {
18463 cx.propagate();
18464 return;
18465 };
18466
18467 let selections = self.selections.all::<usize>(cx);
18468 let multi_buffer = self.buffer.read(cx);
18469 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18470 let mut new_selections_by_buffer = HashMap::default();
18471 for selection in selections {
18472 for (buffer, range, _) in
18473 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18474 {
18475 let mut range = range.to_point(buffer);
18476 range.start.column = 0;
18477 range.end.column = buffer.line_len(range.end.row);
18478 new_selections_by_buffer
18479 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18480 .or_insert(Vec::new())
18481 .push(range)
18482 }
18483 }
18484
18485 let proposed_changes_buffers = new_selections_by_buffer
18486 .into_iter()
18487 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18488 .collect::<Vec<_>>();
18489 let proposed_changes_editor = cx.new(|cx| {
18490 ProposedChangesEditor::new(
18491 "Proposed changes",
18492 proposed_changes_buffers,
18493 self.project.clone(),
18494 window,
18495 cx,
18496 )
18497 });
18498
18499 window.defer(cx, move |window, cx| {
18500 workspace.update(cx, |workspace, cx| {
18501 workspace.active_pane().update(cx, |pane, cx| {
18502 pane.add_item(
18503 Box::new(proposed_changes_editor),
18504 true,
18505 true,
18506 None,
18507 window,
18508 cx,
18509 );
18510 });
18511 });
18512 });
18513 }
18514
18515 pub fn open_excerpts_in_split(
18516 &mut self,
18517 _: &OpenExcerptsSplit,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 self.open_excerpts_common(None, true, window, cx)
18522 }
18523
18524 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18525 self.open_excerpts_common(None, false, window, cx)
18526 }
18527
18528 fn open_excerpts_common(
18529 &mut self,
18530 jump_data: Option<JumpData>,
18531 split: bool,
18532 window: &mut Window,
18533 cx: &mut Context<Self>,
18534 ) {
18535 let Some(workspace) = self.workspace() else {
18536 cx.propagate();
18537 return;
18538 };
18539
18540 if self.buffer.read(cx).is_singleton() {
18541 cx.propagate();
18542 return;
18543 }
18544
18545 let mut new_selections_by_buffer = HashMap::default();
18546 match &jump_data {
18547 Some(JumpData::MultiBufferPoint {
18548 excerpt_id,
18549 position,
18550 anchor,
18551 line_offset_from_top,
18552 }) => {
18553 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18554 if let Some(buffer) = multi_buffer_snapshot
18555 .buffer_id_for_excerpt(*excerpt_id)
18556 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18557 {
18558 let buffer_snapshot = buffer.read(cx).snapshot();
18559 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18560 language::ToPoint::to_point(anchor, &buffer_snapshot)
18561 } else {
18562 buffer_snapshot.clip_point(*position, Bias::Left)
18563 };
18564 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18565 new_selections_by_buffer.insert(
18566 buffer,
18567 (
18568 vec![jump_to_offset..jump_to_offset],
18569 Some(*line_offset_from_top),
18570 ),
18571 );
18572 }
18573 }
18574 Some(JumpData::MultiBufferRow {
18575 row,
18576 line_offset_from_top,
18577 }) => {
18578 let point = MultiBufferPoint::new(row.0, 0);
18579 if let Some((buffer, buffer_point, _)) =
18580 self.buffer.read(cx).point_to_buffer_point(point, cx)
18581 {
18582 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18583 new_selections_by_buffer
18584 .entry(buffer)
18585 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18586 .0
18587 .push(buffer_offset..buffer_offset)
18588 }
18589 }
18590 None => {
18591 let selections = self.selections.all::<usize>(cx);
18592 let multi_buffer = self.buffer.read(cx);
18593 for selection in selections {
18594 for (snapshot, range, _, anchor) in multi_buffer
18595 .snapshot(cx)
18596 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18597 {
18598 if let Some(anchor) = anchor {
18599 // selection is in a deleted hunk
18600 let Some(buffer_id) = anchor.buffer_id else {
18601 continue;
18602 };
18603 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18604 continue;
18605 };
18606 let offset = text::ToOffset::to_offset(
18607 &anchor.text_anchor,
18608 &buffer_handle.read(cx).snapshot(),
18609 );
18610 let range = offset..offset;
18611 new_selections_by_buffer
18612 .entry(buffer_handle)
18613 .or_insert((Vec::new(), None))
18614 .0
18615 .push(range)
18616 } else {
18617 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18618 else {
18619 continue;
18620 };
18621 new_selections_by_buffer
18622 .entry(buffer_handle)
18623 .or_insert((Vec::new(), None))
18624 .0
18625 .push(range)
18626 }
18627 }
18628 }
18629 }
18630 }
18631
18632 new_selections_by_buffer
18633 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18634
18635 if new_selections_by_buffer.is_empty() {
18636 return;
18637 }
18638
18639 // We defer the pane interaction because we ourselves are a workspace item
18640 // and activating a new item causes the pane to call a method on us reentrantly,
18641 // which panics if we're on the stack.
18642 window.defer(cx, move |window, cx| {
18643 workspace.update(cx, |workspace, cx| {
18644 let pane = if split {
18645 workspace.adjacent_pane(window, cx)
18646 } else {
18647 workspace.active_pane().clone()
18648 };
18649
18650 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18651 let editor = buffer
18652 .read(cx)
18653 .file()
18654 .is_none()
18655 .then(|| {
18656 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18657 // so `workspace.open_project_item` will never find them, always opening a new editor.
18658 // Instead, we try to activate the existing editor in the pane first.
18659 let (editor, pane_item_index) =
18660 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18661 let editor = item.downcast::<Editor>()?;
18662 let singleton_buffer =
18663 editor.read(cx).buffer().read(cx).as_singleton()?;
18664 if singleton_buffer == buffer {
18665 Some((editor, i))
18666 } else {
18667 None
18668 }
18669 })?;
18670 pane.update(cx, |pane, cx| {
18671 pane.activate_item(pane_item_index, true, true, window, cx)
18672 });
18673 Some(editor)
18674 })
18675 .flatten()
18676 .unwrap_or_else(|| {
18677 workspace.open_project_item::<Self>(
18678 pane.clone(),
18679 buffer,
18680 true,
18681 true,
18682 window,
18683 cx,
18684 )
18685 });
18686
18687 editor.update(cx, |editor, cx| {
18688 let autoscroll = match scroll_offset {
18689 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18690 None => Autoscroll::newest(),
18691 };
18692 let nav_history = editor.nav_history.take();
18693 editor.change_selections(Some(autoscroll), window, cx, |s| {
18694 s.select_ranges(ranges);
18695 });
18696 editor.nav_history = nav_history;
18697 });
18698 }
18699 })
18700 });
18701 }
18702
18703 // For now, don't allow opening excerpts in buffers that aren't backed by
18704 // regular project files.
18705 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18706 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18707 }
18708
18709 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18710 let snapshot = self.buffer.read(cx).read(cx);
18711 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18712 Some(
18713 ranges
18714 .iter()
18715 .map(move |range| {
18716 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18717 })
18718 .collect(),
18719 )
18720 }
18721
18722 fn selection_replacement_ranges(
18723 &self,
18724 range: Range<OffsetUtf16>,
18725 cx: &mut App,
18726 ) -> Vec<Range<OffsetUtf16>> {
18727 let selections = self.selections.all::<OffsetUtf16>(cx);
18728 let newest_selection = selections
18729 .iter()
18730 .max_by_key(|selection| selection.id)
18731 .unwrap();
18732 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18733 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18734 let snapshot = self.buffer.read(cx).read(cx);
18735 selections
18736 .into_iter()
18737 .map(|mut selection| {
18738 selection.start.0 =
18739 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18740 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18741 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18742 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18743 })
18744 .collect()
18745 }
18746
18747 fn report_editor_event(
18748 &self,
18749 event_type: &'static str,
18750 file_extension: Option<String>,
18751 cx: &App,
18752 ) {
18753 if cfg!(any(test, feature = "test-support")) {
18754 return;
18755 }
18756
18757 let Some(project) = &self.project else { return };
18758
18759 // If None, we are in a file without an extension
18760 let file = self
18761 .buffer
18762 .read(cx)
18763 .as_singleton()
18764 .and_then(|b| b.read(cx).file());
18765 let file_extension = file_extension.or(file
18766 .as_ref()
18767 .and_then(|file| Path::new(file.file_name(cx)).extension())
18768 .and_then(|e| e.to_str())
18769 .map(|a| a.to_string()));
18770
18771 let vim_mode = vim_enabled(cx);
18772
18773 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18774 let copilot_enabled = edit_predictions_provider
18775 == language::language_settings::EditPredictionProvider::Copilot;
18776 let copilot_enabled_for_language = self
18777 .buffer
18778 .read(cx)
18779 .language_settings(cx)
18780 .show_edit_predictions;
18781
18782 let project = project.read(cx);
18783 telemetry::event!(
18784 event_type,
18785 file_extension,
18786 vim_mode,
18787 copilot_enabled,
18788 copilot_enabled_for_language,
18789 edit_predictions_provider,
18790 is_via_ssh = project.is_via_ssh(),
18791 );
18792 }
18793
18794 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18795 /// with each line being an array of {text, highlight} objects.
18796 fn copy_highlight_json(
18797 &mut self,
18798 _: &CopyHighlightJson,
18799 window: &mut Window,
18800 cx: &mut Context<Self>,
18801 ) {
18802 #[derive(Serialize)]
18803 struct Chunk<'a> {
18804 text: String,
18805 highlight: Option<&'a str>,
18806 }
18807
18808 let snapshot = self.buffer.read(cx).snapshot(cx);
18809 let range = self
18810 .selected_text_range(false, window, cx)
18811 .and_then(|selection| {
18812 if selection.range.is_empty() {
18813 None
18814 } else {
18815 Some(selection.range)
18816 }
18817 })
18818 .unwrap_or_else(|| 0..snapshot.len());
18819
18820 let chunks = snapshot.chunks(range, true);
18821 let mut lines = Vec::new();
18822 let mut line: VecDeque<Chunk> = VecDeque::new();
18823
18824 let Some(style) = self.style.as_ref() else {
18825 return;
18826 };
18827
18828 for chunk in chunks {
18829 let highlight = chunk
18830 .syntax_highlight_id
18831 .and_then(|id| id.name(&style.syntax));
18832 let mut chunk_lines = chunk.text.split('\n').peekable();
18833 while let Some(text) = chunk_lines.next() {
18834 let mut merged_with_last_token = false;
18835 if let Some(last_token) = line.back_mut() {
18836 if last_token.highlight == highlight {
18837 last_token.text.push_str(text);
18838 merged_with_last_token = true;
18839 }
18840 }
18841
18842 if !merged_with_last_token {
18843 line.push_back(Chunk {
18844 text: text.into(),
18845 highlight,
18846 });
18847 }
18848
18849 if chunk_lines.peek().is_some() {
18850 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18851 line.pop_front();
18852 }
18853 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18854 line.pop_back();
18855 }
18856
18857 lines.push(mem::take(&mut line));
18858 }
18859 }
18860 }
18861
18862 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18863 return;
18864 };
18865 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18866 }
18867
18868 pub fn open_context_menu(
18869 &mut self,
18870 _: &OpenContextMenu,
18871 window: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 self.request_autoscroll(Autoscroll::newest(), cx);
18875 let position = self.selections.newest_display(cx).start;
18876 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18877 }
18878
18879 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18880 &self.inlay_hint_cache
18881 }
18882
18883 pub fn replay_insert_event(
18884 &mut self,
18885 text: &str,
18886 relative_utf16_range: Option<Range<isize>>,
18887 window: &mut Window,
18888 cx: &mut Context<Self>,
18889 ) {
18890 if !self.input_enabled {
18891 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18892 return;
18893 }
18894 if let Some(relative_utf16_range) = relative_utf16_range {
18895 let selections = self.selections.all::<OffsetUtf16>(cx);
18896 self.change_selections(None, window, cx, |s| {
18897 let new_ranges = selections.into_iter().map(|range| {
18898 let start = OffsetUtf16(
18899 range
18900 .head()
18901 .0
18902 .saturating_add_signed(relative_utf16_range.start),
18903 );
18904 let end = OffsetUtf16(
18905 range
18906 .head()
18907 .0
18908 .saturating_add_signed(relative_utf16_range.end),
18909 );
18910 start..end
18911 });
18912 s.select_ranges(new_ranges);
18913 });
18914 }
18915
18916 self.handle_input(text, window, cx);
18917 }
18918
18919 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18920 let Some(provider) = self.semantics_provider.as_ref() else {
18921 return false;
18922 };
18923
18924 let mut supports = false;
18925 self.buffer().update(cx, |this, cx| {
18926 this.for_each_buffer(|buffer| {
18927 supports |= provider.supports_inlay_hints(buffer, cx);
18928 });
18929 });
18930
18931 supports
18932 }
18933
18934 pub fn is_focused(&self, window: &Window) -> bool {
18935 self.focus_handle.is_focused(window)
18936 }
18937
18938 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18939 cx.emit(EditorEvent::Focused);
18940
18941 if let Some(descendant) = self
18942 .last_focused_descendant
18943 .take()
18944 .and_then(|descendant| descendant.upgrade())
18945 {
18946 window.focus(&descendant);
18947 } else {
18948 if let Some(blame) = self.blame.as_ref() {
18949 blame.update(cx, GitBlame::focus)
18950 }
18951
18952 self.blink_manager.update(cx, BlinkManager::enable);
18953 self.show_cursor_names(window, cx);
18954 self.buffer.update(cx, |buffer, cx| {
18955 buffer.finalize_last_transaction(cx);
18956 if self.leader_id.is_none() {
18957 buffer.set_active_selections(
18958 &self.selections.disjoint_anchors(),
18959 self.selections.line_mode,
18960 self.cursor_shape,
18961 cx,
18962 );
18963 }
18964 });
18965 }
18966 }
18967
18968 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18969 cx.emit(EditorEvent::FocusedIn)
18970 }
18971
18972 fn handle_focus_out(
18973 &mut self,
18974 event: FocusOutEvent,
18975 _window: &mut Window,
18976 cx: &mut Context<Self>,
18977 ) {
18978 if event.blurred != self.focus_handle {
18979 self.last_focused_descendant = Some(event.blurred);
18980 }
18981 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18982 }
18983
18984 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18985 self.blink_manager.update(cx, BlinkManager::disable);
18986 self.buffer
18987 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18988
18989 if let Some(blame) = self.blame.as_ref() {
18990 blame.update(cx, GitBlame::blur)
18991 }
18992 if !self.hover_state.focused(window, cx) {
18993 hide_hover(self, cx);
18994 }
18995 if !self
18996 .context_menu
18997 .borrow()
18998 .as_ref()
18999 .is_some_and(|context_menu| context_menu.focused(window, cx))
19000 {
19001 self.hide_context_menu(window, cx);
19002 }
19003 self.discard_inline_completion(false, cx);
19004 cx.emit(EditorEvent::Blurred);
19005 cx.notify();
19006 }
19007
19008 pub fn register_action<A: Action>(
19009 &mut self,
19010 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19011 ) -> Subscription {
19012 let id = self.next_editor_action_id.post_inc();
19013 let listener = Arc::new(listener);
19014 self.editor_actions.borrow_mut().insert(
19015 id,
19016 Box::new(move |window, _| {
19017 let listener = listener.clone();
19018 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19019 let action = action.downcast_ref().unwrap();
19020 if phase == DispatchPhase::Bubble {
19021 listener(action, window, cx)
19022 }
19023 })
19024 }),
19025 );
19026
19027 let editor_actions = self.editor_actions.clone();
19028 Subscription::new(move || {
19029 editor_actions.borrow_mut().remove(&id);
19030 })
19031 }
19032
19033 pub fn file_header_size(&self) -> u32 {
19034 FILE_HEADER_HEIGHT
19035 }
19036
19037 pub fn restore(
19038 &mut self,
19039 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19040 window: &mut Window,
19041 cx: &mut Context<Self>,
19042 ) {
19043 let workspace = self.workspace();
19044 let project = self.project.as_ref();
19045 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19046 let mut tasks = Vec::new();
19047 for (buffer_id, changes) in revert_changes {
19048 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19049 buffer.update(cx, |buffer, cx| {
19050 buffer.edit(
19051 changes
19052 .into_iter()
19053 .map(|(range, text)| (range, text.to_string())),
19054 None,
19055 cx,
19056 );
19057 });
19058
19059 if let Some(project) =
19060 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19061 {
19062 project.update(cx, |project, cx| {
19063 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19064 })
19065 }
19066 }
19067 }
19068 tasks
19069 });
19070 cx.spawn_in(window, async move |_, cx| {
19071 for (buffer, task) in save_tasks {
19072 let result = task.await;
19073 if result.is_err() {
19074 let Some(path) = buffer
19075 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19076 .ok()
19077 else {
19078 continue;
19079 };
19080 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19081 let Some(task) = cx
19082 .update_window_entity(&workspace, |workspace, window, cx| {
19083 workspace
19084 .open_path_preview(path, None, false, false, false, window, cx)
19085 })
19086 .ok()
19087 else {
19088 continue;
19089 };
19090 task.await.log_err();
19091 }
19092 }
19093 }
19094 })
19095 .detach();
19096 self.change_selections(None, window, cx, |selections| selections.refresh());
19097 }
19098
19099 pub fn to_pixel_point(
19100 &self,
19101 source: multi_buffer::Anchor,
19102 editor_snapshot: &EditorSnapshot,
19103 window: &mut Window,
19104 ) -> Option<gpui::Point<Pixels>> {
19105 let source_point = source.to_display_point(editor_snapshot);
19106 self.display_to_pixel_point(source_point, editor_snapshot, window)
19107 }
19108
19109 pub fn display_to_pixel_point(
19110 &self,
19111 source: DisplayPoint,
19112 editor_snapshot: &EditorSnapshot,
19113 window: &mut Window,
19114 ) -> Option<gpui::Point<Pixels>> {
19115 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19116 let text_layout_details = self.text_layout_details(window);
19117 let scroll_top = text_layout_details
19118 .scroll_anchor
19119 .scroll_position(editor_snapshot)
19120 .y;
19121
19122 if source.row().as_f32() < scroll_top.floor() {
19123 return None;
19124 }
19125 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19126 let source_y = line_height * (source.row().as_f32() - scroll_top);
19127 Some(gpui::Point::new(source_x, source_y))
19128 }
19129
19130 pub fn has_visible_completions_menu(&self) -> bool {
19131 !self.edit_prediction_preview_is_active()
19132 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19133 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19134 })
19135 }
19136
19137 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19138 if self.mode.is_minimap() {
19139 return;
19140 }
19141 self.addons
19142 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19143 }
19144
19145 pub fn unregister_addon<T: Addon>(&mut self) {
19146 self.addons.remove(&std::any::TypeId::of::<T>());
19147 }
19148
19149 pub fn addon<T: Addon>(&self) -> Option<&T> {
19150 let type_id = std::any::TypeId::of::<T>();
19151 self.addons
19152 .get(&type_id)
19153 .and_then(|item| item.to_any().downcast_ref::<T>())
19154 }
19155
19156 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19157 let type_id = std::any::TypeId::of::<T>();
19158 self.addons
19159 .get_mut(&type_id)
19160 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19161 }
19162
19163 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19164 let text_layout_details = self.text_layout_details(window);
19165 let style = &text_layout_details.editor_style;
19166 let font_id = window.text_system().resolve_font(&style.text.font());
19167 let font_size = style.text.font_size.to_pixels(window.rem_size());
19168 let line_height = style.text.line_height_in_pixels(window.rem_size());
19169 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19170
19171 gpui::Size::new(em_width, line_height)
19172 }
19173
19174 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19175 self.load_diff_task.clone()
19176 }
19177
19178 fn read_metadata_from_db(
19179 &mut self,
19180 item_id: u64,
19181 workspace_id: WorkspaceId,
19182 window: &mut Window,
19183 cx: &mut Context<Editor>,
19184 ) {
19185 if self.is_singleton(cx)
19186 && !self.mode.is_minimap()
19187 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19188 {
19189 let buffer_snapshot = OnceCell::new();
19190
19191 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19192 if !folds.is_empty() {
19193 let snapshot =
19194 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19195 self.fold_ranges(
19196 folds
19197 .into_iter()
19198 .map(|(start, end)| {
19199 snapshot.clip_offset(start, Bias::Left)
19200 ..snapshot.clip_offset(end, Bias::Right)
19201 })
19202 .collect(),
19203 false,
19204 window,
19205 cx,
19206 );
19207 }
19208 }
19209
19210 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19211 if !selections.is_empty() {
19212 let snapshot =
19213 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19214 self.change_selections(None, window, cx, |s| {
19215 s.select_ranges(selections.into_iter().map(|(start, end)| {
19216 snapshot.clip_offset(start, Bias::Left)
19217 ..snapshot.clip_offset(end, Bias::Right)
19218 }));
19219 });
19220 }
19221 };
19222 }
19223
19224 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19225 }
19226}
19227
19228fn vim_enabled(cx: &App) -> bool {
19229 cx.global::<SettingsStore>()
19230 .raw_user_settings()
19231 .get("vim_mode")
19232 == Some(&serde_json::Value::Bool(true))
19233}
19234
19235// Consider user intent and default settings
19236fn choose_completion_range(
19237 completion: &Completion,
19238 intent: CompletionIntent,
19239 buffer: &Entity<Buffer>,
19240 cx: &mut Context<Editor>,
19241) -> Range<usize> {
19242 fn should_replace(
19243 completion: &Completion,
19244 insert_range: &Range<text::Anchor>,
19245 intent: CompletionIntent,
19246 completion_mode_setting: LspInsertMode,
19247 buffer: &Buffer,
19248 ) -> bool {
19249 // specific actions take precedence over settings
19250 match intent {
19251 CompletionIntent::CompleteWithInsert => return false,
19252 CompletionIntent::CompleteWithReplace => return true,
19253 CompletionIntent::Complete | CompletionIntent::Compose => {}
19254 }
19255
19256 match completion_mode_setting {
19257 LspInsertMode::Insert => false,
19258 LspInsertMode::Replace => true,
19259 LspInsertMode::ReplaceSubsequence => {
19260 let mut text_to_replace = buffer.chars_for_range(
19261 buffer.anchor_before(completion.replace_range.start)
19262 ..buffer.anchor_after(completion.replace_range.end),
19263 );
19264 let mut completion_text = completion.new_text.chars();
19265
19266 // is `text_to_replace` a subsequence of `completion_text`
19267 text_to_replace
19268 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19269 }
19270 LspInsertMode::ReplaceSuffix => {
19271 let range_after_cursor = insert_range.end..completion.replace_range.end;
19272
19273 let text_after_cursor = buffer
19274 .text_for_range(
19275 buffer.anchor_before(range_after_cursor.start)
19276 ..buffer.anchor_after(range_after_cursor.end),
19277 )
19278 .collect::<String>();
19279 completion.new_text.ends_with(&text_after_cursor)
19280 }
19281 }
19282 }
19283
19284 let buffer = buffer.read(cx);
19285
19286 if let CompletionSource::Lsp {
19287 insert_range: Some(insert_range),
19288 ..
19289 } = &completion.source
19290 {
19291 let completion_mode_setting =
19292 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19293 .completions
19294 .lsp_insert_mode;
19295
19296 if !should_replace(
19297 completion,
19298 &insert_range,
19299 intent,
19300 completion_mode_setting,
19301 buffer,
19302 ) {
19303 return insert_range.to_offset(buffer);
19304 }
19305 }
19306
19307 completion.replace_range.to_offset(buffer)
19308}
19309
19310fn insert_extra_newline_brackets(
19311 buffer: &MultiBufferSnapshot,
19312 range: Range<usize>,
19313 language: &language::LanguageScope,
19314) -> bool {
19315 let leading_whitespace_len = buffer
19316 .reversed_chars_at(range.start)
19317 .take_while(|c| c.is_whitespace() && *c != '\n')
19318 .map(|c| c.len_utf8())
19319 .sum::<usize>();
19320 let trailing_whitespace_len = buffer
19321 .chars_at(range.end)
19322 .take_while(|c| c.is_whitespace() && *c != '\n')
19323 .map(|c| c.len_utf8())
19324 .sum::<usize>();
19325 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19326
19327 language.brackets().any(|(pair, enabled)| {
19328 let pair_start = pair.start.trim_end();
19329 let pair_end = pair.end.trim_start();
19330
19331 enabled
19332 && pair.newline
19333 && buffer.contains_str_at(range.end, pair_end)
19334 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19335 })
19336}
19337
19338fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19339 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19340 [(buffer, range, _)] => (*buffer, range.clone()),
19341 _ => return false,
19342 };
19343 let pair = {
19344 let mut result: Option<BracketMatch> = None;
19345
19346 for pair in buffer
19347 .all_bracket_ranges(range.clone())
19348 .filter(move |pair| {
19349 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19350 })
19351 {
19352 let len = pair.close_range.end - pair.open_range.start;
19353
19354 if let Some(existing) = &result {
19355 let existing_len = existing.close_range.end - existing.open_range.start;
19356 if len > existing_len {
19357 continue;
19358 }
19359 }
19360
19361 result = Some(pair);
19362 }
19363
19364 result
19365 };
19366 let Some(pair) = pair else {
19367 return false;
19368 };
19369 pair.newline_only
19370 && buffer
19371 .chars_for_range(pair.open_range.end..range.start)
19372 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19373 .all(|c| c.is_whitespace() && c != '\n')
19374}
19375
19376fn update_uncommitted_diff_for_buffer(
19377 editor: Entity<Editor>,
19378 project: &Entity<Project>,
19379 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19380 buffer: Entity<MultiBuffer>,
19381 cx: &mut App,
19382) -> Task<()> {
19383 let mut tasks = Vec::new();
19384 project.update(cx, |project, cx| {
19385 for buffer in buffers {
19386 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19387 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19388 }
19389 }
19390 });
19391 cx.spawn(async move |cx| {
19392 let diffs = future::join_all(tasks).await;
19393 if editor
19394 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19395 .unwrap_or(false)
19396 {
19397 return;
19398 }
19399
19400 buffer
19401 .update(cx, |buffer, cx| {
19402 for diff in diffs.into_iter().flatten() {
19403 buffer.add_diff(diff, cx);
19404 }
19405 })
19406 .ok();
19407 })
19408}
19409
19410fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19411 let tab_size = tab_size.get() as usize;
19412 let mut width = offset;
19413
19414 for ch in text.chars() {
19415 width += if ch == '\t' {
19416 tab_size - (width % tab_size)
19417 } else {
19418 1
19419 };
19420 }
19421
19422 width - offset
19423}
19424
19425#[cfg(test)]
19426mod tests {
19427 use super::*;
19428
19429 #[test]
19430 fn test_string_size_with_expanded_tabs() {
19431 let nz = |val| NonZeroU32::new(val).unwrap();
19432 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19433 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19434 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19435 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19436 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19437 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19438 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19439 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19440 }
19441}
19442
19443/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19444struct WordBreakingTokenizer<'a> {
19445 input: &'a str,
19446}
19447
19448impl<'a> WordBreakingTokenizer<'a> {
19449 fn new(input: &'a str) -> Self {
19450 Self { input }
19451 }
19452}
19453
19454fn is_char_ideographic(ch: char) -> bool {
19455 use unicode_script::Script::*;
19456 use unicode_script::UnicodeScript;
19457 matches!(ch.script(), Han | Tangut | Yi)
19458}
19459
19460fn is_grapheme_ideographic(text: &str) -> bool {
19461 text.chars().any(is_char_ideographic)
19462}
19463
19464fn is_grapheme_whitespace(text: &str) -> bool {
19465 text.chars().any(|x| x.is_whitespace())
19466}
19467
19468fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19469 text.chars().next().map_or(false, |ch| {
19470 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19471 })
19472}
19473
19474#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19475enum WordBreakToken<'a> {
19476 Word { token: &'a str, grapheme_len: usize },
19477 InlineWhitespace { token: &'a str, grapheme_len: usize },
19478 Newline,
19479}
19480
19481impl<'a> Iterator for WordBreakingTokenizer<'a> {
19482 /// Yields a span, the count of graphemes in the token, and whether it was
19483 /// whitespace. Note that it also breaks at word boundaries.
19484 type Item = WordBreakToken<'a>;
19485
19486 fn next(&mut self) -> Option<Self::Item> {
19487 use unicode_segmentation::UnicodeSegmentation;
19488 if self.input.is_empty() {
19489 return None;
19490 }
19491
19492 let mut iter = self.input.graphemes(true).peekable();
19493 let mut offset = 0;
19494 let mut grapheme_len = 0;
19495 if let Some(first_grapheme) = iter.next() {
19496 let is_newline = first_grapheme == "\n";
19497 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19498 offset += first_grapheme.len();
19499 grapheme_len += 1;
19500 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19501 if let Some(grapheme) = iter.peek().copied() {
19502 if should_stay_with_preceding_ideograph(grapheme) {
19503 offset += grapheme.len();
19504 grapheme_len += 1;
19505 }
19506 }
19507 } else {
19508 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19509 let mut next_word_bound = words.peek().copied();
19510 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19511 next_word_bound = words.next();
19512 }
19513 while let Some(grapheme) = iter.peek().copied() {
19514 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19515 break;
19516 };
19517 if is_grapheme_whitespace(grapheme) != is_whitespace
19518 || (grapheme == "\n") != is_newline
19519 {
19520 break;
19521 };
19522 offset += grapheme.len();
19523 grapheme_len += 1;
19524 iter.next();
19525 }
19526 }
19527 let token = &self.input[..offset];
19528 self.input = &self.input[offset..];
19529 if token == "\n" {
19530 Some(WordBreakToken::Newline)
19531 } else if is_whitespace {
19532 Some(WordBreakToken::InlineWhitespace {
19533 token,
19534 grapheme_len,
19535 })
19536 } else {
19537 Some(WordBreakToken::Word {
19538 token,
19539 grapheme_len,
19540 })
19541 }
19542 } else {
19543 None
19544 }
19545 }
19546}
19547
19548#[test]
19549fn test_word_breaking_tokenizer() {
19550 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19551 ("", &[]),
19552 (" ", &[whitespace(" ", 2)]),
19553 ("Ʒ", &[word("Ʒ", 1)]),
19554 ("Ǽ", &[word("Ǽ", 1)]),
19555 ("⋑", &[word("⋑", 1)]),
19556 ("⋑⋑", &[word("⋑⋑", 2)]),
19557 (
19558 "原理,进而",
19559 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19560 ),
19561 (
19562 "hello world",
19563 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19564 ),
19565 (
19566 "hello, world",
19567 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19568 ),
19569 (
19570 " hello world",
19571 &[
19572 whitespace(" ", 2),
19573 word("hello", 5),
19574 whitespace(" ", 1),
19575 word("world", 5),
19576 ],
19577 ),
19578 (
19579 "这是什么 \n 钢笔",
19580 &[
19581 word("这", 1),
19582 word("是", 1),
19583 word("什", 1),
19584 word("么", 1),
19585 whitespace(" ", 1),
19586 newline(),
19587 whitespace(" ", 1),
19588 word("钢", 1),
19589 word("笔", 1),
19590 ],
19591 ),
19592 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19593 ];
19594
19595 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19596 WordBreakToken::Word {
19597 token,
19598 grapheme_len,
19599 }
19600 }
19601
19602 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19603 WordBreakToken::InlineWhitespace {
19604 token,
19605 grapheme_len,
19606 }
19607 }
19608
19609 fn newline() -> WordBreakToken<'static> {
19610 WordBreakToken::Newline
19611 }
19612
19613 for (input, result) in tests {
19614 assert_eq!(
19615 WordBreakingTokenizer::new(input)
19616 .collect::<Vec<_>>()
19617 .as_slice(),
19618 *result,
19619 );
19620 }
19621}
19622
19623fn wrap_with_prefix(
19624 line_prefix: String,
19625 unwrapped_text: String,
19626 wrap_column: usize,
19627 tab_size: NonZeroU32,
19628 preserve_existing_whitespace: bool,
19629) -> String {
19630 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19631 let mut wrapped_text = String::new();
19632 let mut current_line = line_prefix.clone();
19633
19634 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19635 let mut current_line_len = line_prefix_len;
19636 let mut in_whitespace = false;
19637 for token in tokenizer {
19638 let have_preceding_whitespace = in_whitespace;
19639 match token {
19640 WordBreakToken::Word {
19641 token,
19642 grapheme_len,
19643 } => {
19644 in_whitespace = false;
19645 if current_line_len + grapheme_len > wrap_column
19646 && current_line_len != line_prefix_len
19647 {
19648 wrapped_text.push_str(current_line.trim_end());
19649 wrapped_text.push('\n');
19650 current_line.truncate(line_prefix.len());
19651 current_line_len = line_prefix_len;
19652 }
19653 current_line.push_str(token);
19654 current_line_len += grapheme_len;
19655 }
19656 WordBreakToken::InlineWhitespace {
19657 mut token,
19658 mut grapheme_len,
19659 } => {
19660 in_whitespace = true;
19661 if have_preceding_whitespace && !preserve_existing_whitespace {
19662 continue;
19663 }
19664 if !preserve_existing_whitespace {
19665 token = " ";
19666 grapheme_len = 1;
19667 }
19668 if current_line_len + grapheme_len > wrap_column {
19669 wrapped_text.push_str(current_line.trim_end());
19670 wrapped_text.push('\n');
19671 current_line.truncate(line_prefix.len());
19672 current_line_len = line_prefix_len;
19673 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19674 current_line.push_str(token);
19675 current_line_len += grapheme_len;
19676 }
19677 }
19678 WordBreakToken::Newline => {
19679 in_whitespace = true;
19680 if preserve_existing_whitespace {
19681 wrapped_text.push_str(current_line.trim_end());
19682 wrapped_text.push('\n');
19683 current_line.truncate(line_prefix.len());
19684 current_line_len = line_prefix_len;
19685 } else if have_preceding_whitespace {
19686 continue;
19687 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19688 {
19689 wrapped_text.push_str(current_line.trim_end());
19690 wrapped_text.push('\n');
19691 current_line.truncate(line_prefix.len());
19692 current_line_len = line_prefix_len;
19693 } else if current_line_len != line_prefix_len {
19694 current_line.push(' ');
19695 current_line_len += 1;
19696 }
19697 }
19698 }
19699 }
19700
19701 if !current_line.is_empty() {
19702 wrapped_text.push_str(¤t_line);
19703 }
19704 wrapped_text
19705}
19706
19707#[test]
19708fn test_wrap_with_prefix() {
19709 assert_eq!(
19710 wrap_with_prefix(
19711 "# ".to_string(),
19712 "abcdefg".to_string(),
19713 4,
19714 NonZeroU32::new(4).unwrap(),
19715 false,
19716 ),
19717 "# abcdefg"
19718 );
19719 assert_eq!(
19720 wrap_with_prefix(
19721 "".to_string(),
19722 "\thello world".to_string(),
19723 8,
19724 NonZeroU32::new(4).unwrap(),
19725 false,
19726 ),
19727 "hello\nworld"
19728 );
19729 assert_eq!(
19730 wrap_with_prefix(
19731 "// ".to_string(),
19732 "xx \nyy zz aa bb cc".to_string(),
19733 12,
19734 NonZeroU32::new(4).unwrap(),
19735 false,
19736 ),
19737 "// xx yy zz\n// aa bb cc"
19738 );
19739 assert_eq!(
19740 wrap_with_prefix(
19741 String::new(),
19742 "这是什么 \n 钢笔".to_string(),
19743 3,
19744 NonZeroU32::new(4).unwrap(),
19745 false,
19746 ),
19747 "这是什\n么 钢\n笔"
19748 );
19749}
19750
19751pub trait CollaborationHub {
19752 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19753 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19754 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19755}
19756
19757impl CollaborationHub for Entity<Project> {
19758 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19759 self.read(cx).collaborators()
19760 }
19761
19762 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19763 self.read(cx).user_store().read(cx).participant_indices()
19764 }
19765
19766 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19767 let this = self.read(cx);
19768 let user_ids = this.collaborators().values().map(|c| c.user_id);
19769 this.user_store().read_with(cx, |user_store, cx| {
19770 user_store.participant_names(user_ids, cx)
19771 })
19772 }
19773}
19774
19775pub trait SemanticsProvider {
19776 fn hover(
19777 &self,
19778 buffer: &Entity<Buffer>,
19779 position: text::Anchor,
19780 cx: &mut App,
19781 ) -> Option<Task<Vec<project::Hover>>>;
19782
19783 fn inline_values(
19784 &self,
19785 buffer_handle: Entity<Buffer>,
19786 range: Range<text::Anchor>,
19787 cx: &mut App,
19788 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19789
19790 fn inlay_hints(
19791 &self,
19792 buffer_handle: Entity<Buffer>,
19793 range: Range<text::Anchor>,
19794 cx: &mut App,
19795 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19796
19797 fn resolve_inlay_hint(
19798 &self,
19799 hint: InlayHint,
19800 buffer_handle: Entity<Buffer>,
19801 server_id: LanguageServerId,
19802 cx: &mut App,
19803 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19804
19805 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19806
19807 fn document_highlights(
19808 &self,
19809 buffer: &Entity<Buffer>,
19810 position: text::Anchor,
19811 cx: &mut App,
19812 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19813
19814 fn definitions(
19815 &self,
19816 buffer: &Entity<Buffer>,
19817 position: text::Anchor,
19818 kind: GotoDefinitionKind,
19819 cx: &mut App,
19820 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19821
19822 fn range_for_rename(
19823 &self,
19824 buffer: &Entity<Buffer>,
19825 position: text::Anchor,
19826 cx: &mut App,
19827 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19828
19829 fn perform_rename(
19830 &self,
19831 buffer: &Entity<Buffer>,
19832 position: text::Anchor,
19833 new_name: String,
19834 cx: &mut App,
19835 ) -> Option<Task<Result<ProjectTransaction>>>;
19836}
19837
19838pub trait CompletionProvider {
19839 fn completions(
19840 &self,
19841 excerpt_id: ExcerptId,
19842 buffer: &Entity<Buffer>,
19843 buffer_position: text::Anchor,
19844 trigger: CompletionContext,
19845 window: &mut Window,
19846 cx: &mut Context<Editor>,
19847 ) -> Task<Result<Option<Vec<Completion>>>>;
19848
19849 fn resolve_completions(
19850 &self,
19851 buffer: Entity<Buffer>,
19852 completion_indices: Vec<usize>,
19853 completions: Rc<RefCell<Box<[Completion]>>>,
19854 cx: &mut Context<Editor>,
19855 ) -> Task<Result<bool>>;
19856
19857 fn apply_additional_edits_for_completion(
19858 &self,
19859 _buffer: Entity<Buffer>,
19860 _completions: Rc<RefCell<Box<[Completion]>>>,
19861 _completion_index: usize,
19862 _push_to_history: bool,
19863 _cx: &mut Context<Editor>,
19864 ) -> Task<Result<Option<language::Transaction>>> {
19865 Task::ready(Ok(None))
19866 }
19867
19868 fn is_completion_trigger(
19869 &self,
19870 buffer: &Entity<Buffer>,
19871 position: language::Anchor,
19872 text: &str,
19873 trigger_in_words: bool,
19874 cx: &mut Context<Editor>,
19875 ) -> bool;
19876
19877 fn sort_completions(&self) -> bool {
19878 true
19879 }
19880
19881 fn filter_completions(&self) -> bool {
19882 true
19883 }
19884}
19885
19886pub trait CodeActionProvider {
19887 fn id(&self) -> Arc<str>;
19888
19889 fn code_actions(
19890 &self,
19891 buffer: &Entity<Buffer>,
19892 range: Range<text::Anchor>,
19893 window: &mut Window,
19894 cx: &mut App,
19895 ) -> Task<Result<Vec<CodeAction>>>;
19896
19897 fn apply_code_action(
19898 &self,
19899 buffer_handle: Entity<Buffer>,
19900 action: CodeAction,
19901 excerpt_id: ExcerptId,
19902 push_to_history: bool,
19903 window: &mut Window,
19904 cx: &mut App,
19905 ) -> Task<Result<ProjectTransaction>>;
19906}
19907
19908impl CodeActionProvider for Entity<Project> {
19909 fn id(&self) -> Arc<str> {
19910 "project".into()
19911 }
19912
19913 fn code_actions(
19914 &self,
19915 buffer: &Entity<Buffer>,
19916 range: Range<text::Anchor>,
19917 _window: &mut Window,
19918 cx: &mut App,
19919 ) -> Task<Result<Vec<CodeAction>>> {
19920 self.update(cx, |project, cx| {
19921 let code_lens = project.code_lens(buffer, range.clone(), cx);
19922 let code_actions = project.code_actions(buffer, range, None, cx);
19923 cx.background_spawn(async move {
19924 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19925 Ok(code_lens
19926 .context("code lens fetch")?
19927 .into_iter()
19928 .chain(code_actions.context("code action fetch")?)
19929 .collect())
19930 })
19931 })
19932 }
19933
19934 fn apply_code_action(
19935 &self,
19936 buffer_handle: Entity<Buffer>,
19937 action: CodeAction,
19938 _excerpt_id: ExcerptId,
19939 push_to_history: bool,
19940 _window: &mut Window,
19941 cx: &mut App,
19942 ) -> Task<Result<ProjectTransaction>> {
19943 self.update(cx, |project, cx| {
19944 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19945 })
19946 }
19947}
19948
19949fn snippet_completions(
19950 project: &Project,
19951 buffer: &Entity<Buffer>,
19952 buffer_position: text::Anchor,
19953 cx: &mut App,
19954) -> Task<Result<Vec<Completion>>> {
19955 let languages = buffer.read(cx).languages_at(buffer_position);
19956 let snippet_store = project.snippets().read(cx);
19957
19958 let scopes: Vec<_> = languages
19959 .iter()
19960 .filter_map(|language| {
19961 let language_name = language.lsp_id();
19962 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19963
19964 if snippets.is_empty() {
19965 None
19966 } else {
19967 Some((language.default_scope(), snippets))
19968 }
19969 })
19970 .collect();
19971
19972 if scopes.is_empty() {
19973 return Task::ready(Ok(vec![]));
19974 }
19975
19976 let snapshot = buffer.read(cx).text_snapshot();
19977 let chars: String = snapshot
19978 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19979 .collect();
19980 let executor = cx.background_executor().clone();
19981
19982 cx.background_spawn(async move {
19983 let mut all_results: Vec<Completion> = Vec::new();
19984 for (scope, snippets) in scopes.into_iter() {
19985 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19986 let mut last_word = chars
19987 .chars()
19988 .take_while(|c| classifier.is_word(*c))
19989 .collect::<String>();
19990 last_word = last_word.chars().rev().collect();
19991
19992 if last_word.is_empty() {
19993 return Ok(vec![]);
19994 }
19995
19996 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19997 let to_lsp = |point: &text::Anchor| {
19998 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19999 point_to_lsp(end)
20000 };
20001 let lsp_end = to_lsp(&buffer_position);
20002
20003 let candidates = snippets
20004 .iter()
20005 .enumerate()
20006 .flat_map(|(ix, snippet)| {
20007 snippet
20008 .prefix
20009 .iter()
20010 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20011 })
20012 .collect::<Vec<StringMatchCandidate>>();
20013
20014 let mut matches = fuzzy::match_strings(
20015 &candidates,
20016 &last_word,
20017 last_word.chars().any(|c| c.is_uppercase()),
20018 100,
20019 &Default::default(),
20020 executor.clone(),
20021 )
20022 .await;
20023
20024 // Remove all candidates where the query's start does not match the start of any word in the candidate
20025 if let Some(query_start) = last_word.chars().next() {
20026 matches.retain(|string_match| {
20027 split_words(&string_match.string).any(|word| {
20028 // Check that the first codepoint of the word as lowercase matches the first
20029 // codepoint of the query as lowercase
20030 word.chars()
20031 .flat_map(|codepoint| codepoint.to_lowercase())
20032 .zip(query_start.to_lowercase())
20033 .all(|(word_cp, query_cp)| word_cp == query_cp)
20034 })
20035 });
20036 }
20037
20038 let matched_strings = matches
20039 .into_iter()
20040 .map(|m| m.string)
20041 .collect::<HashSet<_>>();
20042
20043 let mut result: Vec<Completion> = snippets
20044 .iter()
20045 .filter_map(|snippet| {
20046 let matching_prefix = snippet
20047 .prefix
20048 .iter()
20049 .find(|prefix| matched_strings.contains(*prefix))?;
20050 let start = as_offset - last_word.len();
20051 let start = snapshot.anchor_before(start);
20052 let range = start..buffer_position;
20053 let lsp_start = to_lsp(&start);
20054 let lsp_range = lsp::Range {
20055 start: lsp_start,
20056 end: lsp_end,
20057 };
20058 Some(Completion {
20059 replace_range: range,
20060 new_text: snippet.body.clone(),
20061 source: CompletionSource::Lsp {
20062 insert_range: None,
20063 server_id: LanguageServerId(usize::MAX),
20064 resolved: true,
20065 lsp_completion: Box::new(lsp::CompletionItem {
20066 label: snippet.prefix.first().unwrap().clone(),
20067 kind: Some(CompletionItemKind::SNIPPET),
20068 label_details: snippet.description.as_ref().map(|description| {
20069 lsp::CompletionItemLabelDetails {
20070 detail: Some(description.clone()),
20071 description: None,
20072 }
20073 }),
20074 insert_text_format: Some(InsertTextFormat::SNIPPET),
20075 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20076 lsp::InsertReplaceEdit {
20077 new_text: snippet.body.clone(),
20078 insert: lsp_range,
20079 replace: lsp_range,
20080 },
20081 )),
20082 filter_text: Some(snippet.body.clone()),
20083 sort_text: Some(char::MAX.to_string()),
20084 ..lsp::CompletionItem::default()
20085 }),
20086 lsp_defaults: None,
20087 },
20088 label: CodeLabel {
20089 text: matching_prefix.clone(),
20090 runs: Vec::new(),
20091 filter_range: 0..matching_prefix.len(),
20092 },
20093 icon_path: None,
20094 documentation: Some(
20095 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20096 single_line: snippet.name.clone().into(),
20097 plain_text: snippet
20098 .description
20099 .clone()
20100 .map(|description| description.into()),
20101 },
20102 ),
20103 insert_text_mode: None,
20104 confirm: None,
20105 })
20106 })
20107 .collect();
20108
20109 all_results.append(&mut result);
20110 }
20111
20112 Ok(all_results)
20113 })
20114}
20115
20116impl CompletionProvider for Entity<Project> {
20117 fn completions(
20118 &self,
20119 _excerpt_id: ExcerptId,
20120 buffer: &Entity<Buffer>,
20121 buffer_position: text::Anchor,
20122 options: CompletionContext,
20123 _window: &mut Window,
20124 cx: &mut Context<Editor>,
20125 ) -> Task<Result<Option<Vec<Completion>>>> {
20126 self.update(cx, |project, cx| {
20127 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20128 let project_completions = project.completions(buffer, buffer_position, options, cx);
20129 cx.background_spawn(async move {
20130 let snippets_completions = snippets.await?;
20131 match project_completions.await? {
20132 Some(mut completions) => {
20133 completions.extend(snippets_completions);
20134 Ok(Some(completions))
20135 }
20136 None => {
20137 if snippets_completions.is_empty() {
20138 Ok(None)
20139 } else {
20140 Ok(Some(snippets_completions))
20141 }
20142 }
20143 }
20144 })
20145 })
20146 }
20147
20148 fn resolve_completions(
20149 &self,
20150 buffer: Entity<Buffer>,
20151 completion_indices: Vec<usize>,
20152 completions: Rc<RefCell<Box<[Completion]>>>,
20153 cx: &mut Context<Editor>,
20154 ) -> Task<Result<bool>> {
20155 self.update(cx, |project, cx| {
20156 project.lsp_store().update(cx, |lsp_store, cx| {
20157 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20158 })
20159 })
20160 }
20161
20162 fn apply_additional_edits_for_completion(
20163 &self,
20164 buffer: Entity<Buffer>,
20165 completions: Rc<RefCell<Box<[Completion]>>>,
20166 completion_index: usize,
20167 push_to_history: bool,
20168 cx: &mut Context<Editor>,
20169 ) -> Task<Result<Option<language::Transaction>>> {
20170 self.update(cx, |project, cx| {
20171 project.lsp_store().update(cx, |lsp_store, cx| {
20172 lsp_store.apply_additional_edits_for_completion(
20173 buffer,
20174 completions,
20175 completion_index,
20176 push_to_history,
20177 cx,
20178 )
20179 })
20180 })
20181 }
20182
20183 fn is_completion_trigger(
20184 &self,
20185 buffer: &Entity<Buffer>,
20186 position: language::Anchor,
20187 text: &str,
20188 trigger_in_words: bool,
20189 cx: &mut Context<Editor>,
20190 ) -> bool {
20191 let mut chars = text.chars();
20192 let char = if let Some(char) = chars.next() {
20193 char
20194 } else {
20195 return false;
20196 };
20197 if chars.next().is_some() {
20198 return false;
20199 }
20200
20201 let buffer = buffer.read(cx);
20202 let snapshot = buffer.snapshot();
20203 if !snapshot.settings_at(position, cx).show_completions_on_input {
20204 return false;
20205 }
20206 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20207 if trigger_in_words && classifier.is_word(char) {
20208 return true;
20209 }
20210
20211 buffer.completion_triggers().contains(text)
20212 }
20213}
20214
20215impl SemanticsProvider for Entity<Project> {
20216 fn hover(
20217 &self,
20218 buffer: &Entity<Buffer>,
20219 position: text::Anchor,
20220 cx: &mut App,
20221 ) -> Option<Task<Vec<project::Hover>>> {
20222 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20223 }
20224
20225 fn document_highlights(
20226 &self,
20227 buffer: &Entity<Buffer>,
20228 position: text::Anchor,
20229 cx: &mut App,
20230 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20231 Some(self.update(cx, |project, cx| {
20232 project.document_highlights(buffer, position, cx)
20233 }))
20234 }
20235
20236 fn definitions(
20237 &self,
20238 buffer: &Entity<Buffer>,
20239 position: text::Anchor,
20240 kind: GotoDefinitionKind,
20241 cx: &mut App,
20242 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20243 Some(self.update(cx, |project, cx| match kind {
20244 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20245 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20246 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20247 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20248 }))
20249 }
20250
20251 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20252 // TODO: make this work for remote projects
20253 self.update(cx, |project, cx| {
20254 if project
20255 .active_debug_session(cx)
20256 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20257 {
20258 return true;
20259 }
20260
20261 buffer.update(cx, |buffer, cx| {
20262 project.any_language_server_supports_inlay_hints(buffer, cx)
20263 })
20264 })
20265 }
20266
20267 fn inline_values(
20268 &self,
20269 buffer_handle: Entity<Buffer>,
20270
20271 range: Range<text::Anchor>,
20272 cx: &mut App,
20273 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20274 self.update(cx, |project, cx| {
20275 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20276
20277 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20278 })
20279 }
20280
20281 fn inlay_hints(
20282 &self,
20283 buffer_handle: Entity<Buffer>,
20284 range: Range<text::Anchor>,
20285 cx: &mut App,
20286 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20287 Some(self.update(cx, |project, cx| {
20288 project.inlay_hints(buffer_handle, range, cx)
20289 }))
20290 }
20291
20292 fn resolve_inlay_hint(
20293 &self,
20294 hint: InlayHint,
20295 buffer_handle: Entity<Buffer>,
20296 server_id: LanguageServerId,
20297 cx: &mut App,
20298 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20299 Some(self.update(cx, |project, cx| {
20300 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20301 }))
20302 }
20303
20304 fn range_for_rename(
20305 &self,
20306 buffer: &Entity<Buffer>,
20307 position: text::Anchor,
20308 cx: &mut App,
20309 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20310 Some(self.update(cx, |project, cx| {
20311 let buffer = buffer.clone();
20312 let task = project.prepare_rename(buffer.clone(), position, cx);
20313 cx.spawn(async move |_, cx| {
20314 Ok(match task.await? {
20315 PrepareRenameResponse::Success(range) => Some(range),
20316 PrepareRenameResponse::InvalidPosition => None,
20317 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20318 // Fallback on using TreeSitter info to determine identifier range
20319 buffer.update(cx, |buffer, _| {
20320 let snapshot = buffer.snapshot();
20321 let (range, kind) = snapshot.surrounding_word(position);
20322 if kind != Some(CharKind::Word) {
20323 return None;
20324 }
20325 Some(
20326 snapshot.anchor_before(range.start)
20327 ..snapshot.anchor_after(range.end),
20328 )
20329 })?
20330 }
20331 })
20332 })
20333 }))
20334 }
20335
20336 fn perform_rename(
20337 &self,
20338 buffer: &Entity<Buffer>,
20339 position: text::Anchor,
20340 new_name: String,
20341 cx: &mut App,
20342 ) -> Option<Task<Result<ProjectTransaction>>> {
20343 Some(self.update(cx, |project, cx| {
20344 project.perform_rename(buffer.clone(), position, new_name, cx)
20345 }))
20346 }
20347}
20348
20349fn inlay_hint_settings(
20350 location: Anchor,
20351 snapshot: &MultiBufferSnapshot,
20352 cx: &mut Context<Editor>,
20353) -> InlayHintSettings {
20354 let file = snapshot.file_at(location);
20355 let language = snapshot.language_at(location).map(|l| l.name());
20356 language_settings(language, file, cx).inlay_hints
20357}
20358
20359fn consume_contiguous_rows(
20360 contiguous_row_selections: &mut Vec<Selection<Point>>,
20361 selection: &Selection<Point>,
20362 display_map: &DisplaySnapshot,
20363 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20364) -> (MultiBufferRow, MultiBufferRow) {
20365 contiguous_row_selections.push(selection.clone());
20366 let start_row = MultiBufferRow(selection.start.row);
20367 let mut end_row = ending_row(selection, display_map);
20368
20369 while let Some(next_selection) = selections.peek() {
20370 if next_selection.start.row <= end_row.0 {
20371 end_row = ending_row(next_selection, display_map);
20372 contiguous_row_selections.push(selections.next().unwrap().clone());
20373 } else {
20374 break;
20375 }
20376 }
20377 (start_row, end_row)
20378}
20379
20380fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20381 if next_selection.end.column > 0 || next_selection.is_empty() {
20382 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20383 } else {
20384 MultiBufferRow(next_selection.end.row)
20385 }
20386}
20387
20388impl EditorSnapshot {
20389 pub fn remote_selections_in_range<'a>(
20390 &'a self,
20391 range: &'a Range<Anchor>,
20392 collaboration_hub: &dyn CollaborationHub,
20393 cx: &'a App,
20394 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20395 let participant_names = collaboration_hub.user_names(cx);
20396 let participant_indices = collaboration_hub.user_participant_indices(cx);
20397 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20398 let collaborators_by_replica_id = collaborators_by_peer_id
20399 .values()
20400 .map(|collaborator| (collaborator.replica_id, collaborator))
20401 .collect::<HashMap<_, _>>();
20402 self.buffer_snapshot
20403 .selections_in_range(range, false)
20404 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20405 if replica_id == AGENT_REPLICA_ID {
20406 Some(RemoteSelection {
20407 replica_id,
20408 selection,
20409 cursor_shape,
20410 line_mode,
20411 collaborator_id: CollaboratorId::Agent,
20412 user_name: Some("Agent".into()),
20413 color: cx.theme().players().agent(),
20414 })
20415 } else {
20416 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20417 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20418 let user_name = participant_names.get(&collaborator.user_id).cloned();
20419 Some(RemoteSelection {
20420 replica_id,
20421 selection,
20422 cursor_shape,
20423 line_mode,
20424 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20425 user_name,
20426 color: if let Some(index) = participant_index {
20427 cx.theme().players().color_for_participant(index.0)
20428 } else {
20429 cx.theme().players().absent()
20430 },
20431 })
20432 }
20433 })
20434 }
20435
20436 pub fn hunks_for_ranges(
20437 &self,
20438 ranges: impl IntoIterator<Item = Range<Point>>,
20439 ) -> Vec<MultiBufferDiffHunk> {
20440 let mut hunks = Vec::new();
20441 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20442 HashMap::default();
20443 for query_range in ranges {
20444 let query_rows =
20445 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20446 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20447 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20448 ) {
20449 // Include deleted hunks that are adjacent to the query range, because
20450 // otherwise they would be missed.
20451 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20452 if hunk.status().is_deleted() {
20453 intersects_range |= hunk.row_range.start == query_rows.end;
20454 intersects_range |= hunk.row_range.end == query_rows.start;
20455 }
20456 if intersects_range {
20457 if !processed_buffer_rows
20458 .entry(hunk.buffer_id)
20459 .or_default()
20460 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20461 {
20462 continue;
20463 }
20464 hunks.push(hunk);
20465 }
20466 }
20467 }
20468
20469 hunks
20470 }
20471
20472 fn display_diff_hunks_for_rows<'a>(
20473 &'a self,
20474 display_rows: Range<DisplayRow>,
20475 folded_buffers: &'a HashSet<BufferId>,
20476 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20477 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20478 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20479
20480 self.buffer_snapshot
20481 .diff_hunks_in_range(buffer_start..buffer_end)
20482 .filter_map(|hunk| {
20483 if folded_buffers.contains(&hunk.buffer_id) {
20484 return None;
20485 }
20486
20487 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20488 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20489
20490 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20491 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20492
20493 let display_hunk = if hunk_display_start.column() != 0 {
20494 DisplayDiffHunk::Folded {
20495 display_row: hunk_display_start.row(),
20496 }
20497 } else {
20498 let mut end_row = hunk_display_end.row();
20499 if hunk_display_end.column() > 0 {
20500 end_row.0 += 1;
20501 }
20502 let is_created_file = hunk.is_created_file();
20503 DisplayDiffHunk::Unfolded {
20504 status: hunk.status(),
20505 diff_base_byte_range: hunk.diff_base_byte_range,
20506 display_row_range: hunk_display_start.row()..end_row,
20507 multi_buffer_range: Anchor::range_in_buffer(
20508 hunk.excerpt_id,
20509 hunk.buffer_id,
20510 hunk.buffer_range,
20511 ),
20512 is_created_file,
20513 }
20514 };
20515
20516 Some(display_hunk)
20517 })
20518 }
20519
20520 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20521 self.display_snapshot.buffer_snapshot.language_at(position)
20522 }
20523
20524 pub fn is_focused(&self) -> bool {
20525 self.is_focused
20526 }
20527
20528 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20529 self.placeholder_text.as_ref()
20530 }
20531
20532 pub fn scroll_position(&self) -> gpui::Point<f32> {
20533 self.scroll_anchor.scroll_position(&self.display_snapshot)
20534 }
20535
20536 fn gutter_dimensions(
20537 &self,
20538 font_id: FontId,
20539 font_size: Pixels,
20540 max_line_number_width: Pixels,
20541 cx: &App,
20542 ) -> Option<GutterDimensions> {
20543 if !self.show_gutter {
20544 return None;
20545 }
20546
20547 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20548 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20549
20550 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20551 matches!(
20552 ProjectSettings::get_global(cx).git.git_gutter,
20553 Some(GitGutterSetting::TrackedFiles)
20554 )
20555 });
20556 let gutter_settings = EditorSettings::get_global(cx).gutter;
20557 let show_line_numbers = self
20558 .show_line_numbers
20559 .unwrap_or(gutter_settings.line_numbers);
20560 let line_gutter_width = if show_line_numbers {
20561 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20562 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20563 max_line_number_width.max(min_width_for_number_on_gutter)
20564 } else {
20565 0.0.into()
20566 };
20567
20568 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20569 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20570
20571 let git_blame_entries_width =
20572 self.git_blame_gutter_max_author_length
20573 .map(|max_author_length| {
20574 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20575 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20576
20577 /// The number of characters to dedicate to gaps and margins.
20578 const SPACING_WIDTH: usize = 4;
20579
20580 let max_char_count = max_author_length.min(renderer.max_author_length())
20581 + ::git::SHORT_SHA_LENGTH
20582 + MAX_RELATIVE_TIMESTAMP.len()
20583 + SPACING_WIDTH;
20584
20585 em_advance * max_char_count
20586 });
20587
20588 let is_singleton = self.buffer_snapshot.is_singleton();
20589
20590 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20591 left_padding += if !is_singleton {
20592 em_width * 4.0
20593 } else if show_runnables || show_breakpoints {
20594 em_width * 3.0
20595 } else if show_git_gutter && show_line_numbers {
20596 em_width * 2.0
20597 } else if show_git_gutter || show_line_numbers {
20598 em_width
20599 } else {
20600 px(0.)
20601 };
20602
20603 let shows_folds = is_singleton && gutter_settings.folds;
20604
20605 let right_padding = if shows_folds && show_line_numbers {
20606 em_width * 4.0
20607 } else if shows_folds || (!is_singleton && show_line_numbers) {
20608 em_width * 3.0
20609 } else if show_line_numbers {
20610 em_width
20611 } else {
20612 px(0.)
20613 };
20614
20615 Some(GutterDimensions {
20616 left_padding,
20617 right_padding,
20618 width: line_gutter_width + left_padding + right_padding,
20619 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20620 git_blame_entries_width,
20621 })
20622 }
20623
20624 pub fn render_crease_toggle(
20625 &self,
20626 buffer_row: MultiBufferRow,
20627 row_contains_cursor: bool,
20628 editor: Entity<Editor>,
20629 window: &mut Window,
20630 cx: &mut App,
20631 ) -> Option<AnyElement> {
20632 let folded = self.is_line_folded(buffer_row);
20633 let mut is_foldable = false;
20634
20635 if let Some(crease) = self
20636 .crease_snapshot
20637 .query_row(buffer_row, &self.buffer_snapshot)
20638 {
20639 is_foldable = true;
20640 match crease {
20641 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20642 if let Some(render_toggle) = render_toggle {
20643 let toggle_callback =
20644 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20645 if folded {
20646 editor.update(cx, |editor, cx| {
20647 editor.fold_at(buffer_row, window, cx)
20648 });
20649 } else {
20650 editor.update(cx, |editor, cx| {
20651 editor.unfold_at(buffer_row, window, cx)
20652 });
20653 }
20654 });
20655 return Some((render_toggle)(
20656 buffer_row,
20657 folded,
20658 toggle_callback,
20659 window,
20660 cx,
20661 ));
20662 }
20663 }
20664 }
20665 }
20666
20667 is_foldable |= self.starts_indent(buffer_row);
20668
20669 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20670 Some(
20671 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20672 .toggle_state(folded)
20673 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20674 if folded {
20675 this.unfold_at(buffer_row, window, cx);
20676 } else {
20677 this.fold_at(buffer_row, window, cx);
20678 }
20679 }))
20680 .into_any_element(),
20681 )
20682 } else {
20683 None
20684 }
20685 }
20686
20687 pub fn render_crease_trailer(
20688 &self,
20689 buffer_row: MultiBufferRow,
20690 window: &mut Window,
20691 cx: &mut App,
20692 ) -> Option<AnyElement> {
20693 let folded = self.is_line_folded(buffer_row);
20694 if let Crease::Inline { render_trailer, .. } = self
20695 .crease_snapshot
20696 .query_row(buffer_row, &self.buffer_snapshot)?
20697 {
20698 let render_trailer = render_trailer.as_ref()?;
20699 Some(render_trailer(buffer_row, folded, window, cx))
20700 } else {
20701 None
20702 }
20703 }
20704}
20705
20706impl Deref for EditorSnapshot {
20707 type Target = DisplaySnapshot;
20708
20709 fn deref(&self) -> &Self::Target {
20710 &self.display_snapshot
20711 }
20712}
20713
20714#[derive(Clone, Debug, PartialEq, Eq)]
20715pub enum EditorEvent {
20716 InputIgnored {
20717 text: Arc<str>,
20718 },
20719 InputHandled {
20720 utf16_range_to_replace: Option<Range<isize>>,
20721 text: Arc<str>,
20722 },
20723 ExcerptsAdded {
20724 buffer: Entity<Buffer>,
20725 predecessor: ExcerptId,
20726 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20727 },
20728 ExcerptsRemoved {
20729 ids: Vec<ExcerptId>,
20730 removed_buffer_ids: Vec<BufferId>,
20731 },
20732 BufferFoldToggled {
20733 ids: Vec<ExcerptId>,
20734 folded: bool,
20735 },
20736 ExcerptsEdited {
20737 ids: Vec<ExcerptId>,
20738 },
20739 ExcerptsExpanded {
20740 ids: Vec<ExcerptId>,
20741 },
20742 BufferEdited,
20743 Edited {
20744 transaction_id: clock::Lamport,
20745 },
20746 Reparsed(BufferId),
20747 Focused,
20748 FocusedIn,
20749 Blurred,
20750 DirtyChanged,
20751 Saved,
20752 TitleChanged,
20753 DiffBaseChanged,
20754 SelectionsChanged {
20755 local: bool,
20756 },
20757 ScrollPositionChanged {
20758 local: bool,
20759 autoscroll: bool,
20760 },
20761 Closed,
20762 TransactionUndone {
20763 transaction_id: clock::Lamport,
20764 },
20765 TransactionBegun {
20766 transaction_id: clock::Lamport,
20767 },
20768 Reloaded,
20769 CursorShapeChanged,
20770 PushedToNavHistory {
20771 anchor: Anchor,
20772 is_deactivate: bool,
20773 },
20774}
20775
20776impl EventEmitter<EditorEvent> for Editor {}
20777
20778impl Focusable for Editor {
20779 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20780 self.focus_handle.clone()
20781 }
20782}
20783
20784impl Render for Editor {
20785 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20786 let settings = ThemeSettings::get_global(cx);
20787
20788 let mut text_style = match self.mode {
20789 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20790 color: cx.theme().colors().editor_foreground,
20791 font_family: settings.ui_font.family.clone(),
20792 font_features: settings.ui_font.features.clone(),
20793 font_fallbacks: settings.ui_font.fallbacks.clone(),
20794 font_size: rems(0.875).into(),
20795 font_weight: settings.ui_font.weight,
20796 line_height: relative(settings.buffer_line_height.value()),
20797 ..Default::default()
20798 },
20799 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20800 color: cx.theme().colors().editor_foreground,
20801 font_family: settings.buffer_font.family.clone(),
20802 font_features: settings.buffer_font.features.clone(),
20803 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20804 font_size: settings.buffer_font_size(cx).into(),
20805 font_weight: settings.buffer_font.weight,
20806 line_height: relative(settings.buffer_line_height.value()),
20807 ..Default::default()
20808 },
20809 };
20810 if let Some(text_style_refinement) = &self.text_style_refinement {
20811 text_style.refine(text_style_refinement)
20812 }
20813
20814 let background = match self.mode {
20815 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20816 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20817 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20818 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20819 };
20820
20821 EditorElement::new(
20822 &cx.entity(),
20823 EditorStyle {
20824 background,
20825 local_player: cx.theme().players().local(),
20826 text: text_style,
20827 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20828 syntax: cx.theme().syntax().clone(),
20829 status: cx.theme().status().clone(),
20830 inlay_hints_style: make_inlay_hints_style(cx),
20831 inline_completion_styles: make_suggestion_styles(cx),
20832 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20833 show_underlines: !self.mode.is_minimap(),
20834 },
20835 )
20836 }
20837}
20838
20839impl EntityInputHandler for Editor {
20840 fn text_for_range(
20841 &mut self,
20842 range_utf16: Range<usize>,
20843 adjusted_range: &mut Option<Range<usize>>,
20844 _: &mut Window,
20845 cx: &mut Context<Self>,
20846 ) -> Option<String> {
20847 let snapshot = self.buffer.read(cx).read(cx);
20848 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20849 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20850 if (start.0..end.0) != range_utf16 {
20851 adjusted_range.replace(start.0..end.0);
20852 }
20853 Some(snapshot.text_for_range(start..end).collect())
20854 }
20855
20856 fn selected_text_range(
20857 &mut self,
20858 ignore_disabled_input: bool,
20859 _: &mut Window,
20860 cx: &mut Context<Self>,
20861 ) -> Option<UTF16Selection> {
20862 // Prevent the IME menu from appearing when holding down an alphabetic key
20863 // while input is disabled.
20864 if !ignore_disabled_input && !self.input_enabled {
20865 return None;
20866 }
20867
20868 let selection = self.selections.newest::<OffsetUtf16>(cx);
20869 let range = selection.range();
20870
20871 Some(UTF16Selection {
20872 range: range.start.0..range.end.0,
20873 reversed: selection.reversed,
20874 })
20875 }
20876
20877 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20878 let snapshot = self.buffer.read(cx).read(cx);
20879 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20880 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20881 }
20882
20883 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20884 self.clear_highlights::<InputComposition>(cx);
20885 self.ime_transaction.take();
20886 }
20887
20888 fn replace_text_in_range(
20889 &mut self,
20890 range_utf16: Option<Range<usize>>,
20891 text: &str,
20892 window: &mut Window,
20893 cx: &mut Context<Self>,
20894 ) {
20895 if !self.input_enabled {
20896 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20897 return;
20898 }
20899
20900 self.transact(window, cx, |this, window, cx| {
20901 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20902 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20903 Some(this.selection_replacement_ranges(range_utf16, cx))
20904 } else {
20905 this.marked_text_ranges(cx)
20906 };
20907
20908 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20909 let newest_selection_id = this.selections.newest_anchor().id;
20910 this.selections
20911 .all::<OffsetUtf16>(cx)
20912 .iter()
20913 .zip(ranges_to_replace.iter())
20914 .find_map(|(selection, range)| {
20915 if selection.id == newest_selection_id {
20916 Some(
20917 (range.start.0 as isize - selection.head().0 as isize)
20918 ..(range.end.0 as isize - selection.head().0 as isize),
20919 )
20920 } else {
20921 None
20922 }
20923 })
20924 });
20925
20926 cx.emit(EditorEvent::InputHandled {
20927 utf16_range_to_replace: range_to_replace,
20928 text: text.into(),
20929 });
20930
20931 if let Some(new_selected_ranges) = new_selected_ranges {
20932 this.change_selections(None, window, cx, |selections| {
20933 selections.select_ranges(new_selected_ranges)
20934 });
20935 this.backspace(&Default::default(), window, cx);
20936 }
20937
20938 this.handle_input(text, window, cx);
20939 });
20940
20941 if let Some(transaction) = self.ime_transaction {
20942 self.buffer.update(cx, |buffer, cx| {
20943 buffer.group_until_transaction(transaction, cx);
20944 });
20945 }
20946
20947 self.unmark_text(window, cx);
20948 }
20949
20950 fn replace_and_mark_text_in_range(
20951 &mut self,
20952 range_utf16: Option<Range<usize>>,
20953 text: &str,
20954 new_selected_range_utf16: Option<Range<usize>>,
20955 window: &mut Window,
20956 cx: &mut Context<Self>,
20957 ) {
20958 if !self.input_enabled {
20959 return;
20960 }
20961
20962 let transaction = self.transact(window, cx, |this, window, cx| {
20963 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20964 let snapshot = this.buffer.read(cx).read(cx);
20965 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20966 for marked_range in &mut marked_ranges {
20967 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20968 marked_range.start.0 += relative_range_utf16.start;
20969 marked_range.start =
20970 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20971 marked_range.end =
20972 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20973 }
20974 }
20975 Some(marked_ranges)
20976 } else if let Some(range_utf16) = range_utf16 {
20977 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20978 Some(this.selection_replacement_ranges(range_utf16, cx))
20979 } else {
20980 None
20981 };
20982
20983 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20984 let newest_selection_id = this.selections.newest_anchor().id;
20985 this.selections
20986 .all::<OffsetUtf16>(cx)
20987 .iter()
20988 .zip(ranges_to_replace.iter())
20989 .find_map(|(selection, range)| {
20990 if selection.id == newest_selection_id {
20991 Some(
20992 (range.start.0 as isize - selection.head().0 as isize)
20993 ..(range.end.0 as isize - selection.head().0 as isize),
20994 )
20995 } else {
20996 None
20997 }
20998 })
20999 });
21000
21001 cx.emit(EditorEvent::InputHandled {
21002 utf16_range_to_replace: range_to_replace,
21003 text: text.into(),
21004 });
21005
21006 if let Some(ranges) = ranges_to_replace {
21007 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21008 }
21009
21010 let marked_ranges = {
21011 let snapshot = this.buffer.read(cx).read(cx);
21012 this.selections
21013 .disjoint_anchors()
21014 .iter()
21015 .map(|selection| {
21016 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21017 })
21018 .collect::<Vec<_>>()
21019 };
21020
21021 if text.is_empty() {
21022 this.unmark_text(window, cx);
21023 } else {
21024 this.highlight_text::<InputComposition>(
21025 marked_ranges.clone(),
21026 HighlightStyle {
21027 underline: Some(UnderlineStyle {
21028 thickness: px(1.),
21029 color: None,
21030 wavy: false,
21031 }),
21032 ..Default::default()
21033 },
21034 cx,
21035 );
21036 }
21037
21038 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21039 let use_autoclose = this.use_autoclose;
21040 let use_auto_surround = this.use_auto_surround;
21041 this.set_use_autoclose(false);
21042 this.set_use_auto_surround(false);
21043 this.handle_input(text, window, cx);
21044 this.set_use_autoclose(use_autoclose);
21045 this.set_use_auto_surround(use_auto_surround);
21046
21047 if let Some(new_selected_range) = new_selected_range_utf16 {
21048 let snapshot = this.buffer.read(cx).read(cx);
21049 let new_selected_ranges = marked_ranges
21050 .into_iter()
21051 .map(|marked_range| {
21052 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21053 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21054 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21055 snapshot.clip_offset_utf16(new_start, Bias::Left)
21056 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21057 })
21058 .collect::<Vec<_>>();
21059
21060 drop(snapshot);
21061 this.change_selections(None, window, cx, |selections| {
21062 selections.select_ranges(new_selected_ranges)
21063 });
21064 }
21065 });
21066
21067 self.ime_transaction = self.ime_transaction.or(transaction);
21068 if let Some(transaction) = self.ime_transaction {
21069 self.buffer.update(cx, |buffer, cx| {
21070 buffer.group_until_transaction(transaction, cx);
21071 });
21072 }
21073
21074 if self.text_highlights::<InputComposition>(cx).is_none() {
21075 self.ime_transaction.take();
21076 }
21077 }
21078
21079 fn bounds_for_range(
21080 &mut self,
21081 range_utf16: Range<usize>,
21082 element_bounds: gpui::Bounds<Pixels>,
21083 window: &mut Window,
21084 cx: &mut Context<Self>,
21085 ) -> Option<gpui::Bounds<Pixels>> {
21086 let text_layout_details = self.text_layout_details(window);
21087 let gpui::Size {
21088 width: em_width,
21089 height: line_height,
21090 } = self.character_size(window);
21091
21092 let snapshot = self.snapshot(window, cx);
21093 let scroll_position = snapshot.scroll_position();
21094 let scroll_left = scroll_position.x * em_width;
21095
21096 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21097 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21098 + self.gutter_dimensions.width
21099 + self.gutter_dimensions.margin;
21100 let y = line_height * (start.row().as_f32() - scroll_position.y);
21101
21102 Some(Bounds {
21103 origin: element_bounds.origin + point(x, y),
21104 size: size(em_width, line_height),
21105 })
21106 }
21107
21108 fn character_index_for_point(
21109 &mut self,
21110 point: gpui::Point<Pixels>,
21111 _window: &mut Window,
21112 _cx: &mut Context<Self>,
21113 ) -> Option<usize> {
21114 let position_map = self.last_position_map.as_ref()?;
21115 if !position_map.text_hitbox.contains(&point) {
21116 return None;
21117 }
21118 let display_point = position_map.point_for_position(point).previous_valid;
21119 let anchor = position_map
21120 .snapshot
21121 .display_point_to_anchor(display_point, Bias::Left);
21122 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21123 Some(utf16_offset.0)
21124 }
21125}
21126
21127trait SelectionExt {
21128 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21129 fn spanned_rows(
21130 &self,
21131 include_end_if_at_line_start: bool,
21132 map: &DisplaySnapshot,
21133 ) -> Range<MultiBufferRow>;
21134}
21135
21136impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21137 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21138 let start = self
21139 .start
21140 .to_point(&map.buffer_snapshot)
21141 .to_display_point(map);
21142 let end = self
21143 .end
21144 .to_point(&map.buffer_snapshot)
21145 .to_display_point(map);
21146 if self.reversed {
21147 end..start
21148 } else {
21149 start..end
21150 }
21151 }
21152
21153 fn spanned_rows(
21154 &self,
21155 include_end_if_at_line_start: bool,
21156 map: &DisplaySnapshot,
21157 ) -> Range<MultiBufferRow> {
21158 let start = self.start.to_point(&map.buffer_snapshot);
21159 let mut end = self.end.to_point(&map.buffer_snapshot);
21160 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21161 end.row -= 1;
21162 }
21163
21164 let buffer_start = map.prev_line_boundary(start).0;
21165 let buffer_end = map.next_line_boundary(end).0;
21166 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21167 }
21168}
21169
21170impl<T: InvalidationRegion> InvalidationStack<T> {
21171 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21172 where
21173 S: Clone + ToOffset,
21174 {
21175 while let Some(region) = self.last() {
21176 let all_selections_inside_invalidation_ranges =
21177 if selections.len() == region.ranges().len() {
21178 selections
21179 .iter()
21180 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21181 .all(|(selection, invalidation_range)| {
21182 let head = selection.head().to_offset(buffer);
21183 invalidation_range.start <= head && invalidation_range.end >= head
21184 })
21185 } else {
21186 false
21187 };
21188
21189 if all_selections_inside_invalidation_ranges {
21190 break;
21191 } else {
21192 self.pop();
21193 }
21194 }
21195 }
21196}
21197
21198impl<T> Default for InvalidationStack<T> {
21199 fn default() -> Self {
21200 Self(Default::default())
21201 }
21202}
21203
21204impl<T> Deref for InvalidationStack<T> {
21205 type Target = Vec<T>;
21206
21207 fn deref(&self) -> &Self::Target {
21208 &self.0
21209 }
21210}
21211
21212impl<T> DerefMut for InvalidationStack<T> {
21213 fn deref_mut(&mut self) -> &mut Self::Target {
21214 &mut self.0
21215 }
21216}
21217
21218impl InvalidationRegion for SnippetState {
21219 fn ranges(&self) -> &[Range<Anchor>] {
21220 &self.ranges[self.active_index]
21221 }
21222}
21223
21224fn inline_completion_edit_text(
21225 current_snapshot: &BufferSnapshot,
21226 edits: &[(Range<Anchor>, String)],
21227 edit_preview: &EditPreview,
21228 include_deletions: bool,
21229 cx: &App,
21230) -> HighlightedText {
21231 let edits = edits
21232 .iter()
21233 .map(|(anchor, text)| {
21234 (
21235 anchor.start.text_anchor..anchor.end.text_anchor,
21236 text.clone(),
21237 )
21238 })
21239 .collect::<Vec<_>>();
21240
21241 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21242}
21243
21244pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21245 match severity {
21246 lsp::DiagnosticSeverity::ERROR => colors.error,
21247 lsp::DiagnosticSeverity::WARNING => colors.warning,
21248 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21249 lsp::DiagnosticSeverity::HINT => colors.info,
21250 _ => colors.ignored,
21251 }
21252}
21253
21254pub fn styled_runs_for_code_label<'a>(
21255 label: &'a CodeLabel,
21256 syntax_theme: &'a theme::SyntaxTheme,
21257) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21258 let fade_out = HighlightStyle {
21259 fade_out: Some(0.35),
21260 ..Default::default()
21261 };
21262
21263 let mut prev_end = label.filter_range.end;
21264 label
21265 .runs
21266 .iter()
21267 .enumerate()
21268 .flat_map(move |(ix, (range, highlight_id))| {
21269 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21270 style
21271 } else {
21272 return Default::default();
21273 };
21274 let mut muted_style = style;
21275 muted_style.highlight(fade_out);
21276
21277 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21278 if range.start >= label.filter_range.end {
21279 if range.start > prev_end {
21280 runs.push((prev_end..range.start, fade_out));
21281 }
21282 runs.push((range.clone(), muted_style));
21283 } else if range.end <= label.filter_range.end {
21284 runs.push((range.clone(), style));
21285 } else {
21286 runs.push((range.start..label.filter_range.end, style));
21287 runs.push((label.filter_range.end..range.end, muted_style));
21288 }
21289 prev_end = cmp::max(prev_end, range.end);
21290
21291 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21292 runs.push((prev_end..label.text.len(), fade_out));
21293 }
21294
21295 runs
21296 })
21297}
21298
21299pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21300 let mut prev_index = 0;
21301 let mut prev_codepoint: Option<char> = None;
21302 text.char_indices()
21303 .chain([(text.len(), '\0')])
21304 .filter_map(move |(index, codepoint)| {
21305 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21306 let is_boundary = index == text.len()
21307 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21308 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21309 if is_boundary {
21310 let chunk = &text[prev_index..index];
21311 prev_index = index;
21312 Some(chunk)
21313 } else {
21314 None
21315 }
21316 })
21317}
21318
21319pub trait RangeToAnchorExt: Sized {
21320 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21321
21322 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21323 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21324 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21325 }
21326}
21327
21328impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21329 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21330 let start_offset = self.start.to_offset(snapshot);
21331 let end_offset = self.end.to_offset(snapshot);
21332 if start_offset == end_offset {
21333 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21334 } else {
21335 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21336 }
21337 }
21338}
21339
21340pub trait RowExt {
21341 fn as_f32(&self) -> f32;
21342
21343 fn next_row(&self) -> Self;
21344
21345 fn previous_row(&self) -> Self;
21346
21347 fn minus(&self, other: Self) -> u32;
21348}
21349
21350impl RowExt for DisplayRow {
21351 fn as_f32(&self) -> f32 {
21352 self.0 as f32
21353 }
21354
21355 fn next_row(&self) -> Self {
21356 Self(self.0 + 1)
21357 }
21358
21359 fn previous_row(&self) -> Self {
21360 Self(self.0.saturating_sub(1))
21361 }
21362
21363 fn minus(&self, other: Self) -> u32 {
21364 self.0 - other.0
21365 }
21366}
21367
21368impl RowExt for MultiBufferRow {
21369 fn as_f32(&self) -> f32 {
21370 self.0 as f32
21371 }
21372
21373 fn next_row(&self) -> Self {
21374 Self(self.0 + 1)
21375 }
21376
21377 fn previous_row(&self) -> Self {
21378 Self(self.0.saturating_sub(1))
21379 }
21380
21381 fn minus(&self, other: Self) -> u32 {
21382 self.0 - other.0
21383 }
21384}
21385
21386trait RowRangeExt {
21387 type Row;
21388
21389 fn len(&self) -> usize;
21390
21391 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21392}
21393
21394impl RowRangeExt for Range<MultiBufferRow> {
21395 type Row = MultiBufferRow;
21396
21397 fn len(&self) -> usize {
21398 (self.end.0 - self.start.0) as usize
21399 }
21400
21401 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21402 (self.start.0..self.end.0).map(MultiBufferRow)
21403 }
21404}
21405
21406impl RowRangeExt for Range<DisplayRow> {
21407 type Row = DisplayRow;
21408
21409 fn len(&self) -> usize {
21410 (self.end.0 - self.start.0) as usize
21411 }
21412
21413 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21414 (self.start.0..self.end.0).map(DisplayRow)
21415 }
21416}
21417
21418/// If select range has more than one line, we
21419/// just point the cursor to range.start.
21420fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21421 if range.start.row == range.end.row {
21422 range
21423 } else {
21424 range.start..range.start
21425 }
21426}
21427pub struct KillRing(ClipboardItem);
21428impl Global for KillRing {}
21429
21430const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21431
21432enum BreakpointPromptEditAction {
21433 Log,
21434 Condition,
21435 HitCondition,
21436}
21437
21438struct BreakpointPromptEditor {
21439 pub(crate) prompt: Entity<Editor>,
21440 editor: WeakEntity<Editor>,
21441 breakpoint_anchor: Anchor,
21442 breakpoint: Breakpoint,
21443 edit_action: BreakpointPromptEditAction,
21444 block_ids: HashSet<CustomBlockId>,
21445 editor_margins: Arc<Mutex<EditorMargins>>,
21446 _subscriptions: Vec<Subscription>,
21447}
21448
21449impl BreakpointPromptEditor {
21450 const MAX_LINES: u8 = 4;
21451
21452 fn new(
21453 editor: WeakEntity<Editor>,
21454 breakpoint_anchor: Anchor,
21455 breakpoint: Breakpoint,
21456 edit_action: BreakpointPromptEditAction,
21457 window: &mut Window,
21458 cx: &mut Context<Self>,
21459 ) -> Self {
21460 let base_text = match edit_action {
21461 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21462 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21463 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21464 }
21465 .map(|msg| msg.to_string())
21466 .unwrap_or_default();
21467
21468 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21469 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21470
21471 let prompt = cx.new(|cx| {
21472 let mut prompt = Editor::new(
21473 EditorMode::AutoHeight {
21474 max_lines: Self::MAX_LINES as usize,
21475 },
21476 buffer,
21477 None,
21478 window,
21479 cx,
21480 );
21481 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21482 prompt.set_show_cursor_when_unfocused(false, cx);
21483 prompt.set_placeholder_text(
21484 match edit_action {
21485 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21486 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21487 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21488 },
21489 cx,
21490 );
21491
21492 prompt
21493 });
21494
21495 Self {
21496 prompt,
21497 editor,
21498 breakpoint_anchor,
21499 breakpoint,
21500 edit_action,
21501 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21502 block_ids: Default::default(),
21503 _subscriptions: vec![],
21504 }
21505 }
21506
21507 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21508 self.block_ids.extend(block_ids)
21509 }
21510
21511 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21512 if let Some(editor) = self.editor.upgrade() {
21513 let message = self
21514 .prompt
21515 .read(cx)
21516 .buffer
21517 .read(cx)
21518 .as_singleton()
21519 .expect("A multi buffer in breakpoint prompt isn't possible")
21520 .read(cx)
21521 .as_rope()
21522 .to_string();
21523
21524 editor.update(cx, |editor, cx| {
21525 editor.edit_breakpoint_at_anchor(
21526 self.breakpoint_anchor,
21527 self.breakpoint.clone(),
21528 match self.edit_action {
21529 BreakpointPromptEditAction::Log => {
21530 BreakpointEditAction::EditLogMessage(message.into())
21531 }
21532 BreakpointPromptEditAction::Condition => {
21533 BreakpointEditAction::EditCondition(message.into())
21534 }
21535 BreakpointPromptEditAction::HitCondition => {
21536 BreakpointEditAction::EditHitCondition(message.into())
21537 }
21538 },
21539 cx,
21540 );
21541
21542 editor.remove_blocks(self.block_ids.clone(), None, cx);
21543 cx.focus_self(window);
21544 });
21545 }
21546 }
21547
21548 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21549 self.editor
21550 .update(cx, |editor, cx| {
21551 editor.remove_blocks(self.block_ids.clone(), None, cx);
21552 window.focus(&editor.focus_handle);
21553 })
21554 .log_err();
21555 }
21556
21557 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21558 let settings = ThemeSettings::get_global(cx);
21559 let text_style = TextStyle {
21560 color: if self.prompt.read(cx).read_only(cx) {
21561 cx.theme().colors().text_disabled
21562 } else {
21563 cx.theme().colors().text
21564 },
21565 font_family: settings.buffer_font.family.clone(),
21566 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21567 font_size: settings.buffer_font_size(cx).into(),
21568 font_weight: settings.buffer_font.weight,
21569 line_height: relative(settings.buffer_line_height.value()),
21570 ..Default::default()
21571 };
21572 EditorElement::new(
21573 &self.prompt,
21574 EditorStyle {
21575 background: cx.theme().colors().editor_background,
21576 local_player: cx.theme().players().local(),
21577 text: text_style,
21578 ..Default::default()
21579 },
21580 )
21581 }
21582}
21583
21584impl Render for BreakpointPromptEditor {
21585 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21586 let editor_margins = *self.editor_margins.lock();
21587 let gutter_dimensions = editor_margins.gutter;
21588 h_flex()
21589 .key_context("Editor")
21590 .bg(cx.theme().colors().editor_background)
21591 .border_y_1()
21592 .border_color(cx.theme().status().info_border)
21593 .size_full()
21594 .py(window.line_height() / 2.5)
21595 .on_action(cx.listener(Self::confirm))
21596 .on_action(cx.listener(Self::cancel))
21597 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21598 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21599 }
21600}
21601
21602impl Focusable for BreakpointPromptEditor {
21603 fn focus_handle(&self, cx: &App) -> FocusHandle {
21604 self.prompt.focus_handle(cx)
21605 }
21606}
21607
21608fn all_edits_insertions_or_deletions(
21609 edits: &Vec<(Range<Anchor>, String)>,
21610 snapshot: &MultiBufferSnapshot,
21611) -> bool {
21612 let mut all_insertions = true;
21613 let mut all_deletions = true;
21614
21615 for (range, new_text) in edits.iter() {
21616 let range_is_empty = range.to_offset(&snapshot).is_empty();
21617 let text_is_empty = new_text.is_empty();
21618
21619 if range_is_empty != text_is_empty {
21620 if range_is_empty {
21621 all_deletions = false;
21622 } else {
21623 all_insertions = false;
21624 }
21625 } else {
21626 return false;
21627 }
21628
21629 if !all_insertions && !all_deletions {
21630 return false;
21631 }
21632 }
21633 all_insertions || all_deletions
21634}
21635
21636struct MissingEditPredictionKeybindingTooltip;
21637
21638impl Render for MissingEditPredictionKeybindingTooltip {
21639 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21640 ui::tooltip_container(window, cx, |container, _, cx| {
21641 container
21642 .flex_shrink_0()
21643 .max_w_80()
21644 .min_h(rems_from_px(124.))
21645 .justify_between()
21646 .child(
21647 v_flex()
21648 .flex_1()
21649 .text_ui_sm(cx)
21650 .child(Label::new("Conflict with Accept Keybinding"))
21651 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21652 )
21653 .child(
21654 h_flex()
21655 .pb_1()
21656 .gap_1()
21657 .items_end()
21658 .w_full()
21659 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21660 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21661 }))
21662 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21663 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21664 })),
21665 )
21666 })
21667 }
21668}
21669
21670#[derive(Debug, Clone, Copy, PartialEq)]
21671pub struct LineHighlight {
21672 pub background: Background,
21673 pub border: Option<gpui::Hsla>,
21674 pub include_gutter: bool,
21675 pub type_id: Option<TypeId>,
21676}
21677
21678fn render_diff_hunk_controls(
21679 row: u32,
21680 status: &DiffHunkStatus,
21681 hunk_range: Range<Anchor>,
21682 is_created_file: bool,
21683 line_height: Pixels,
21684 editor: &Entity<Editor>,
21685 _window: &mut Window,
21686 cx: &mut App,
21687) -> AnyElement {
21688 h_flex()
21689 .h(line_height)
21690 .mr_1()
21691 .gap_1()
21692 .px_0p5()
21693 .pb_1()
21694 .border_x_1()
21695 .border_b_1()
21696 .border_color(cx.theme().colors().border_variant)
21697 .rounded_b_lg()
21698 .bg(cx.theme().colors().editor_background)
21699 .gap_1()
21700 .occlude()
21701 .shadow_md()
21702 .child(if status.has_secondary_hunk() {
21703 Button::new(("stage", row as u64), "Stage")
21704 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21705 .tooltip({
21706 let focus_handle = editor.focus_handle(cx);
21707 move |window, cx| {
21708 Tooltip::for_action_in(
21709 "Stage Hunk",
21710 &::git::ToggleStaged,
21711 &focus_handle,
21712 window,
21713 cx,
21714 )
21715 }
21716 })
21717 .on_click({
21718 let editor = editor.clone();
21719 move |_event, _window, cx| {
21720 editor.update(cx, |editor, cx| {
21721 editor.stage_or_unstage_diff_hunks(
21722 true,
21723 vec![hunk_range.start..hunk_range.start],
21724 cx,
21725 );
21726 });
21727 }
21728 })
21729 } else {
21730 Button::new(("unstage", row as u64), "Unstage")
21731 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21732 .tooltip({
21733 let focus_handle = editor.focus_handle(cx);
21734 move |window, cx| {
21735 Tooltip::for_action_in(
21736 "Unstage Hunk",
21737 &::git::ToggleStaged,
21738 &focus_handle,
21739 window,
21740 cx,
21741 )
21742 }
21743 })
21744 .on_click({
21745 let editor = editor.clone();
21746 move |_event, _window, cx| {
21747 editor.update(cx, |editor, cx| {
21748 editor.stage_or_unstage_diff_hunks(
21749 false,
21750 vec![hunk_range.start..hunk_range.start],
21751 cx,
21752 );
21753 });
21754 }
21755 })
21756 })
21757 .child(
21758 Button::new(("restore", row as u64), "Restore")
21759 .tooltip({
21760 let focus_handle = editor.focus_handle(cx);
21761 move |window, cx| {
21762 Tooltip::for_action_in(
21763 "Restore Hunk",
21764 &::git::Restore,
21765 &focus_handle,
21766 window,
21767 cx,
21768 )
21769 }
21770 })
21771 .on_click({
21772 let editor = editor.clone();
21773 move |_event, window, cx| {
21774 editor.update(cx, |editor, cx| {
21775 let snapshot = editor.snapshot(window, cx);
21776 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21777 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21778 });
21779 }
21780 })
21781 .disabled(is_created_file),
21782 )
21783 .when(
21784 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21785 |el| {
21786 el.child(
21787 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21788 .shape(IconButtonShape::Square)
21789 .icon_size(IconSize::Small)
21790 // .disabled(!has_multiple_hunks)
21791 .tooltip({
21792 let focus_handle = editor.focus_handle(cx);
21793 move |window, cx| {
21794 Tooltip::for_action_in(
21795 "Next Hunk",
21796 &GoToHunk,
21797 &focus_handle,
21798 window,
21799 cx,
21800 )
21801 }
21802 })
21803 .on_click({
21804 let editor = editor.clone();
21805 move |_event, window, cx| {
21806 editor.update(cx, |editor, cx| {
21807 let snapshot = editor.snapshot(window, cx);
21808 let position =
21809 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21810 editor.go_to_hunk_before_or_after_position(
21811 &snapshot,
21812 position,
21813 Direction::Next,
21814 window,
21815 cx,
21816 );
21817 editor.expand_selected_diff_hunks(cx);
21818 });
21819 }
21820 }),
21821 )
21822 .child(
21823 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21824 .shape(IconButtonShape::Square)
21825 .icon_size(IconSize::Small)
21826 // .disabled(!has_multiple_hunks)
21827 .tooltip({
21828 let focus_handle = editor.focus_handle(cx);
21829 move |window, cx| {
21830 Tooltip::for_action_in(
21831 "Previous Hunk",
21832 &GoToPreviousHunk,
21833 &focus_handle,
21834 window,
21835 cx,
21836 )
21837 }
21838 })
21839 .on_click({
21840 let editor = editor.clone();
21841 move |_event, window, cx| {
21842 editor.update(cx, |editor, cx| {
21843 let snapshot = editor.snapshot(window, cx);
21844 let point =
21845 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21846 editor.go_to_hunk_before_or_after_position(
21847 &snapshot,
21848 point,
21849 Direction::Prev,
21850 window,
21851 cx,
21852 );
21853 editor.expand_selected_diff_hunks(cx);
21854 });
21855 }
21856 }),
21857 )
21858 },
21859 )
21860 .into_any_element()
21861}