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_code_actions: Option<bool>,
1076 show_runnables: Option<bool>,
1077 show_breakpoints: Option<bool>,
1078 git_blame_gutter_max_author_length: Option<usize>,
1079 pub display_snapshot: DisplaySnapshot,
1080 pub placeholder_text: Option<Arc<str>>,
1081 is_focused: bool,
1082 scroll_anchor: ScrollAnchor,
1083 ongoing_scroll: OngoingScroll,
1084 current_line_highlight: CurrentLineHighlight,
1085 gutter_hovered: bool,
1086}
1087
1088#[derive(Default, Debug, Clone, Copy)]
1089pub struct GutterDimensions {
1090 pub left_padding: Pixels,
1091 pub right_padding: Pixels,
1092 pub width: Pixels,
1093 pub margin: Pixels,
1094 pub git_blame_entries_width: Option<Pixels>,
1095}
1096
1097impl GutterDimensions {
1098 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1099 Self {
1100 margin: Self::default_gutter_margin(font_id, font_size, cx),
1101 ..Default::default()
1102 }
1103 }
1104
1105 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1106 -cx.text_system().descent(font_id, font_size)
1107 }
1108 /// The full width of the space taken up by the gutter.
1109 pub fn full_width(&self) -> Pixels {
1110 self.margin + self.width
1111 }
1112
1113 /// The width of the space reserved for the fold indicators,
1114 /// use alongside 'justify_end' and `gutter_width` to
1115 /// right align content with the line numbers
1116 pub fn fold_area_width(&self) -> Pixels {
1117 self.margin + self.right_padding
1118 }
1119}
1120
1121#[derive(Debug)]
1122pub struct RemoteSelection {
1123 pub replica_id: ReplicaId,
1124 pub selection: Selection<Anchor>,
1125 pub cursor_shape: CursorShape,
1126 pub collaborator_id: CollaboratorId,
1127 pub line_mode: bool,
1128 pub user_name: Option<SharedString>,
1129 pub color: PlayerColor,
1130}
1131
1132#[derive(Clone, Debug)]
1133struct SelectionHistoryEntry {
1134 selections: Arc<[Selection<Anchor>]>,
1135 select_next_state: Option<SelectNextState>,
1136 select_prev_state: Option<SelectNextState>,
1137 add_selections_state: Option<AddSelectionsState>,
1138}
1139
1140enum SelectionHistoryMode {
1141 Normal,
1142 Undoing,
1143 Redoing,
1144}
1145
1146#[derive(Clone, PartialEq, Eq, Hash)]
1147struct HoveredCursor {
1148 replica_id: u16,
1149 selection_id: usize,
1150}
1151
1152impl Default for SelectionHistoryMode {
1153 fn default() -> Self {
1154 Self::Normal
1155 }
1156}
1157
1158#[derive(Default)]
1159struct SelectionHistory {
1160 #[allow(clippy::type_complexity)]
1161 selections_by_transaction:
1162 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1163 mode: SelectionHistoryMode,
1164 undo_stack: VecDeque<SelectionHistoryEntry>,
1165 redo_stack: VecDeque<SelectionHistoryEntry>,
1166}
1167
1168impl SelectionHistory {
1169 fn insert_transaction(
1170 &mut self,
1171 transaction_id: TransactionId,
1172 selections: Arc<[Selection<Anchor>]>,
1173 ) {
1174 self.selections_by_transaction
1175 .insert(transaction_id, (selections, None));
1176 }
1177
1178 #[allow(clippy::type_complexity)]
1179 fn transaction(
1180 &self,
1181 transaction_id: TransactionId,
1182 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1183 self.selections_by_transaction.get(&transaction_id)
1184 }
1185
1186 #[allow(clippy::type_complexity)]
1187 fn transaction_mut(
1188 &mut self,
1189 transaction_id: TransactionId,
1190 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1191 self.selections_by_transaction.get_mut(&transaction_id)
1192 }
1193
1194 fn push(&mut self, entry: SelectionHistoryEntry) {
1195 if !entry.selections.is_empty() {
1196 match self.mode {
1197 SelectionHistoryMode::Normal => {
1198 self.push_undo(entry);
1199 self.redo_stack.clear();
1200 }
1201 SelectionHistoryMode::Undoing => self.push_redo(entry),
1202 SelectionHistoryMode::Redoing => self.push_undo(entry),
1203 }
1204 }
1205 }
1206
1207 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1208 if self
1209 .undo_stack
1210 .back()
1211 .map_or(true, |e| e.selections != entry.selections)
1212 {
1213 self.undo_stack.push_back(entry);
1214 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1215 self.undo_stack.pop_front();
1216 }
1217 }
1218 }
1219
1220 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1221 if self
1222 .redo_stack
1223 .back()
1224 .map_or(true, |e| e.selections != entry.selections)
1225 {
1226 self.redo_stack.push_back(entry);
1227 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1228 self.redo_stack.pop_front();
1229 }
1230 }
1231 }
1232}
1233
1234#[derive(Clone, Copy)]
1235pub struct RowHighlightOptions {
1236 pub autoscroll: bool,
1237 pub include_gutter: bool,
1238}
1239
1240impl Default for RowHighlightOptions {
1241 fn default() -> Self {
1242 Self {
1243 autoscroll: Default::default(),
1244 include_gutter: true,
1245 }
1246 }
1247}
1248
1249struct RowHighlight {
1250 index: usize,
1251 range: Range<Anchor>,
1252 color: Hsla,
1253 options: RowHighlightOptions,
1254 type_id: TypeId,
1255}
1256
1257#[derive(Clone, Debug)]
1258struct AddSelectionsState {
1259 above: bool,
1260 stack: Vec<usize>,
1261}
1262
1263#[derive(Clone)]
1264struct SelectNextState {
1265 query: AhoCorasick,
1266 wordwise: bool,
1267 done: bool,
1268}
1269
1270impl std::fmt::Debug for SelectNextState {
1271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1272 f.debug_struct(std::any::type_name::<Self>())
1273 .field("wordwise", &self.wordwise)
1274 .field("done", &self.done)
1275 .finish()
1276 }
1277}
1278
1279#[derive(Debug)]
1280struct AutocloseRegion {
1281 selection_id: usize,
1282 range: Range<Anchor>,
1283 pair: BracketPair,
1284}
1285
1286#[derive(Debug)]
1287struct SnippetState {
1288 ranges: Vec<Vec<Range<Anchor>>>,
1289 active_index: usize,
1290 choices: Vec<Option<Vec<String>>>,
1291}
1292
1293#[doc(hidden)]
1294pub struct RenameState {
1295 pub range: Range<Anchor>,
1296 pub old_name: Arc<str>,
1297 pub editor: Entity<Editor>,
1298 block_id: CustomBlockId,
1299}
1300
1301struct InvalidationStack<T>(Vec<T>);
1302
1303struct RegisteredInlineCompletionProvider {
1304 provider: Arc<dyn InlineCompletionProviderHandle>,
1305 _subscription: Subscription,
1306}
1307
1308#[derive(Debug, PartialEq, Eq)]
1309pub struct ActiveDiagnosticGroup {
1310 pub active_range: Range<Anchor>,
1311 pub active_message: String,
1312 pub group_id: usize,
1313 pub blocks: HashSet<CustomBlockId>,
1314}
1315
1316#[derive(Debug, PartialEq, Eq)]
1317
1318pub(crate) enum ActiveDiagnostic {
1319 None,
1320 All,
1321 Group(ActiveDiagnosticGroup),
1322}
1323
1324#[derive(Serialize, Deserialize, Clone, Debug)]
1325pub struct ClipboardSelection {
1326 /// The number of bytes in this selection.
1327 pub len: usize,
1328 /// Whether this was a full-line selection.
1329 pub is_entire_line: bool,
1330 /// The indentation of the first line when this content was originally copied.
1331 pub first_line_indent: u32,
1332}
1333
1334// selections, scroll behavior, was newest selection reversed
1335type SelectSyntaxNodeHistoryState = (
1336 Box<[Selection<usize>]>,
1337 SelectSyntaxNodeScrollBehavior,
1338 bool,
1339);
1340
1341#[derive(Default)]
1342struct SelectSyntaxNodeHistory {
1343 stack: Vec<SelectSyntaxNodeHistoryState>,
1344 // disable temporarily to allow changing selections without losing the stack
1345 pub disable_clearing: bool,
1346}
1347
1348impl SelectSyntaxNodeHistory {
1349 pub fn try_clear(&mut self) {
1350 if !self.disable_clearing {
1351 self.stack.clear();
1352 }
1353 }
1354
1355 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1356 self.stack.push(selection);
1357 }
1358
1359 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1360 self.stack.pop()
1361 }
1362}
1363
1364enum SelectSyntaxNodeScrollBehavior {
1365 CursorTop,
1366 FitSelection,
1367 CursorBottom,
1368}
1369
1370#[derive(Debug)]
1371pub(crate) struct NavigationData {
1372 cursor_anchor: Anchor,
1373 cursor_position: Point,
1374 scroll_anchor: ScrollAnchor,
1375 scroll_top_row: u32,
1376}
1377
1378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1379pub enum GotoDefinitionKind {
1380 Symbol,
1381 Declaration,
1382 Type,
1383 Implementation,
1384}
1385
1386#[derive(Debug, Clone)]
1387enum InlayHintRefreshReason {
1388 ModifiersChanged(bool),
1389 Toggle(bool),
1390 SettingsChange(InlayHintSettings),
1391 NewLinesShown,
1392 BufferEdited(HashSet<Arc<Language>>),
1393 RefreshRequested,
1394 ExcerptsRemoved(Vec<ExcerptId>),
1395}
1396
1397impl InlayHintRefreshReason {
1398 fn description(&self) -> &'static str {
1399 match self {
1400 Self::ModifiersChanged(_) => "modifiers changed",
1401 Self::Toggle(_) => "toggle",
1402 Self::SettingsChange(_) => "settings change",
1403 Self::NewLinesShown => "new lines shown",
1404 Self::BufferEdited(_) => "buffer edited",
1405 Self::RefreshRequested => "refresh requested",
1406 Self::ExcerptsRemoved(_) => "excerpts removed",
1407 }
1408 }
1409}
1410
1411pub enum FormatTarget {
1412 Buffers,
1413 Ranges(Vec<Range<MultiBufferPoint>>),
1414}
1415
1416pub(crate) struct FocusedBlock {
1417 id: BlockId,
1418 focus_handle: WeakFocusHandle,
1419}
1420
1421#[derive(Clone)]
1422enum JumpData {
1423 MultiBufferRow {
1424 row: MultiBufferRow,
1425 line_offset_from_top: u32,
1426 },
1427 MultiBufferPoint {
1428 excerpt_id: ExcerptId,
1429 position: Point,
1430 anchor: text::Anchor,
1431 line_offset_from_top: u32,
1432 },
1433}
1434
1435pub enum MultibufferSelectionMode {
1436 First,
1437 All,
1438}
1439
1440#[derive(Clone, Copy, Debug, Default)]
1441pub struct RewrapOptions {
1442 pub override_language_settings: bool,
1443 pub preserve_existing_whitespace: bool,
1444}
1445
1446impl Editor {
1447 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1448 let buffer = cx.new(|cx| Buffer::local("", cx));
1449 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1450 Self::new(
1451 EditorMode::SingleLine { auto_width: false },
1452 buffer,
1453 None,
1454 window,
1455 cx,
1456 )
1457 }
1458
1459 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1460 let buffer = cx.new(|cx| Buffer::local("", cx));
1461 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1462 Self::new(EditorMode::full(), buffer, None, window, cx)
1463 }
1464
1465 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1466 let buffer = cx.new(|cx| Buffer::local("", cx));
1467 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1468 Self::new(
1469 EditorMode::SingleLine { auto_width: true },
1470 buffer,
1471 None,
1472 window,
1473 cx,
1474 )
1475 }
1476
1477 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1478 let buffer = cx.new(|cx| Buffer::local("", cx));
1479 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1480 Self::new(
1481 EditorMode::AutoHeight { max_lines },
1482 buffer,
1483 None,
1484 window,
1485 cx,
1486 )
1487 }
1488
1489 pub fn for_buffer(
1490 buffer: Entity<Buffer>,
1491 project: Option<Entity<Project>>,
1492 window: &mut Window,
1493 cx: &mut Context<Self>,
1494 ) -> Self {
1495 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1496 Self::new(EditorMode::full(), buffer, project, window, cx)
1497 }
1498
1499 pub fn for_multibuffer(
1500 buffer: Entity<MultiBuffer>,
1501 project: Option<Entity<Project>>,
1502 window: &mut Window,
1503 cx: &mut Context<Self>,
1504 ) -> Self {
1505 Self::new(EditorMode::full(), buffer, project, window, cx)
1506 }
1507
1508 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1509 let mut clone = Self::new(
1510 self.mode.clone(),
1511 self.buffer.clone(),
1512 self.project.clone(),
1513 window,
1514 cx,
1515 );
1516 self.display_map.update(cx, |display_map, cx| {
1517 let snapshot = display_map.snapshot(cx);
1518 clone.display_map.update(cx, |display_map, cx| {
1519 display_map.set_state(&snapshot, cx);
1520 });
1521 });
1522 clone.folds_did_change(cx);
1523 clone.selections.clone_state(&self.selections);
1524 clone.scroll_manager.clone_state(&self.scroll_manager);
1525 clone.searchable = self.searchable;
1526 clone.read_only = self.read_only;
1527 clone
1528 }
1529
1530 pub fn new(
1531 mode: EditorMode,
1532 buffer: Entity<MultiBuffer>,
1533 project: Option<Entity<Project>>,
1534 window: &mut Window,
1535 cx: &mut Context<Self>,
1536 ) -> Self {
1537 Editor::new_internal(mode, buffer, project, None, window, cx)
1538 }
1539
1540 fn new_internal(
1541 mode: EditorMode,
1542 buffer: Entity<MultiBuffer>,
1543 project: Option<Entity<Project>>,
1544 display_map: Option<Entity<DisplayMap>>,
1545 window: &mut Window,
1546 cx: &mut Context<Self>,
1547 ) -> Self {
1548 debug_assert!(
1549 display_map.is_none() || mode.is_minimap(),
1550 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1551 );
1552
1553 let full_mode = mode.is_full();
1554 let diagnostics_max_severity = if full_mode {
1555 EditorSettings::get_global(cx)
1556 .diagnostics_max_severity
1557 .unwrap_or(DiagnosticSeverity::Hint)
1558 } else {
1559 DiagnosticSeverity::Off
1560 };
1561 let style = window.text_style();
1562 let font_size = style.font_size.to_pixels(window.rem_size());
1563 let editor = cx.entity().downgrade();
1564 let fold_placeholder = FoldPlaceholder {
1565 constrain_width: true,
1566 render: Arc::new(move |fold_id, fold_range, cx| {
1567 let editor = editor.clone();
1568 div()
1569 .id(fold_id)
1570 .bg(cx.theme().colors().ghost_element_background)
1571 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1572 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1573 .rounded_xs()
1574 .size_full()
1575 .cursor_pointer()
1576 .child("⋯")
1577 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1578 .on_click(move |_, _window, cx| {
1579 editor
1580 .update(cx, |editor, cx| {
1581 editor.unfold_ranges(
1582 &[fold_range.start..fold_range.end],
1583 true,
1584 false,
1585 cx,
1586 );
1587 cx.stop_propagation();
1588 })
1589 .ok();
1590 })
1591 .into_any()
1592 }),
1593 merge_adjacent: true,
1594 ..FoldPlaceholder::default()
1595 };
1596 let display_map = display_map.unwrap_or_else(|| {
1597 cx.new(|cx| {
1598 DisplayMap::new(
1599 buffer.clone(),
1600 style.font(),
1601 font_size,
1602 None,
1603 FILE_HEADER_HEIGHT,
1604 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1605 fold_placeholder,
1606 diagnostics_max_severity,
1607 cx,
1608 )
1609 })
1610 });
1611
1612 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1613
1614 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1615
1616 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1617 .then(|| language_settings::SoftWrap::None);
1618
1619 let mut project_subscriptions = Vec::new();
1620 if mode.is_full() {
1621 if let Some(project) = project.as_ref() {
1622 project_subscriptions.push(cx.subscribe_in(
1623 project,
1624 window,
1625 |editor, _, event, window, cx| match event {
1626 project::Event::RefreshCodeLens => {
1627 // we always query lens with actions, without storing them, always refreshing them
1628 }
1629 project::Event::RefreshInlayHints => {
1630 editor
1631 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1632 }
1633 project::Event::SnippetEdit(id, snippet_edits) => {
1634 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1635 let focus_handle = editor.focus_handle(cx);
1636 if focus_handle.is_focused(window) {
1637 let snapshot = buffer.read(cx).snapshot();
1638 for (range, snippet) in snippet_edits {
1639 let editor_range =
1640 language::range_from_lsp(*range).to_offset(&snapshot);
1641 editor
1642 .insert_snippet(
1643 &[editor_range],
1644 snippet.clone(),
1645 window,
1646 cx,
1647 )
1648 .ok();
1649 }
1650 }
1651 }
1652 }
1653 _ => {}
1654 },
1655 ));
1656 if let Some(task_inventory) = project
1657 .read(cx)
1658 .task_store()
1659 .read(cx)
1660 .task_inventory()
1661 .cloned()
1662 {
1663 project_subscriptions.push(cx.observe_in(
1664 &task_inventory,
1665 window,
1666 |editor, _, window, cx| {
1667 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1668 },
1669 ));
1670 };
1671
1672 project_subscriptions.push(cx.subscribe_in(
1673 &project.read(cx).breakpoint_store(),
1674 window,
1675 |editor, _, event, window, cx| match event {
1676 BreakpointStoreEvent::ClearDebugLines => {
1677 editor.clear_row_highlights::<ActiveDebugLine>();
1678 editor.refresh_inline_values(cx);
1679 }
1680 BreakpointStoreEvent::SetDebugLine => {
1681 if editor.go_to_active_debug_line(window, cx) {
1682 cx.stop_propagation();
1683 }
1684
1685 editor.refresh_inline_values(cx);
1686 }
1687 _ => {}
1688 },
1689 ));
1690 }
1691 }
1692
1693 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1694
1695 let inlay_hint_settings =
1696 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1697 let focus_handle = cx.focus_handle();
1698 cx.on_focus(&focus_handle, window, Self::handle_focus)
1699 .detach();
1700 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1701 .detach();
1702 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1703 .detach();
1704 cx.on_blur(&focus_handle, window, Self::handle_blur)
1705 .detach();
1706
1707 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1708 Some(false)
1709 } else {
1710 None
1711 };
1712
1713 let breakpoint_store = match (&mode, project.as_ref()) {
1714 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1715 _ => None,
1716 };
1717
1718 let mut code_action_providers = Vec::new();
1719 let mut load_uncommitted_diff = None;
1720 if let Some(project) = project.clone() {
1721 load_uncommitted_diff = Some(
1722 update_uncommitted_diff_for_buffer(
1723 cx.entity(),
1724 &project,
1725 buffer.read(cx).all_buffers(),
1726 buffer.clone(),
1727 cx,
1728 )
1729 .shared(),
1730 );
1731 code_action_providers.push(Rc::new(project) as Rc<_>);
1732 }
1733
1734 let mut this = Self {
1735 focus_handle,
1736 show_cursor_when_unfocused: false,
1737 last_focused_descendant: None,
1738 buffer: buffer.clone(),
1739 display_map: display_map.clone(),
1740 selections,
1741 scroll_manager: ScrollManager::new(cx),
1742 columnar_selection_tail: None,
1743 add_selections_state: None,
1744 select_next_state: None,
1745 select_prev_state: None,
1746 selection_history: SelectionHistory::default(),
1747 autoclose_regions: Vec::new(),
1748 snippet_stack: InvalidationStack::default(),
1749 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1750 ime_transaction: None,
1751 active_diagnostics: ActiveDiagnostic::None,
1752 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1753 inline_diagnostics_update: Task::ready(()),
1754 inline_diagnostics: Vec::new(),
1755 soft_wrap_mode_override,
1756 diagnostics_max_severity,
1757 hard_wrap: None,
1758 completion_provider: project.clone().map(|project| Box::new(project) as _),
1759 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1760 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1761 project,
1762 blink_manager: blink_manager.clone(),
1763 show_local_selections: true,
1764 show_scrollbars: full_mode,
1765 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1766 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1767 show_gutter: mode.is_full(),
1768 show_line_numbers: None,
1769 use_relative_line_numbers: None,
1770 disable_expand_excerpt_buttons: false,
1771 show_git_diff_gutter: None,
1772 show_code_actions: None,
1773 show_runnables: None,
1774 show_breakpoints: None,
1775 show_wrap_guides: None,
1776 show_indent_guides,
1777 placeholder_text: None,
1778 highlight_order: 0,
1779 highlighted_rows: HashMap::default(),
1780 background_highlights: TreeMap::default(),
1781 gutter_highlights: TreeMap::default(),
1782 scrollbar_marker_state: ScrollbarMarkerState::default(),
1783 active_indent_guides_state: ActiveIndentGuidesState::default(),
1784 nav_history: None,
1785 context_menu: RefCell::new(None),
1786 context_menu_options: None,
1787 mouse_context_menu: None,
1788 completion_tasks: Vec::new(),
1789 inline_blame_popover: None,
1790 signature_help_state: SignatureHelpState::default(),
1791 auto_signature_help: None,
1792 find_all_references_task_sources: Vec::new(),
1793 next_completion_id: 0,
1794 next_inlay_id: 0,
1795 code_action_providers,
1796 available_code_actions: None,
1797 code_actions_task: None,
1798 quick_selection_highlight_task: None,
1799 debounced_selection_highlight_task: None,
1800 document_highlights_task: None,
1801 linked_editing_range_task: None,
1802 pending_rename: None,
1803 searchable: true,
1804 cursor_shape: EditorSettings::get_global(cx)
1805 .cursor_shape
1806 .unwrap_or_default(),
1807 current_line_highlight: None,
1808 autoindent_mode: Some(AutoindentMode::EachLine),
1809 collapse_matches: false,
1810 workspace: None,
1811 input_enabled: true,
1812 use_modal_editing: mode.is_full(),
1813 read_only: mode.is_minimap(),
1814 use_autoclose: true,
1815 use_auto_surround: true,
1816 auto_replace_emoji_shortcode: false,
1817 jsx_tag_auto_close_enabled_in_any_buffer: false,
1818 leader_id: None,
1819 remote_id: None,
1820 hover_state: HoverState::default(),
1821 pending_mouse_down: None,
1822 hovered_link_state: None,
1823 edit_prediction_provider: None,
1824 active_inline_completion: None,
1825 stale_inline_completion_in_menu: None,
1826 edit_prediction_preview: EditPredictionPreview::Inactive {
1827 released_too_fast: false,
1828 },
1829 inline_diagnostics_enabled: mode.is_full(),
1830 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1831 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1832
1833 gutter_hovered: false,
1834 pixel_position_of_newest_cursor: None,
1835 last_bounds: None,
1836 last_position_map: None,
1837 expect_bounds_change: None,
1838 gutter_dimensions: GutterDimensions::default(),
1839 style: None,
1840 show_cursor_names: false,
1841 hovered_cursors: HashMap::default(),
1842 next_editor_action_id: EditorActionId::default(),
1843 editor_actions: Rc::default(),
1844 inline_completions_hidden_for_vim_mode: false,
1845 show_inline_completions_override: None,
1846 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1847 edit_prediction_settings: EditPredictionSettings::Disabled,
1848 edit_prediction_indent_conflict: false,
1849 edit_prediction_requires_modifier_in_indent_conflict: true,
1850 custom_context_menu: None,
1851 show_git_blame_gutter: false,
1852 show_git_blame_inline: false,
1853 show_selection_menu: None,
1854 show_git_blame_inline_delay_task: None,
1855 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1856 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1857 serialize_dirty_buffers: !mode.is_minimap()
1858 && ProjectSettings::get_global(cx)
1859 .session
1860 .restore_unsaved_buffers,
1861 blame: None,
1862 blame_subscription: None,
1863 tasks: BTreeMap::default(),
1864
1865 breakpoint_store,
1866 gutter_breakpoint_indicator: (None, None),
1867 _subscriptions: vec![
1868 cx.observe(&buffer, Self::on_buffer_changed),
1869 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1870 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1871 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1872 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1873 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1874 cx.observe_window_activation(window, |editor, window, cx| {
1875 let active = window.is_window_active();
1876 editor.blink_manager.update(cx, |blink_manager, cx| {
1877 if active {
1878 blink_manager.enable(cx);
1879 } else {
1880 blink_manager.disable(cx);
1881 }
1882 });
1883 }),
1884 ],
1885 tasks_update_task: None,
1886 linked_edit_ranges: Default::default(),
1887 in_project_search: false,
1888 previous_search_ranges: None,
1889 breadcrumb_header: None,
1890 focused_block: None,
1891 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1892 addons: HashMap::default(),
1893 registered_buffers: HashMap::default(),
1894 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1895 selection_mark_mode: false,
1896 toggle_fold_multiple_buffers: Task::ready(()),
1897 serialize_selections: Task::ready(()),
1898 serialize_folds: Task::ready(()),
1899 text_style_refinement: None,
1900 load_diff_task: load_uncommitted_diff,
1901 temporary_diff_override: false,
1902 mouse_cursor_hidden: false,
1903 minimap: None,
1904 hide_mouse_mode: EditorSettings::get_global(cx)
1905 .hide_mouse
1906 .unwrap_or_default(),
1907 change_list: ChangeList::new(),
1908 mode,
1909 };
1910 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1911 this._subscriptions
1912 .push(cx.observe(breakpoints, |_, _, cx| {
1913 cx.notify();
1914 }));
1915 }
1916 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1917 this._subscriptions.extend(project_subscriptions);
1918
1919 this._subscriptions.push(cx.subscribe_in(
1920 &cx.entity(),
1921 window,
1922 |editor, _, e: &EditorEvent, window, cx| match e {
1923 EditorEvent::ScrollPositionChanged { local, .. } => {
1924 if *local {
1925 let new_anchor = editor.scroll_manager.anchor();
1926 let snapshot = editor.snapshot(window, cx);
1927 editor.update_restoration_data(cx, move |data| {
1928 data.scroll_position = (
1929 new_anchor.top_row(&snapshot.buffer_snapshot),
1930 new_anchor.offset,
1931 );
1932 });
1933 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1934 editor.inline_blame_popover.take();
1935 }
1936 }
1937 EditorEvent::Edited { .. } => {
1938 if !vim_enabled(cx) {
1939 let (map, selections) = editor.selections.all_adjusted_display(cx);
1940 let pop_state = editor
1941 .change_list
1942 .last()
1943 .map(|previous| {
1944 previous.len() == selections.len()
1945 && previous.iter().enumerate().all(|(ix, p)| {
1946 p.to_display_point(&map).row()
1947 == selections[ix].head().row()
1948 })
1949 })
1950 .unwrap_or(false);
1951 let new_positions = selections
1952 .into_iter()
1953 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1954 .collect();
1955 editor
1956 .change_list
1957 .push_to_change_list(pop_state, new_positions);
1958 }
1959 }
1960 _ => (),
1961 },
1962 ));
1963
1964 if let Some(dap_store) = this
1965 .project
1966 .as_ref()
1967 .map(|project| project.read(cx).dap_store())
1968 {
1969 let weak_editor = cx.weak_entity();
1970
1971 this._subscriptions
1972 .push(
1973 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1974 let session_entity = cx.entity();
1975 weak_editor
1976 .update(cx, |editor, cx| {
1977 editor._subscriptions.push(
1978 cx.subscribe(&session_entity, Self::on_debug_session_event),
1979 );
1980 })
1981 .ok();
1982 }),
1983 );
1984
1985 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1986 this._subscriptions
1987 .push(cx.subscribe(&session, Self::on_debug_session_event));
1988 }
1989 }
1990
1991 this.end_selection(window, cx);
1992 this.scroll_manager.show_scrollbars(window, cx);
1993 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1994
1995 if full_mode {
1996 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1997 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1998
1999 if this.git_blame_inline_enabled {
2000 this.start_git_blame_inline(false, window, cx);
2001 }
2002
2003 this.go_to_active_debug_line(window, cx);
2004
2005 if let Some(buffer) = buffer.read(cx).as_singleton() {
2006 if let Some(project) = this.project.as_ref() {
2007 let handle = project.update(cx, |project, cx| {
2008 project.register_buffer_with_language_servers(&buffer, cx)
2009 });
2010 this.registered_buffers
2011 .insert(buffer.read(cx).remote_id(), handle);
2012 }
2013 }
2014
2015 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2016 }
2017
2018 this.report_editor_event("Editor Opened", None, cx);
2019 this
2020 }
2021
2022 pub fn deploy_mouse_context_menu(
2023 &mut self,
2024 position: gpui::Point<Pixels>,
2025 context_menu: Entity<ContextMenu>,
2026 window: &mut Window,
2027 cx: &mut Context<Self>,
2028 ) {
2029 self.mouse_context_menu = Some(MouseContextMenu::new(
2030 self,
2031 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2032 context_menu,
2033 window,
2034 cx,
2035 ));
2036 }
2037
2038 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2039 self.mouse_context_menu
2040 .as_ref()
2041 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2042 }
2043
2044 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2045 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2046 }
2047
2048 fn key_context_internal(
2049 &self,
2050 has_active_edit_prediction: bool,
2051 window: &Window,
2052 cx: &App,
2053 ) -> KeyContext {
2054 let mut key_context = KeyContext::new_with_defaults();
2055 key_context.add("Editor");
2056 let mode = match self.mode {
2057 EditorMode::SingleLine { .. } => "single_line",
2058 EditorMode::AutoHeight { .. } => "auto_height",
2059 EditorMode::Minimap { .. } => "minimap",
2060 EditorMode::Full { .. } => "full",
2061 };
2062
2063 if EditorSettings::jupyter_enabled(cx) {
2064 key_context.add("jupyter");
2065 }
2066
2067 key_context.set("mode", mode);
2068 if self.pending_rename.is_some() {
2069 key_context.add("renaming");
2070 }
2071
2072 match self.context_menu.borrow().as_ref() {
2073 Some(CodeContextMenu::Completions(_)) => {
2074 key_context.add("menu");
2075 key_context.add("showing_completions");
2076 }
2077 Some(CodeContextMenu::CodeActions(_)) => {
2078 key_context.add("menu");
2079 key_context.add("showing_code_actions")
2080 }
2081 None => {}
2082 }
2083
2084 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2085 if !self.focus_handle(cx).contains_focused(window, cx)
2086 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2087 {
2088 for addon in self.addons.values() {
2089 addon.extend_key_context(&mut key_context, cx)
2090 }
2091 }
2092
2093 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2094 if let Some(extension) = singleton_buffer
2095 .read(cx)
2096 .file()
2097 .and_then(|file| file.path().extension()?.to_str())
2098 {
2099 key_context.set("extension", extension.to_string());
2100 }
2101 } else {
2102 key_context.add("multibuffer");
2103 }
2104
2105 if has_active_edit_prediction {
2106 if self.edit_prediction_in_conflict() {
2107 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2108 } else {
2109 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2110 key_context.add("copilot_suggestion");
2111 }
2112 }
2113
2114 if self.selection_mark_mode {
2115 key_context.add("selection_mode");
2116 }
2117
2118 key_context
2119 }
2120
2121 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2122 self.mouse_cursor_hidden = match origin {
2123 HideMouseCursorOrigin::TypingAction => {
2124 matches!(
2125 self.hide_mouse_mode,
2126 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2127 )
2128 }
2129 HideMouseCursorOrigin::MovementAction => {
2130 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2131 }
2132 };
2133 }
2134
2135 pub fn edit_prediction_in_conflict(&self) -> bool {
2136 if !self.show_edit_predictions_in_menu() {
2137 return false;
2138 }
2139
2140 let showing_completions = self
2141 .context_menu
2142 .borrow()
2143 .as_ref()
2144 .map_or(false, |context| {
2145 matches!(context, CodeContextMenu::Completions(_))
2146 });
2147
2148 showing_completions
2149 || self.edit_prediction_requires_modifier()
2150 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2151 // bindings to insert tab characters.
2152 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2153 }
2154
2155 pub fn accept_edit_prediction_keybind(
2156 &self,
2157 window: &Window,
2158 cx: &App,
2159 ) -> AcceptEditPredictionBinding {
2160 let key_context = self.key_context_internal(true, window, cx);
2161 let in_conflict = self.edit_prediction_in_conflict();
2162
2163 AcceptEditPredictionBinding(
2164 window
2165 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2166 .into_iter()
2167 .filter(|binding| {
2168 !in_conflict
2169 || binding
2170 .keystrokes()
2171 .first()
2172 .map_or(false, |keystroke| keystroke.modifiers.modified())
2173 })
2174 .rev()
2175 .min_by_key(|binding| {
2176 binding
2177 .keystrokes()
2178 .first()
2179 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2180 }),
2181 )
2182 }
2183
2184 pub fn new_file(
2185 workspace: &mut Workspace,
2186 _: &workspace::NewFile,
2187 window: &mut Window,
2188 cx: &mut Context<Workspace>,
2189 ) {
2190 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2191 "Failed to create buffer",
2192 window,
2193 cx,
2194 |e, _, _| match e.error_code() {
2195 ErrorCode::RemoteUpgradeRequired => Some(format!(
2196 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2197 e.error_tag("required").unwrap_or("the latest version")
2198 )),
2199 _ => None,
2200 },
2201 );
2202 }
2203
2204 pub fn new_in_workspace(
2205 workspace: &mut Workspace,
2206 window: &mut Window,
2207 cx: &mut Context<Workspace>,
2208 ) -> Task<Result<Entity<Editor>>> {
2209 let project = workspace.project().clone();
2210 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2211
2212 cx.spawn_in(window, async move |workspace, cx| {
2213 let buffer = create.await?;
2214 workspace.update_in(cx, |workspace, window, cx| {
2215 let editor =
2216 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2217 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2218 editor
2219 })
2220 })
2221 }
2222
2223 fn new_file_vertical(
2224 workspace: &mut Workspace,
2225 _: &workspace::NewFileSplitVertical,
2226 window: &mut Window,
2227 cx: &mut Context<Workspace>,
2228 ) {
2229 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2230 }
2231
2232 fn new_file_horizontal(
2233 workspace: &mut Workspace,
2234 _: &workspace::NewFileSplitHorizontal,
2235 window: &mut Window,
2236 cx: &mut Context<Workspace>,
2237 ) {
2238 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2239 }
2240
2241 fn new_file_in_direction(
2242 workspace: &mut Workspace,
2243 direction: SplitDirection,
2244 window: &mut Window,
2245 cx: &mut Context<Workspace>,
2246 ) {
2247 let project = workspace.project().clone();
2248 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2249
2250 cx.spawn_in(window, async move |workspace, cx| {
2251 let buffer = create.await?;
2252 workspace.update_in(cx, move |workspace, window, cx| {
2253 workspace.split_item(
2254 direction,
2255 Box::new(
2256 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2257 ),
2258 window,
2259 cx,
2260 )
2261 })?;
2262 anyhow::Ok(())
2263 })
2264 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2265 match e.error_code() {
2266 ErrorCode::RemoteUpgradeRequired => Some(format!(
2267 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2268 e.error_tag("required").unwrap_or("the latest version")
2269 )),
2270 _ => None,
2271 }
2272 });
2273 }
2274
2275 pub fn leader_id(&self) -> Option<CollaboratorId> {
2276 self.leader_id
2277 }
2278
2279 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2280 &self.buffer
2281 }
2282
2283 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2284 self.workspace.as_ref()?.0.upgrade()
2285 }
2286
2287 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2288 self.buffer().read(cx).title(cx)
2289 }
2290
2291 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2292 let git_blame_gutter_max_author_length = self
2293 .render_git_blame_gutter(cx)
2294 .then(|| {
2295 if let Some(blame) = self.blame.as_ref() {
2296 let max_author_length =
2297 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2298 Some(max_author_length)
2299 } else {
2300 None
2301 }
2302 })
2303 .flatten();
2304
2305 EditorSnapshot {
2306 mode: self.mode.clone(),
2307 show_gutter: self.show_gutter,
2308 show_line_numbers: self.show_line_numbers,
2309 show_git_diff_gutter: self.show_git_diff_gutter,
2310 show_code_actions: self.show_code_actions,
2311 show_runnables: self.show_runnables,
2312 show_breakpoints: self.show_breakpoints,
2313 git_blame_gutter_max_author_length,
2314 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2315 scroll_anchor: self.scroll_manager.anchor(),
2316 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2317 placeholder_text: self.placeholder_text.clone(),
2318 is_focused: self.focus_handle.is_focused(window),
2319 current_line_highlight: self
2320 .current_line_highlight
2321 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2322 gutter_hovered: self.gutter_hovered,
2323 }
2324 }
2325
2326 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2327 self.buffer.read(cx).language_at(point, cx)
2328 }
2329
2330 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2331 self.buffer.read(cx).read(cx).file_at(point).cloned()
2332 }
2333
2334 pub fn active_excerpt(
2335 &self,
2336 cx: &App,
2337 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2338 self.buffer
2339 .read(cx)
2340 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2341 }
2342
2343 pub fn mode(&self) -> &EditorMode {
2344 &self.mode
2345 }
2346
2347 pub fn set_mode(&mut self, mode: EditorMode) {
2348 self.mode = mode;
2349 }
2350
2351 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2352 self.collaboration_hub.as_deref()
2353 }
2354
2355 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2356 self.collaboration_hub = Some(hub);
2357 }
2358
2359 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2360 self.in_project_search = in_project_search;
2361 }
2362
2363 pub fn set_custom_context_menu(
2364 &mut self,
2365 f: impl 'static
2366 + Fn(
2367 &mut Self,
2368 DisplayPoint,
2369 &mut Window,
2370 &mut Context<Self>,
2371 ) -> Option<Entity<ui::ContextMenu>>,
2372 ) {
2373 self.custom_context_menu = Some(Box::new(f))
2374 }
2375
2376 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2377 self.completion_provider = provider;
2378 }
2379
2380 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2381 self.semantics_provider.clone()
2382 }
2383
2384 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2385 self.semantics_provider = provider;
2386 }
2387
2388 pub fn set_edit_prediction_provider<T>(
2389 &mut self,
2390 provider: Option<Entity<T>>,
2391 window: &mut Window,
2392 cx: &mut Context<Self>,
2393 ) where
2394 T: EditPredictionProvider,
2395 {
2396 self.edit_prediction_provider =
2397 provider.map(|provider| RegisteredInlineCompletionProvider {
2398 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2399 if this.focus_handle.is_focused(window) {
2400 this.update_visible_inline_completion(window, cx);
2401 }
2402 }),
2403 provider: Arc::new(provider),
2404 });
2405 self.update_edit_prediction_settings(cx);
2406 self.refresh_inline_completion(false, false, window, cx);
2407 }
2408
2409 pub fn placeholder_text(&self) -> Option<&str> {
2410 self.placeholder_text.as_deref()
2411 }
2412
2413 pub fn set_placeholder_text(
2414 &mut self,
2415 placeholder_text: impl Into<Arc<str>>,
2416 cx: &mut Context<Self>,
2417 ) {
2418 let placeholder_text = Some(placeholder_text.into());
2419 if self.placeholder_text != placeholder_text {
2420 self.placeholder_text = placeholder_text;
2421 cx.notify();
2422 }
2423 }
2424
2425 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2426 self.cursor_shape = cursor_shape;
2427
2428 // Disrupt blink for immediate user feedback that the cursor shape has changed
2429 self.blink_manager.update(cx, BlinkManager::show_cursor);
2430
2431 cx.notify();
2432 }
2433
2434 pub fn set_current_line_highlight(
2435 &mut self,
2436 current_line_highlight: Option<CurrentLineHighlight>,
2437 ) {
2438 self.current_line_highlight = current_line_highlight;
2439 }
2440
2441 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2442 self.collapse_matches = collapse_matches;
2443 }
2444
2445 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2446 let buffers = self.buffer.read(cx).all_buffers();
2447 let Some(project) = self.project.as_ref() else {
2448 return;
2449 };
2450 project.update(cx, |project, cx| {
2451 for buffer in buffers {
2452 self.registered_buffers
2453 .entry(buffer.read(cx).remote_id())
2454 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2455 }
2456 })
2457 }
2458
2459 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2460 if self.collapse_matches {
2461 return range.start..range.start;
2462 }
2463 range.clone()
2464 }
2465
2466 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2467 if self.display_map.read(cx).clip_at_line_ends != clip {
2468 self.display_map
2469 .update(cx, |map, _| map.clip_at_line_ends = clip);
2470 }
2471 }
2472
2473 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2474 self.input_enabled = input_enabled;
2475 }
2476
2477 pub fn set_inline_completions_hidden_for_vim_mode(
2478 &mut self,
2479 hidden: bool,
2480 window: &mut Window,
2481 cx: &mut Context<Self>,
2482 ) {
2483 if hidden != self.inline_completions_hidden_for_vim_mode {
2484 self.inline_completions_hidden_for_vim_mode = hidden;
2485 if hidden {
2486 self.update_visible_inline_completion(window, cx);
2487 } else {
2488 self.refresh_inline_completion(true, false, window, cx);
2489 }
2490 }
2491 }
2492
2493 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2494 self.menu_inline_completions_policy = value;
2495 }
2496
2497 pub fn set_autoindent(&mut self, autoindent: bool) {
2498 if autoindent {
2499 self.autoindent_mode = Some(AutoindentMode::EachLine);
2500 } else {
2501 self.autoindent_mode = None;
2502 }
2503 }
2504
2505 pub fn read_only(&self, cx: &App) -> bool {
2506 self.read_only || self.buffer.read(cx).read_only()
2507 }
2508
2509 pub fn set_read_only(&mut self, read_only: bool) {
2510 self.read_only = read_only;
2511 }
2512
2513 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2514 self.use_autoclose = autoclose;
2515 }
2516
2517 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2518 self.use_auto_surround = auto_surround;
2519 }
2520
2521 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2522 self.auto_replace_emoji_shortcode = auto_replace;
2523 }
2524
2525 pub fn toggle_edit_predictions(
2526 &mut self,
2527 _: &ToggleEditPrediction,
2528 window: &mut Window,
2529 cx: &mut Context<Self>,
2530 ) {
2531 if self.show_inline_completions_override.is_some() {
2532 self.set_show_edit_predictions(None, window, cx);
2533 } else {
2534 let show_edit_predictions = !self.edit_predictions_enabled();
2535 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2536 }
2537 }
2538
2539 pub fn set_show_edit_predictions(
2540 &mut self,
2541 show_edit_predictions: Option<bool>,
2542 window: &mut Window,
2543 cx: &mut Context<Self>,
2544 ) {
2545 self.show_inline_completions_override = show_edit_predictions;
2546 self.update_edit_prediction_settings(cx);
2547
2548 if let Some(false) = show_edit_predictions {
2549 self.discard_inline_completion(false, cx);
2550 } else {
2551 self.refresh_inline_completion(false, true, window, cx);
2552 }
2553 }
2554
2555 fn inline_completions_disabled_in_scope(
2556 &self,
2557 buffer: &Entity<Buffer>,
2558 buffer_position: language::Anchor,
2559 cx: &App,
2560 ) -> bool {
2561 let snapshot = buffer.read(cx).snapshot();
2562 let settings = snapshot.settings_at(buffer_position, cx);
2563
2564 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2565 return false;
2566 };
2567
2568 scope.override_name().map_or(false, |scope_name| {
2569 settings
2570 .edit_predictions_disabled_in
2571 .iter()
2572 .any(|s| s == scope_name)
2573 })
2574 }
2575
2576 pub fn set_use_modal_editing(&mut self, to: bool) {
2577 self.use_modal_editing = to;
2578 }
2579
2580 pub fn use_modal_editing(&self) -> bool {
2581 self.use_modal_editing
2582 }
2583
2584 fn selections_did_change(
2585 &mut self,
2586 local: bool,
2587 old_cursor_position: &Anchor,
2588 show_completions: bool,
2589 window: &mut Window,
2590 cx: &mut Context<Self>,
2591 ) {
2592 window.invalidate_character_coordinates();
2593
2594 // Copy selections to primary selection buffer
2595 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2596 if local {
2597 let selections = self.selections.all::<usize>(cx);
2598 let buffer_handle = self.buffer.read(cx).read(cx);
2599
2600 let mut text = String::new();
2601 for (index, selection) in selections.iter().enumerate() {
2602 let text_for_selection = buffer_handle
2603 .text_for_range(selection.start..selection.end)
2604 .collect::<String>();
2605
2606 text.push_str(&text_for_selection);
2607 if index != selections.len() - 1 {
2608 text.push('\n');
2609 }
2610 }
2611
2612 if !text.is_empty() {
2613 cx.write_to_primary(ClipboardItem::new_string(text));
2614 }
2615 }
2616
2617 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2618 self.buffer.update(cx, |buffer, cx| {
2619 buffer.set_active_selections(
2620 &self.selections.disjoint_anchors(),
2621 self.selections.line_mode,
2622 self.cursor_shape,
2623 cx,
2624 )
2625 });
2626 }
2627 let display_map = self
2628 .display_map
2629 .update(cx, |display_map, cx| display_map.snapshot(cx));
2630 let buffer = &display_map.buffer_snapshot;
2631 self.add_selections_state = None;
2632 self.select_next_state = None;
2633 self.select_prev_state = None;
2634 self.select_syntax_node_history.try_clear();
2635 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2636 self.snippet_stack
2637 .invalidate(&self.selections.disjoint_anchors(), buffer);
2638 self.take_rename(false, window, cx);
2639
2640 let new_cursor_position = self.selections.newest_anchor().head();
2641
2642 self.push_to_nav_history(
2643 *old_cursor_position,
2644 Some(new_cursor_position.to_point(buffer)),
2645 false,
2646 cx,
2647 );
2648
2649 if local {
2650 let new_cursor_position = self.selections.newest_anchor().head();
2651 let mut context_menu = self.context_menu.borrow_mut();
2652 let completion_menu = match context_menu.as_ref() {
2653 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2654 _ => {
2655 *context_menu = None;
2656 None
2657 }
2658 };
2659 if let Some(buffer_id) = new_cursor_position.buffer_id {
2660 if !self.registered_buffers.contains_key(&buffer_id) {
2661 if let Some(project) = self.project.as_ref() {
2662 project.update(cx, |project, cx| {
2663 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2664 return;
2665 };
2666 self.registered_buffers.insert(
2667 buffer_id,
2668 project.register_buffer_with_language_servers(&buffer, cx),
2669 );
2670 })
2671 }
2672 }
2673 }
2674
2675 if let Some(completion_menu) = completion_menu {
2676 let cursor_position = new_cursor_position.to_offset(buffer);
2677 let (word_range, kind) =
2678 buffer.surrounding_word(completion_menu.initial_position, true);
2679 if kind == Some(CharKind::Word)
2680 && word_range.to_inclusive().contains(&cursor_position)
2681 {
2682 let mut completion_menu = completion_menu.clone();
2683 drop(context_menu);
2684
2685 let query = Self::completion_query(buffer, cursor_position);
2686 cx.spawn(async move |this, cx| {
2687 completion_menu
2688 .filter(query.as_deref(), cx.background_executor().clone())
2689 .await;
2690
2691 this.update(cx, |this, cx| {
2692 let mut context_menu = this.context_menu.borrow_mut();
2693 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2694 else {
2695 return;
2696 };
2697
2698 if menu.id > completion_menu.id {
2699 return;
2700 }
2701
2702 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2703 drop(context_menu);
2704 cx.notify();
2705 })
2706 })
2707 .detach();
2708
2709 if show_completions {
2710 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2711 }
2712 } else {
2713 drop(context_menu);
2714 self.hide_context_menu(window, cx);
2715 }
2716 } else {
2717 drop(context_menu);
2718 }
2719
2720 hide_hover(self, cx);
2721
2722 if old_cursor_position.to_display_point(&display_map).row()
2723 != new_cursor_position.to_display_point(&display_map).row()
2724 {
2725 self.available_code_actions.take();
2726 }
2727 self.refresh_code_actions(window, cx);
2728 self.refresh_document_highlights(cx);
2729 self.refresh_selected_text_highlights(false, window, cx);
2730 refresh_matching_bracket_highlights(self, window, cx);
2731 self.update_visible_inline_completion(window, cx);
2732 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2733 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2734 self.inline_blame_popover.take();
2735 if self.git_blame_inline_enabled {
2736 self.start_inline_blame_timer(window, cx);
2737 }
2738 }
2739
2740 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2741 cx.emit(EditorEvent::SelectionsChanged { local });
2742
2743 let selections = &self.selections.disjoint;
2744 if selections.len() == 1 {
2745 cx.emit(SearchEvent::ActiveMatchChanged)
2746 }
2747 if local {
2748 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2749 let inmemory_selections = selections
2750 .iter()
2751 .map(|s| {
2752 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2753 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2754 })
2755 .collect();
2756 self.update_restoration_data(cx, |data| {
2757 data.selections = inmemory_selections;
2758 });
2759
2760 if WorkspaceSettings::get(None, cx).restore_on_startup
2761 != RestoreOnStartupBehavior::None
2762 {
2763 if let Some(workspace_id) =
2764 self.workspace.as_ref().and_then(|workspace| workspace.1)
2765 {
2766 let snapshot = self.buffer().read(cx).snapshot(cx);
2767 let selections = selections.clone();
2768 let background_executor = cx.background_executor().clone();
2769 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2770 self.serialize_selections = cx.background_spawn(async move {
2771 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2772 let db_selections = selections
2773 .iter()
2774 .map(|selection| {
2775 (
2776 selection.start.to_offset(&snapshot),
2777 selection.end.to_offset(&snapshot),
2778 )
2779 })
2780 .collect();
2781
2782 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2783 .await
2784 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2785 .log_err();
2786 });
2787 }
2788 }
2789 }
2790 }
2791
2792 cx.notify();
2793 }
2794
2795 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2796 use text::ToOffset as _;
2797 use text::ToPoint as _;
2798
2799 if self.mode.is_minimap()
2800 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2801 {
2802 return;
2803 }
2804
2805 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2806 return;
2807 };
2808
2809 let snapshot = singleton.read(cx).snapshot();
2810 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2811 let display_snapshot = display_map.snapshot(cx);
2812
2813 display_snapshot
2814 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2815 .map(|fold| {
2816 fold.range.start.text_anchor.to_point(&snapshot)
2817 ..fold.range.end.text_anchor.to_point(&snapshot)
2818 })
2819 .collect()
2820 });
2821 self.update_restoration_data(cx, |data| {
2822 data.folds = inmemory_folds;
2823 });
2824
2825 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2826 return;
2827 };
2828 let background_executor = cx.background_executor().clone();
2829 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2830 let db_folds = self.display_map.update(cx, |display_map, cx| {
2831 display_map
2832 .snapshot(cx)
2833 .folds_in_range(0..snapshot.len())
2834 .map(|fold| {
2835 (
2836 fold.range.start.text_anchor.to_offset(&snapshot),
2837 fold.range.end.text_anchor.to_offset(&snapshot),
2838 )
2839 })
2840 .collect()
2841 });
2842 self.serialize_folds = cx.background_spawn(async move {
2843 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2844 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2845 .await
2846 .with_context(|| {
2847 format!(
2848 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2849 )
2850 })
2851 .log_err();
2852 });
2853 }
2854
2855 pub fn sync_selections(
2856 &mut self,
2857 other: Entity<Editor>,
2858 cx: &mut Context<Self>,
2859 ) -> gpui::Subscription {
2860 let other_selections = other.read(cx).selections.disjoint.to_vec();
2861 self.selections.change_with(cx, |selections| {
2862 selections.select_anchors(other_selections);
2863 });
2864
2865 let other_subscription =
2866 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2867 EditorEvent::SelectionsChanged { local: true } => {
2868 let other_selections = other.read(cx).selections.disjoint.to_vec();
2869 if other_selections.is_empty() {
2870 return;
2871 }
2872 this.selections.change_with(cx, |selections| {
2873 selections.select_anchors(other_selections);
2874 });
2875 }
2876 _ => {}
2877 });
2878
2879 let this_subscription =
2880 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2881 EditorEvent::SelectionsChanged { local: true } => {
2882 let these_selections = this.selections.disjoint.to_vec();
2883 if these_selections.is_empty() {
2884 return;
2885 }
2886 other.update(cx, |other_editor, cx| {
2887 other_editor.selections.change_with(cx, |selections| {
2888 selections.select_anchors(these_selections);
2889 })
2890 });
2891 }
2892 _ => {}
2893 });
2894
2895 Subscription::join(other_subscription, this_subscription)
2896 }
2897
2898 pub fn change_selections<R>(
2899 &mut self,
2900 autoscroll: Option<Autoscroll>,
2901 window: &mut Window,
2902 cx: &mut Context<Self>,
2903 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2904 ) -> R {
2905 self.change_selections_inner(autoscroll, true, window, cx, change)
2906 }
2907
2908 fn change_selections_inner<R>(
2909 &mut self,
2910 autoscroll: Option<Autoscroll>,
2911 request_completions: bool,
2912 window: &mut Window,
2913 cx: &mut Context<Self>,
2914 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2915 ) -> R {
2916 let old_cursor_position = self.selections.newest_anchor().head();
2917 self.push_to_selection_history();
2918
2919 let (changed, result) = self.selections.change_with(cx, change);
2920
2921 if changed {
2922 if let Some(autoscroll) = autoscroll {
2923 self.request_autoscroll(autoscroll, cx);
2924 }
2925 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2926
2927 if self.should_open_signature_help_automatically(
2928 &old_cursor_position,
2929 self.signature_help_state.backspace_pressed(),
2930 cx,
2931 ) {
2932 self.show_signature_help(&ShowSignatureHelp, window, cx);
2933 }
2934 self.signature_help_state.set_backspace_pressed(false);
2935 }
2936
2937 result
2938 }
2939
2940 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2941 where
2942 I: IntoIterator<Item = (Range<S>, T)>,
2943 S: ToOffset,
2944 T: Into<Arc<str>>,
2945 {
2946 if self.read_only(cx) {
2947 return;
2948 }
2949
2950 self.buffer
2951 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2952 }
2953
2954 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2955 where
2956 I: IntoIterator<Item = (Range<S>, T)>,
2957 S: ToOffset,
2958 T: Into<Arc<str>>,
2959 {
2960 if self.read_only(cx) {
2961 return;
2962 }
2963
2964 self.buffer.update(cx, |buffer, cx| {
2965 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2966 });
2967 }
2968
2969 pub fn edit_with_block_indent<I, S, T>(
2970 &mut self,
2971 edits: I,
2972 original_indent_columns: Vec<Option<u32>>,
2973 cx: &mut Context<Self>,
2974 ) where
2975 I: IntoIterator<Item = (Range<S>, T)>,
2976 S: ToOffset,
2977 T: Into<Arc<str>>,
2978 {
2979 if self.read_only(cx) {
2980 return;
2981 }
2982
2983 self.buffer.update(cx, |buffer, cx| {
2984 buffer.edit(
2985 edits,
2986 Some(AutoindentMode::Block {
2987 original_indent_columns,
2988 }),
2989 cx,
2990 )
2991 });
2992 }
2993
2994 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2995 self.hide_context_menu(window, cx);
2996
2997 match phase {
2998 SelectPhase::Begin {
2999 position,
3000 add,
3001 click_count,
3002 } => self.begin_selection(position, add, click_count, window, cx),
3003 SelectPhase::BeginColumnar {
3004 position,
3005 goal_column,
3006 reset,
3007 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3008 SelectPhase::Extend {
3009 position,
3010 click_count,
3011 } => self.extend_selection(position, click_count, window, cx),
3012 SelectPhase::Update {
3013 position,
3014 goal_column,
3015 scroll_delta,
3016 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3017 SelectPhase::End => self.end_selection(window, cx),
3018 }
3019 }
3020
3021 fn extend_selection(
3022 &mut self,
3023 position: DisplayPoint,
3024 click_count: usize,
3025 window: &mut Window,
3026 cx: &mut Context<Self>,
3027 ) {
3028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3029 let tail = self.selections.newest::<usize>(cx).tail();
3030 self.begin_selection(position, false, click_count, window, cx);
3031
3032 let position = position.to_offset(&display_map, Bias::Left);
3033 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3034
3035 let mut pending_selection = self
3036 .selections
3037 .pending_anchor()
3038 .expect("extend_selection not called with pending selection");
3039 if position >= tail {
3040 pending_selection.start = tail_anchor;
3041 } else {
3042 pending_selection.end = tail_anchor;
3043 pending_selection.reversed = true;
3044 }
3045
3046 let mut pending_mode = self.selections.pending_mode().unwrap();
3047 match &mut pending_mode {
3048 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3049 _ => {}
3050 }
3051
3052 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3053
3054 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3055 s.set_pending(pending_selection, pending_mode)
3056 });
3057 }
3058
3059 fn begin_selection(
3060 &mut self,
3061 position: DisplayPoint,
3062 add: bool,
3063 click_count: usize,
3064 window: &mut Window,
3065 cx: &mut Context<Self>,
3066 ) {
3067 if !self.focus_handle.is_focused(window) {
3068 self.last_focused_descendant = None;
3069 window.focus(&self.focus_handle);
3070 }
3071
3072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3073 let buffer = &display_map.buffer_snapshot;
3074 let position = display_map.clip_point(position, Bias::Left);
3075
3076 let start;
3077 let end;
3078 let mode;
3079 let mut auto_scroll;
3080 match click_count {
3081 1 => {
3082 start = buffer.anchor_before(position.to_point(&display_map));
3083 end = start;
3084 mode = SelectMode::Character;
3085 auto_scroll = true;
3086 }
3087 2 => {
3088 let range = movement::surrounding_word(&display_map, position);
3089 start = buffer.anchor_before(range.start.to_point(&display_map));
3090 end = buffer.anchor_before(range.end.to_point(&display_map));
3091 mode = SelectMode::Word(start..end);
3092 auto_scroll = true;
3093 }
3094 3 => {
3095 let position = display_map
3096 .clip_point(position, Bias::Left)
3097 .to_point(&display_map);
3098 let line_start = display_map.prev_line_boundary(position).0;
3099 let next_line_start = buffer.clip_point(
3100 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3101 Bias::Left,
3102 );
3103 start = buffer.anchor_before(line_start);
3104 end = buffer.anchor_before(next_line_start);
3105 mode = SelectMode::Line(start..end);
3106 auto_scroll = true;
3107 }
3108 _ => {
3109 start = buffer.anchor_before(0);
3110 end = buffer.anchor_before(buffer.len());
3111 mode = SelectMode::All;
3112 auto_scroll = false;
3113 }
3114 }
3115 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3116
3117 let point_to_delete: Option<usize> = {
3118 let selected_points: Vec<Selection<Point>> =
3119 self.selections.disjoint_in_range(start..end, cx);
3120
3121 if !add || click_count > 1 {
3122 None
3123 } else if !selected_points.is_empty() {
3124 Some(selected_points[0].id)
3125 } else {
3126 let clicked_point_already_selected =
3127 self.selections.disjoint.iter().find(|selection| {
3128 selection.start.to_point(buffer) == start.to_point(buffer)
3129 || selection.end.to_point(buffer) == end.to_point(buffer)
3130 });
3131
3132 clicked_point_already_selected.map(|selection| selection.id)
3133 }
3134 };
3135
3136 let selections_count = self.selections.count();
3137
3138 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3139 if let Some(point_to_delete) = point_to_delete {
3140 s.delete(point_to_delete);
3141
3142 if selections_count == 1 {
3143 s.set_pending_anchor_range(start..end, mode);
3144 }
3145 } else {
3146 if !add {
3147 s.clear_disjoint();
3148 }
3149
3150 s.set_pending_anchor_range(start..end, mode);
3151 }
3152 });
3153 }
3154
3155 fn begin_columnar_selection(
3156 &mut self,
3157 position: DisplayPoint,
3158 goal_column: u32,
3159 reset: bool,
3160 window: &mut Window,
3161 cx: &mut Context<Self>,
3162 ) {
3163 if !self.focus_handle.is_focused(window) {
3164 self.last_focused_descendant = None;
3165 window.focus(&self.focus_handle);
3166 }
3167
3168 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3169
3170 if reset {
3171 let pointer_position = display_map
3172 .buffer_snapshot
3173 .anchor_before(position.to_point(&display_map));
3174
3175 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3176 s.clear_disjoint();
3177 s.set_pending_anchor_range(
3178 pointer_position..pointer_position,
3179 SelectMode::Character,
3180 );
3181 });
3182 }
3183
3184 let tail = self.selections.newest::<Point>(cx).tail();
3185 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3186
3187 if !reset {
3188 self.select_columns(
3189 tail.to_display_point(&display_map),
3190 position,
3191 goal_column,
3192 &display_map,
3193 window,
3194 cx,
3195 );
3196 }
3197 }
3198
3199 fn update_selection(
3200 &mut self,
3201 position: DisplayPoint,
3202 goal_column: u32,
3203 scroll_delta: gpui::Point<f32>,
3204 window: &mut Window,
3205 cx: &mut Context<Self>,
3206 ) {
3207 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3208
3209 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3210 let tail = tail.to_display_point(&display_map);
3211 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3212 } else if let Some(mut pending) = self.selections.pending_anchor() {
3213 let buffer = self.buffer.read(cx).snapshot(cx);
3214 let head;
3215 let tail;
3216 let mode = self.selections.pending_mode().unwrap();
3217 match &mode {
3218 SelectMode::Character => {
3219 head = position.to_point(&display_map);
3220 tail = pending.tail().to_point(&buffer);
3221 }
3222 SelectMode::Word(original_range) => {
3223 let original_display_range = original_range.start.to_display_point(&display_map)
3224 ..original_range.end.to_display_point(&display_map);
3225 let original_buffer_range = original_display_range.start.to_point(&display_map)
3226 ..original_display_range.end.to_point(&display_map);
3227 if movement::is_inside_word(&display_map, position)
3228 || original_display_range.contains(&position)
3229 {
3230 let word_range = movement::surrounding_word(&display_map, position);
3231 if word_range.start < original_display_range.start {
3232 head = word_range.start.to_point(&display_map);
3233 } else {
3234 head = word_range.end.to_point(&display_map);
3235 }
3236 } else {
3237 head = position.to_point(&display_map);
3238 }
3239
3240 if head <= original_buffer_range.start {
3241 tail = original_buffer_range.end;
3242 } else {
3243 tail = original_buffer_range.start;
3244 }
3245 }
3246 SelectMode::Line(original_range) => {
3247 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3248
3249 let position = display_map
3250 .clip_point(position, Bias::Left)
3251 .to_point(&display_map);
3252 let line_start = display_map.prev_line_boundary(position).0;
3253 let next_line_start = buffer.clip_point(
3254 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3255 Bias::Left,
3256 );
3257
3258 if line_start < original_range.start {
3259 head = line_start
3260 } else {
3261 head = next_line_start
3262 }
3263
3264 if head <= original_range.start {
3265 tail = original_range.end;
3266 } else {
3267 tail = original_range.start;
3268 }
3269 }
3270 SelectMode::All => {
3271 return;
3272 }
3273 };
3274
3275 if head < tail {
3276 pending.start = buffer.anchor_before(head);
3277 pending.end = buffer.anchor_before(tail);
3278 pending.reversed = true;
3279 } else {
3280 pending.start = buffer.anchor_before(tail);
3281 pending.end = buffer.anchor_before(head);
3282 pending.reversed = false;
3283 }
3284
3285 self.change_selections(None, window, cx, |s| {
3286 s.set_pending(pending, mode);
3287 });
3288 } else {
3289 log::error!("update_selection dispatched with no pending selection");
3290 return;
3291 }
3292
3293 self.apply_scroll_delta(scroll_delta, window, cx);
3294 cx.notify();
3295 }
3296
3297 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3298 self.columnar_selection_tail.take();
3299 if self.selections.pending_anchor().is_some() {
3300 let selections = self.selections.all::<usize>(cx);
3301 self.change_selections(None, window, cx, |s| {
3302 s.select(selections);
3303 s.clear_pending();
3304 });
3305 }
3306 }
3307
3308 fn select_columns(
3309 &mut self,
3310 tail: DisplayPoint,
3311 head: DisplayPoint,
3312 goal_column: u32,
3313 display_map: &DisplaySnapshot,
3314 window: &mut Window,
3315 cx: &mut Context<Self>,
3316 ) {
3317 let start_row = cmp::min(tail.row(), head.row());
3318 let end_row = cmp::max(tail.row(), head.row());
3319 let start_column = cmp::min(tail.column(), goal_column);
3320 let end_column = cmp::max(tail.column(), goal_column);
3321 let reversed = start_column < tail.column();
3322
3323 let selection_ranges = (start_row.0..=end_row.0)
3324 .map(DisplayRow)
3325 .filter_map(|row| {
3326 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3327 let start = display_map
3328 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3329 .to_point(display_map);
3330 let end = display_map
3331 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3332 .to_point(display_map);
3333 if reversed {
3334 Some(end..start)
3335 } else {
3336 Some(start..end)
3337 }
3338 } else {
3339 None
3340 }
3341 })
3342 .collect::<Vec<_>>();
3343
3344 self.change_selections(None, window, cx, |s| {
3345 s.select_ranges(selection_ranges);
3346 });
3347 cx.notify();
3348 }
3349
3350 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3351 self.selections
3352 .all_adjusted(cx)
3353 .iter()
3354 .any(|selection| !selection.is_empty())
3355 }
3356
3357 pub fn has_pending_nonempty_selection(&self) -> bool {
3358 let pending_nonempty_selection = match self.selections.pending_anchor() {
3359 Some(Selection { start, end, .. }) => start != end,
3360 None => false,
3361 };
3362
3363 pending_nonempty_selection
3364 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3365 }
3366
3367 pub fn has_pending_selection(&self) -> bool {
3368 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3369 }
3370
3371 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3372 self.selection_mark_mode = false;
3373
3374 if self.clear_expanded_diff_hunks(cx) {
3375 cx.notify();
3376 return;
3377 }
3378 if self.dismiss_menus_and_popups(true, window, cx) {
3379 return;
3380 }
3381
3382 if self.mode.is_full()
3383 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3384 {
3385 return;
3386 }
3387
3388 cx.propagate();
3389 }
3390
3391 pub fn dismiss_menus_and_popups(
3392 &mut self,
3393 is_user_requested: bool,
3394 window: &mut Window,
3395 cx: &mut Context<Self>,
3396 ) -> bool {
3397 if self.take_rename(false, window, cx).is_some() {
3398 return true;
3399 }
3400
3401 if hide_hover(self, cx) {
3402 return true;
3403 }
3404
3405 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3406 return true;
3407 }
3408
3409 if self.hide_context_menu(window, cx).is_some() {
3410 return true;
3411 }
3412
3413 if self.mouse_context_menu.take().is_some() {
3414 return true;
3415 }
3416
3417 if is_user_requested && self.discard_inline_completion(true, cx) {
3418 return true;
3419 }
3420
3421 if self.snippet_stack.pop().is_some() {
3422 return true;
3423 }
3424
3425 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3426 self.dismiss_diagnostics(cx);
3427 return true;
3428 }
3429
3430 false
3431 }
3432
3433 fn linked_editing_ranges_for(
3434 &self,
3435 selection: Range<text::Anchor>,
3436 cx: &App,
3437 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3438 if self.linked_edit_ranges.is_empty() {
3439 return None;
3440 }
3441 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3442 selection.end.buffer_id.and_then(|end_buffer_id| {
3443 if selection.start.buffer_id != Some(end_buffer_id) {
3444 return None;
3445 }
3446 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3447 let snapshot = buffer.read(cx).snapshot();
3448 self.linked_edit_ranges
3449 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3450 .map(|ranges| (ranges, snapshot, buffer))
3451 })?;
3452 use text::ToOffset as TO;
3453 // find offset from the start of current range to current cursor position
3454 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3455
3456 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3457 let start_difference = start_offset - start_byte_offset;
3458 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3459 let end_difference = end_offset - start_byte_offset;
3460 // Current range has associated linked ranges.
3461 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3462 for range in linked_ranges.iter() {
3463 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3464 let end_offset = start_offset + end_difference;
3465 let start_offset = start_offset + start_difference;
3466 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3467 continue;
3468 }
3469 if self.selections.disjoint_anchor_ranges().any(|s| {
3470 if s.start.buffer_id != selection.start.buffer_id
3471 || s.end.buffer_id != selection.end.buffer_id
3472 {
3473 return false;
3474 }
3475 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3476 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3477 }) {
3478 continue;
3479 }
3480 let start = buffer_snapshot.anchor_after(start_offset);
3481 let end = buffer_snapshot.anchor_after(end_offset);
3482 linked_edits
3483 .entry(buffer.clone())
3484 .or_default()
3485 .push(start..end);
3486 }
3487 Some(linked_edits)
3488 }
3489
3490 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3491 let text: Arc<str> = text.into();
3492
3493 if self.read_only(cx) {
3494 return;
3495 }
3496
3497 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3498
3499 let selections = self.selections.all_adjusted(cx);
3500 let mut bracket_inserted = false;
3501 let mut edits = Vec::new();
3502 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3503 let mut new_selections = Vec::with_capacity(selections.len());
3504 let mut new_autoclose_regions = Vec::new();
3505 let snapshot = self.buffer.read(cx).read(cx);
3506 let mut clear_linked_edit_ranges = false;
3507
3508 for (selection, autoclose_region) in
3509 self.selections_with_autoclose_regions(selections, &snapshot)
3510 {
3511 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3512 // Determine if the inserted text matches the opening or closing
3513 // bracket of any of this language's bracket pairs.
3514 let mut bracket_pair = None;
3515 let mut is_bracket_pair_start = false;
3516 let mut is_bracket_pair_end = false;
3517 if !text.is_empty() {
3518 let mut bracket_pair_matching_end = None;
3519 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3520 // and they are removing the character that triggered IME popup.
3521 for (pair, enabled) in scope.brackets() {
3522 if !pair.close && !pair.surround {
3523 continue;
3524 }
3525
3526 if enabled && pair.start.ends_with(text.as_ref()) {
3527 let prefix_len = pair.start.len() - text.len();
3528 let preceding_text_matches_prefix = prefix_len == 0
3529 || (selection.start.column >= (prefix_len as u32)
3530 && snapshot.contains_str_at(
3531 Point::new(
3532 selection.start.row,
3533 selection.start.column - (prefix_len as u32),
3534 ),
3535 &pair.start[..prefix_len],
3536 ));
3537 if preceding_text_matches_prefix {
3538 bracket_pair = Some(pair.clone());
3539 is_bracket_pair_start = true;
3540 break;
3541 }
3542 }
3543 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3544 {
3545 // take first bracket pair matching end, but don't break in case a later bracket
3546 // pair matches start
3547 bracket_pair_matching_end = Some(pair.clone());
3548 }
3549 }
3550 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3551 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3552 is_bracket_pair_end = true;
3553 }
3554 }
3555
3556 if let Some(bracket_pair) = bracket_pair {
3557 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3558 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3559 let auto_surround =
3560 self.use_auto_surround && snapshot_settings.use_auto_surround;
3561 if selection.is_empty() {
3562 if is_bracket_pair_start {
3563 // If the inserted text is a suffix of an opening bracket and the
3564 // selection is preceded by the rest of the opening bracket, then
3565 // insert the closing bracket.
3566 let following_text_allows_autoclose = snapshot
3567 .chars_at(selection.start)
3568 .next()
3569 .map_or(true, |c| scope.should_autoclose_before(c));
3570
3571 let preceding_text_allows_autoclose = selection.start.column == 0
3572 || snapshot.reversed_chars_at(selection.start).next().map_or(
3573 true,
3574 |c| {
3575 bracket_pair.start != bracket_pair.end
3576 || !snapshot
3577 .char_classifier_at(selection.start)
3578 .is_word(c)
3579 },
3580 );
3581
3582 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3583 && bracket_pair.start.len() == 1
3584 {
3585 let target = bracket_pair.start.chars().next().unwrap();
3586 let current_line_count = snapshot
3587 .reversed_chars_at(selection.start)
3588 .take_while(|&c| c != '\n')
3589 .filter(|&c| c == target)
3590 .count();
3591 current_line_count % 2 == 1
3592 } else {
3593 false
3594 };
3595
3596 if autoclose
3597 && bracket_pair.close
3598 && following_text_allows_autoclose
3599 && preceding_text_allows_autoclose
3600 && !is_closing_quote
3601 {
3602 let anchor = snapshot.anchor_before(selection.end);
3603 new_selections.push((selection.map(|_| anchor), text.len()));
3604 new_autoclose_regions.push((
3605 anchor,
3606 text.len(),
3607 selection.id,
3608 bracket_pair.clone(),
3609 ));
3610 edits.push((
3611 selection.range(),
3612 format!("{}{}", text, bracket_pair.end).into(),
3613 ));
3614 bracket_inserted = true;
3615 continue;
3616 }
3617 }
3618
3619 if let Some(region) = autoclose_region {
3620 // If the selection is followed by an auto-inserted closing bracket,
3621 // then don't insert that closing bracket again; just move the selection
3622 // past the closing bracket.
3623 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3624 && text.as_ref() == region.pair.end.as_str();
3625 if should_skip {
3626 let anchor = snapshot.anchor_after(selection.end);
3627 new_selections
3628 .push((selection.map(|_| anchor), region.pair.end.len()));
3629 continue;
3630 }
3631 }
3632
3633 let always_treat_brackets_as_autoclosed = snapshot
3634 .language_settings_at(selection.start, cx)
3635 .always_treat_brackets_as_autoclosed;
3636 if always_treat_brackets_as_autoclosed
3637 && is_bracket_pair_end
3638 && snapshot.contains_str_at(selection.end, text.as_ref())
3639 {
3640 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3641 // and the inserted text is a closing bracket and the selection is followed
3642 // by the closing bracket then move the selection past the closing bracket.
3643 let anchor = snapshot.anchor_after(selection.end);
3644 new_selections.push((selection.map(|_| anchor), text.len()));
3645 continue;
3646 }
3647 }
3648 // If an opening bracket is 1 character long and is typed while
3649 // text is selected, then surround that text with the bracket pair.
3650 else if auto_surround
3651 && bracket_pair.surround
3652 && is_bracket_pair_start
3653 && bracket_pair.start.chars().count() == 1
3654 {
3655 edits.push((selection.start..selection.start, text.clone()));
3656 edits.push((
3657 selection.end..selection.end,
3658 bracket_pair.end.as_str().into(),
3659 ));
3660 bracket_inserted = true;
3661 new_selections.push((
3662 Selection {
3663 id: selection.id,
3664 start: snapshot.anchor_after(selection.start),
3665 end: snapshot.anchor_before(selection.end),
3666 reversed: selection.reversed,
3667 goal: selection.goal,
3668 },
3669 0,
3670 ));
3671 continue;
3672 }
3673 }
3674 }
3675
3676 if self.auto_replace_emoji_shortcode
3677 && selection.is_empty()
3678 && text.as_ref().ends_with(':')
3679 {
3680 if let Some(possible_emoji_short_code) =
3681 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3682 {
3683 if !possible_emoji_short_code.is_empty() {
3684 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3685 let emoji_shortcode_start = Point::new(
3686 selection.start.row,
3687 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3688 );
3689
3690 // Remove shortcode from buffer
3691 edits.push((
3692 emoji_shortcode_start..selection.start,
3693 "".to_string().into(),
3694 ));
3695 new_selections.push((
3696 Selection {
3697 id: selection.id,
3698 start: snapshot.anchor_after(emoji_shortcode_start),
3699 end: snapshot.anchor_before(selection.start),
3700 reversed: selection.reversed,
3701 goal: selection.goal,
3702 },
3703 0,
3704 ));
3705
3706 // Insert emoji
3707 let selection_start_anchor = snapshot.anchor_after(selection.start);
3708 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3709 edits.push((selection.start..selection.end, emoji.to_string().into()));
3710
3711 continue;
3712 }
3713 }
3714 }
3715 }
3716
3717 // If not handling any auto-close operation, then just replace the selected
3718 // text with the given input and move the selection to the end of the
3719 // newly inserted text.
3720 let anchor = snapshot.anchor_after(selection.end);
3721 if !self.linked_edit_ranges.is_empty() {
3722 let start_anchor = snapshot.anchor_before(selection.start);
3723
3724 let is_word_char = text.chars().next().map_or(true, |char| {
3725 let classifier = snapshot
3726 .char_classifier_at(start_anchor.to_offset(&snapshot))
3727 .ignore_punctuation(true);
3728 classifier.is_word(char)
3729 });
3730
3731 if is_word_char {
3732 if let Some(ranges) = self
3733 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3734 {
3735 for (buffer, edits) in ranges {
3736 linked_edits
3737 .entry(buffer.clone())
3738 .or_default()
3739 .extend(edits.into_iter().map(|range| (range, text.clone())));
3740 }
3741 }
3742 } else {
3743 clear_linked_edit_ranges = true;
3744 }
3745 }
3746
3747 new_selections.push((selection.map(|_| anchor), 0));
3748 edits.push((selection.start..selection.end, text.clone()));
3749 }
3750
3751 drop(snapshot);
3752
3753 self.transact(window, cx, |this, window, cx| {
3754 if clear_linked_edit_ranges {
3755 this.linked_edit_ranges.clear();
3756 }
3757 let initial_buffer_versions =
3758 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3759
3760 this.buffer.update(cx, |buffer, cx| {
3761 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3762 });
3763 for (buffer, edits) in linked_edits {
3764 buffer.update(cx, |buffer, cx| {
3765 let snapshot = buffer.snapshot();
3766 let edits = edits
3767 .into_iter()
3768 .map(|(range, text)| {
3769 use text::ToPoint as TP;
3770 let end_point = TP::to_point(&range.end, &snapshot);
3771 let start_point = TP::to_point(&range.start, &snapshot);
3772 (start_point..end_point, text)
3773 })
3774 .sorted_by_key(|(range, _)| range.start);
3775 buffer.edit(edits, None, cx);
3776 })
3777 }
3778 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3779 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3780 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3781 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3782 .zip(new_selection_deltas)
3783 .map(|(selection, delta)| Selection {
3784 id: selection.id,
3785 start: selection.start + delta,
3786 end: selection.end + delta,
3787 reversed: selection.reversed,
3788 goal: SelectionGoal::None,
3789 })
3790 .collect::<Vec<_>>();
3791
3792 let mut i = 0;
3793 for (position, delta, selection_id, pair) in new_autoclose_regions {
3794 let position = position.to_offset(&map.buffer_snapshot) + delta;
3795 let start = map.buffer_snapshot.anchor_before(position);
3796 let end = map.buffer_snapshot.anchor_after(position);
3797 while let Some(existing_state) = this.autoclose_regions.get(i) {
3798 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3799 Ordering::Less => i += 1,
3800 Ordering::Greater => break,
3801 Ordering::Equal => {
3802 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3803 Ordering::Less => i += 1,
3804 Ordering::Equal => break,
3805 Ordering::Greater => break,
3806 }
3807 }
3808 }
3809 }
3810 this.autoclose_regions.insert(
3811 i,
3812 AutocloseRegion {
3813 selection_id,
3814 range: start..end,
3815 pair,
3816 },
3817 );
3818 }
3819
3820 let had_active_inline_completion = this.has_active_inline_completion();
3821 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3822 s.select(new_selections)
3823 });
3824
3825 if !bracket_inserted {
3826 if let Some(on_type_format_task) =
3827 this.trigger_on_type_formatting(text.to_string(), window, cx)
3828 {
3829 on_type_format_task.detach_and_log_err(cx);
3830 }
3831 }
3832
3833 let editor_settings = EditorSettings::get_global(cx);
3834 if bracket_inserted
3835 && (editor_settings.auto_signature_help
3836 || editor_settings.show_signature_help_after_edits)
3837 {
3838 this.show_signature_help(&ShowSignatureHelp, window, cx);
3839 }
3840
3841 let trigger_in_words =
3842 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3843 if this.hard_wrap.is_some() {
3844 let latest: Range<Point> = this.selections.newest(cx).range();
3845 if latest.is_empty()
3846 && this
3847 .buffer()
3848 .read(cx)
3849 .snapshot(cx)
3850 .line_len(MultiBufferRow(latest.start.row))
3851 == latest.start.column
3852 {
3853 this.rewrap_impl(
3854 RewrapOptions {
3855 override_language_settings: true,
3856 preserve_existing_whitespace: true,
3857 },
3858 cx,
3859 )
3860 }
3861 }
3862 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3863 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3864 this.refresh_inline_completion(true, false, window, cx);
3865 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3866 });
3867 }
3868
3869 fn find_possible_emoji_shortcode_at_position(
3870 snapshot: &MultiBufferSnapshot,
3871 position: Point,
3872 ) -> Option<String> {
3873 let mut chars = Vec::new();
3874 let mut found_colon = false;
3875 for char in snapshot.reversed_chars_at(position).take(100) {
3876 // Found a possible emoji shortcode in the middle of the buffer
3877 if found_colon {
3878 if char.is_whitespace() {
3879 chars.reverse();
3880 return Some(chars.iter().collect());
3881 }
3882 // If the previous character is not a whitespace, we are in the middle of a word
3883 // and we only want to complete the shortcode if the word is made up of other emojis
3884 let mut containing_word = String::new();
3885 for ch in snapshot
3886 .reversed_chars_at(position)
3887 .skip(chars.len() + 1)
3888 .take(100)
3889 {
3890 if ch.is_whitespace() {
3891 break;
3892 }
3893 containing_word.push(ch);
3894 }
3895 let containing_word = containing_word.chars().rev().collect::<String>();
3896 if util::word_consists_of_emojis(containing_word.as_str()) {
3897 chars.reverse();
3898 return Some(chars.iter().collect());
3899 }
3900 }
3901
3902 if char.is_whitespace() || !char.is_ascii() {
3903 return None;
3904 }
3905 if char == ':' {
3906 found_colon = true;
3907 } else {
3908 chars.push(char);
3909 }
3910 }
3911 // Found a possible emoji shortcode at the beginning of the buffer
3912 chars.reverse();
3913 Some(chars.iter().collect())
3914 }
3915
3916 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3917 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3918 self.transact(window, cx, |this, window, cx| {
3919 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3920 let selections = this.selections.all::<usize>(cx);
3921 let multi_buffer = this.buffer.read(cx);
3922 let buffer = multi_buffer.snapshot(cx);
3923 selections
3924 .iter()
3925 .map(|selection| {
3926 let start_point = selection.start.to_point(&buffer);
3927 let mut existing_indent =
3928 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3929 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3930 let start = selection.start;
3931 let end = selection.end;
3932 let selection_is_empty = start == end;
3933 let language_scope = buffer.language_scope_at(start);
3934 let (
3935 comment_delimiter,
3936 doc_delimiter,
3937 insert_extra_newline,
3938 indent_on_newline,
3939 indent_on_extra_newline,
3940 ) = if let Some(language) = &language_scope {
3941 let mut insert_extra_newline =
3942 insert_extra_newline_brackets(&buffer, start..end, language)
3943 || insert_extra_newline_tree_sitter(&buffer, start..end);
3944
3945 // Comment extension on newline is allowed only for cursor selections
3946 let comment_delimiter = maybe!({
3947 if !selection_is_empty {
3948 return None;
3949 }
3950
3951 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3952 return None;
3953 }
3954
3955 let delimiters = language.line_comment_prefixes();
3956 let max_len_of_delimiter =
3957 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3958 let (snapshot, range) =
3959 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3960
3961 let num_of_whitespaces = snapshot
3962 .chars_for_range(range.clone())
3963 .take_while(|c| c.is_whitespace())
3964 .count();
3965 let comment_candidate = snapshot
3966 .chars_for_range(range)
3967 .skip(num_of_whitespaces)
3968 .take(max_len_of_delimiter)
3969 .collect::<String>();
3970 let (delimiter, trimmed_len) = delimiters
3971 .iter()
3972 .filter_map(|delimiter| {
3973 let prefix = delimiter.trim_end();
3974 if comment_candidate.starts_with(prefix) {
3975 Some((delimiter, prefix.len()))
3976 } else {
3977 None
3978 }
3979 })
3980 .max_by_key(|(_, len)| *len)?;
3981
3982 let cursor_is_placed_after_comment_marker =
3983 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3984 if cursor_is_placed_after_comment_marker {
3985 Some(delimiter.clone())
3986 } else {
3987 None
3988 }
3989 });
3990
3991 let mut indent_on_newline = IndentSize::spaces(0);
3992 let mut indent_on_extra_newline = IndentSize::spaces(0);
3993
3994 let doc_delimiter = maybe!({
3995 if !selection_is_empty {
3996 return None;
3997 }
3998
3999 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4000 return None;
4001 }
4002
4003 let DocumentationConfig {
4004 start: start_tag,
4005 end: end_tag,
4006 prefix: delimiter,
4007 tab_size: len,
4008 } = language.documentation()?;
4009
4010 let is_within_block_comment = buffer
4011 .language_scope_at(start_point)
4012 .is_some_and(|scope| scope.override_name() == Some("comment"));
4013 if !is_within_block_comment {
4014 return None;
4015 }
4016
4017 let (snapshot, range) =
4018 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4019
4020 let num_of_whitespaces = snapshot
4021 .chars_for_range(range.clone())
4022 .take_while(|c| c.is_whitespace())
4023 .count();
4024
4025 // 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.
4026 let column = start_point.column;
4027 let cursor_is_after_start_tag = {
4028 let start_tag_len = start_tag.len();
4029 let start_tag_line = snapshot
4030 .chars_for_range(range.clone())
4031 .skip(num_of_whitespaces)
4032 .take(start_tag_len)
4033 .collect::<String>();
4034 if start_tag_line.starts_with(start_tag.as_ref()) {
4035 num_of_whitespaces + start_tag_len <= column as usize
4036 } else {
4037 false
4038 }
4039 };
4040
4041 let cursor_is_after_delimiter = {
4042 let delimiter_trim = delimiter.trim_end();
4043 let delimiter_line = snapshot
4044 .chars_for_range(range.clone())
4045 .skip(num_of_whitespaces)
4046 .take(delimiter_trim.len())
4047 .collect::<String>();
4048 if delimiter_line.starts_with(delimiter_trim) {
4049 num_of_whitespaces + delimiter_trim.len() <= column as usize
4050 } else {
4051 false
4052 }
4053 };
4054
4055 let cursor_is_before_end_tag_if_exists = {
4056 let mut char_position = 0u32;
4057 let mut end_tag_offset = None;
4058
4059 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4060 if let Some(byte_pos) = chunk.find(&**end_tag) {
4061 let chars_before_match =
4062 chunk[..byte_pos].chars().count() as u32;
4063 end_tag_offset =
4064 Some(char_position + chars_before_match);
4065 break 'outer;
4066 }
4067 char_position += chunk.chars().count() as u32;
4068 }
4069
4070 if let Some(end_tag_offset) = end_tag_offset {
4071 let cursor_is_before_end_tag = column <= end_tag_offset;
4072 if cursor_is_after_start_tag {
4073 if cursor_is_before_end_tag {
4074 insert_extra_newline = true;
4075 }
4076 let cursor_is_at_start_of_end_tag =
4077 column == end_tag_offset;
4078 if cursor_is_at_start_of_end_tag {
4079 indent_on_extra_newline.len = (*len).into();
4080 }
4081 }
4082 cursor_is_before_end_tag
4083 } else {
4084 true
4085 }
4086 };
4087
4088 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4089 && cursor_is_before_end_tag_if_exists
4090 {
4091 if cursor_is_after_start_tag {
4092 indent_on_newline.len = (*len).into();
4093 }
4094 Some(delimiter.clone())
4095 } else {
4096 None
4097 }
4098 });
4099
4100 (
4101 comment_delimiter,
4102 doc_delimiter,
4103 insert_extra_newline,
4104 indent_on_newline,
4105 indent_on_extra_newline,
4106 )
4107 } else {
4108 (
4109 None,
4110 None,
4111 false,
4112 IndentSize::default(),
4113 IndentSize::default(),
4114 )
4115 };
4116
4117 let prevent_auto_indent = doc_delimiter.is_some();
4118 let delimiter = comment_delimiter.or(doc_delimiter);
4119
4120 let capacity_for_delimiter =
4121 delimiter.as_deref().map(str::len).unwrap_or_default();
4122 let mut new_text = String::with_capacity(
4123 1 + capacity_for_delimiter
4124 + existing_indent.len as usize
4125 + indent_on_newline.len as usize
4126 + indent_on_extra_newline.len as usize,
4127 );
4128 new_text.push('\n');
4129 new_text.extend(existing_indent.chars());
4130 new_text.extend(indent_on_newline.chars());
4131
4132 if let Some(delimiter) = &delimiter {
4133 new_text.push_str(delimiter);
4134 }
4135
4136 if insert_extra_newline {
4137 new_text.push('\n');
4138 new_text.extend(existing_indent.chars());
4139 new_text.extend(indent_on_extra_newline.chars());
4140 }
4141
4142 let anchor = buffer.anchor_after(end);
4143 let new_selection = selection.map(|_| anchor);
4144 (
4145 ((start..end, new_text), prevent_auto_indent),
4146 (insert_extra_newline, new_selection),
4147 )
4148 })
4149 .unzip()
4150 };
4151
4152 let mut auto_indent_edits = Vec::new();
4153 let mut edits = Vec::new();
4154 for (edit, prevent_auto_indent) in edits_with_flags {
4155 if prevent_auto_indent {
4156 edits.push(edit);
4157 } else {
4158 auto_indent_edits.push(edit);
4159 }
4160 }
4161 if !edits.is_empty() {
4162 this.edit(edits, cx);
4163 }
4164 if !auto_indent_edits.is_empty() {
4165 this.edit_with_autoindent(auto_indent_edits, cx);
4166 }
4167
4168 let buffer = this.buffer.read(cx).snapshot(cx);
4169 let new_selections = selection_info
4170 .into_iter()
4171 .map(|(extra_newline_inserted, new_selection)| {
4172 let mut cursor = new_selection.end.to_point(&buffer);
4173 if extra_newline_inserted {
4174 cursor.row -= 1;
4175 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4176 }
4177 new_selection.map(|_| cursor)
4178 })
4179 .collect();
4180
4181 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4182 s.select(new_selections)
4183 });
4184 this.refresh_inline_completion(true, false, window, cx);
4185 });
4186 }
4187
4188 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4189 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4190
4191 let buffer = self.buffer.read(cx);
4192 let snapshot = buffer.snapshot(cx);
4193
4194 let mut edits = Vec::new();
4195 let mut rows = Vec::new();
4196
4197 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4198 let cursor = selection.head();
4199 let row = cursor.row;
4200
4201 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4202
4203 let newline = "\n".to_string();
4204 edits.push((start_of_line..start_of_line, newline));
4205
4206 rows.push(row + rows_inserted as u32);
4207 }
4208
4209 self.transact(window, cx, |editor, window, cx| {
4210 editor.edit(edits, cx);
4211
4212 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4213 let mut index = 0;
4214 s.move_cursors_with(|map, _, _| {
4215 let row = rows[index];
4216 index += 1;
4217
4218 let point = Point::new(row, 0);
4219 let boundary = map.next_line_boundary(point).1;
4220 let clipped = map.clip_point(boundary, Bias::Left);
4221
4222 (clipped, SelectionGoal::None)
4223 });
4224 });
4225
4226 let mut indent_edits = Vec::new();
4227 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4228 for row in rows {
4229 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4230 for (row, indent) in indents {
4231 if indent.len == 0 {
4232 continue;
4233 }
4234
4235 let text = match indent.kind {
4236 IndentKind::Space => " ".repeat(indent.len as usize),
4237 IndentKind::Tab => "\t".repeat(indent.len as usize),
4238 };
4239 let point = Point::new(row.0, 0);
4240 indent_edits.push((point..point, text));
4241 }
4242 }
4243 editor.edit(indent_edits, cx);
4244 });
4245 }
4246
4247 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4248 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4249
4250 let buffer = self.buffer.read(cx);
4251 let snapshot = buffer.snapshot(cx);
4252
4253 let mut edits = Vec::new();
4254 let mut rows = Vec::new();
4255 let mut rows_inserted = 0;
4256
4257 for selection in self.selections.all_adjusted(cx) {
4258 let cursor = selection.head();
4259 let row = cursor.row;
4260
4261 let point = Point::new(row + 1, 0);
4262 let start_of_line = snapshot.clip_point(point, Bias::Left);
4263
4264 let newline = "\n".to_string();
4265 edits.push((start_of_line..start_of_line, newline));
4266
4267 rows_inserted += 1;
4268 rows.push(row + rows_inserted);
4269 }
4270
4271 self.transact(window, cx, |editor, window, cx| {
4272 editor.edit(edits, cx);
4273
4274 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4275 let mut index = 0;
4276 s.move_cursors_with(|map, _, _| {
4277 let row = rows[index];
4278 index += 1;
4279
4280 let point = Point::new(row, 0);
4281 let boundary = map.next_line_boundary(point).1;
4282 let clipped = map.clip_point(boundary, Bias::Left);
4283
4284 (clipped, SelectionGoal::None)
4285 });
4286 });
4287
4288 let mut indent_edits = Vec::new();
4289 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4290 for row in rows {
4291 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4292 for (row, indent) in indents {
4293 if indent.len == 0 {
4294 continue;
4295 }
4296
4297 let text = match indent.kind {
4298 IndentKind::Space => " ".repeat(indent.len as usize),
4299 IndentKind::Tab => "\t".repeat(indent.len as usize),
4300 };
4301 let point = Point::new(row.0, 0);
4302 indent_edits.push((point..point, text));
4303 }
4304 }
4305 editor.edit(indent_edits, cx);
4306 });
4307 }
4308
4309 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4310 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4311 original_indent_columns: Vec::new(),
4312 });
4313 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4314 }
4315
4316 fn insert_with_autoindent_mode(
4317 &mut self,
4318 text: &str,
4319 autoindent_mode: Option<AutoindentMode>,
4320 window: &mut Window,
4321 cx: &mut Context<Self>,
4322 ) {
4323 if self.read_only(cx) {
4324 return;
4325 }
4326
4327 let text: Arc<str> = text.into();
4328 self.transact(window, cx, |this, window, cx| {
4329 let old_selections = this.selections.all_adjusted(cx);
4330 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4331 let anchors = {
4332 let snapshot = buffer.read(cx);
4333 old_selections
4334 .iter()
4335 .map(|s| {
4336 let anchor = snapshot.anchor_after(s.head());
4337 s.map(|_| anchor)
4338 })
4339 .collect::<Vec<_>>()
4340 };
4341 buffer.edit(
4342 old_selections
4343 .iter()
4344 .map(|s| (s.start..s.end, text.clone())),
4345 autoindent_mode,
4346 cx,
4347 );
4348 anchors
4349 });
4350
4351 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4352 s.select_anchors(selection_anchors);
4353 });
4354
4355 cx.notify();
4356 });
4357 }
4358
4359 fn trigger_completion_on_input(
4360 &mut self,
4361 text: &str,
4362 trigger_in_words: bool,
4363 window: &mut Window,
4364 cx: &mut Context<Self>,
4365 ) {
4366 let ignore_completion_provider = self
4367 .context_menu
4368 .borrow()
4369 .as_ref()
4370 .map(|menu| match menu {
4371 CodeContextMenu::Completions(completions_menu) => {
4372 completions_menu.ignore_completion_provider
4373 }
4374 CodeContextMenu::CodeActions(_) => false,
4375 })
4376 .unwrap_or(false);
4377
4378 if ignore_completion_provider {
4379 self.show_word_completions(&ShowWordCompletions, window, cx);
4380 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4381 self.show_completions(
4382 &ShowCompletions {
4383 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4384 },
4385 window,
4386 cx,
4387 );
4388 } else {
4389 self.hide_context_menu(window, cx);
4390 }
4391 }
4392
4393 fn is_completion_trigger(
4394 &self,
4395 text: &str,
4396 trigger_in_words: bool,
4397 cx: &mut Context<Self>,
4398 ) -> bool {
4399 let position = self.selections.newest_anchor().head();
4400 let multibuffer = self.buffer.read(cx);
4401 let Some(buffer) = position
4402 .buffer_id
4403 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4404 else {
4405 return false;
4406 };
4407
4408 if let Some(completion_provider) = &self.completion_provider {
4409 completion_provider.is_completion_trigger(
4410 &buffer,
4411 position.text_anchor,
4412 text,
4413 trigger_in_words,
4414 cx,
4415 )
4416 } else {
4417 false
4418 }
4419 }
4420
4421 /// If any empty selections is touching the start of its innermost containing autoclose
4422 /// region, expand it to select the brackets.
4423 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4424 let selections = self.selections.all::<usize>(cx);
4425 let buffer = self.buffer.read(cx).read(cx);
4426 let new_selections = self
4427 .selections_with_autoclose_regions(selections, &buffer)
4428 .map(|(mut selection, region)| {
4429 if !selection.is_empty() {
4430 return selection;
4431 }
4432
4433 if let Some(region) = region {
4434 let mut range = region.range.to_offset(&buffer);
4435 if selection.start == range.start && range.start >= region.pair.start.len() {
4436 range.start -= region.pair.start.len();
4437 if buffer.contains_str_at(range.start, ®ion.pair.start)
4438 && buffer.contains_str_at(range.end, ®ion.pair.end)
4439 {
4440 range.end += region.pair.end.len();
4441 selection.start = range.start;
4442 selection.end = range.end;
4443
4444 return selection;
4445 }
4446 }
4447 }
4448
4449 let always_treat_brackets_as_autoclosed = buffer
4450 .language_settings_at(selection.start, cx)
4451 .always_treat_brackets_as_autoclosed;
4452
4453 if !always_treat_brackets_as_autoclosed {
4454 return selection;
4455 }
4456
4457 if let Some(scope) = buffer.language_scope_at(selection.start) {
4458 for (pair, enabled) in scope.brackets() {
4459 if !enabled || !pair.close {
4460 continue;
4461 }
4462
4463 if buffer.contains_str_at(selection.start, &pair.end) {
4464 let pair_start_len = pair.start.len();
4465 if buffer.contains_str_at(
4466 selection.start.saturating_sub(pair_start_len),
4467 &pair.start,
4468 ) {
4469 selection.start -= pair_start_len;
4470 selection.end += pair.end.len();
4471
4472 return selection;
4473 }
4474 }
4475 }
4476 }
4477
4478 selection
4479 })
4480 .collect();
4481
4482 drop(buffer);
4483 self.change_selections(None, window, cx, |selections| {
4484 selections.select(new_selections)
4485 });
4486 }
4487
4488 /// Iterate the given selections, and for each one, find the smallest surrounding
4489 /// autoclose region. This uses the ordering of the selections and the autoclose
4490 /// regions to avoid repeated comparisons.
4491 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4492 &'a self,
4493 selections: impl IntoIterator<Item = Selection<D>>,
4494 buffer: &'a MultiBufferSnapshot,
4495 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4496 let mut i = 0;
4497 let mut regions = self.autoclose_regions.as_slice();
4498 selections.into_iter().map(move |selection| {
4499 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4500
4501 let mut enclosing = None;
4502 while let Some(pair_state) = regions.get(i) {
4503 if pair_state.range.end.to_offset(buffer) < range.start {
4504 regions = ®ions[i + 1..];
4505 i = 0;
4506 } else if pair_state.range.start.to_offset(buffer) > range.end {
4507 break;
4508 } else {
4509 if pair_state.selection_id == selection.id {
4510 enclosing = Some(pair_state);
4511 }
4512 i += 1;
4513 }
4514 }
4515
4516 (selection, enclosing)
4517 })
4518 }
4519
4520 /// Remove any autoclose regions that no longer contain their selection.
4521 fn invalidate_autoclose_regions(
4522 &mut self,
4523 mut selections: &[Selection<Anchor>],
4524 buffer: &MultiBufferSnapshot,
4525 ) {
4526 self.autoclose_regions.retain(|state| {
4527 let mut i = 0;
4528 while let Some(selection) = selections.get(i) {
4529 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4530 selections = &selections[1..];
4531 continue;
4532 }
4533 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4534 break;
4535 }
4536 if selection.id == state.selection_id {
4537 return true;
4538 } else {
4539 i += 1;
4540 }
4541 }
4542 false
4543 });
4544 }
4545
4546 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4547 let offset = position.to_offset(buffer);
4548 let (word_range, kind) = buffer.surrounding_word(offset, true);
4549 if offset > word_range.start && kind == Some(CharKind::Word) {
4550 Some(
4551 buffer
4552 .text_for_range(word_range.start..offset)
4553 .collect::<String>(),
4554 )
4555 } else {
4556 None
4557 }
4558 }
4559
4560 pub fn toggle_inline_values(
4561 &mut self,
4562 _: &ToggleInlineValues,
4563 _: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4567
4568 self.refresh_inline_values(cx);
4569 }
4570
4571 pub fn toggle_inlay_hints(
4572 &mut self,
4573 _: &ToggleInlayHints,
4574 _: &mut Window,
4575 cx: &mut Context<Self>,
4576 ) {
4577 self.refresh_inlay_hints(
4578 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4579 cx,
4580 );
4581 }
4582
4583 pub fn inlay_hints_enabled(&self) -> bool {
4584 self.inlay_hint_cache.enabled
4585 }
4586
4587 pub fn inline_values_enabled(&self) -> bool {
4588 self.inline_value_cache.enabled
4589 }
4590
4591 #[cfg(any(test, feature = "test-support"))]
4592 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4593 self.display_map
4594 .read(cx)
4595 .current_inlays()
4596 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4597 .cloned()
4598 .collect()
4599 }
4600
4601 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4602 if self.semantics_provider.is_none() || !self.mode.is_full() {
4603 return;
4604 }
4605
4606 let reason_description = reason.description();
4607 let ignore_debounce = matches!(
4608 reason,
4609 InlayHintRefreshReason::SettingsChange(_)
4610 | InlayHintRefreshReason::Toggle(_)
4611 | InlayHintRefreshReason::ExcerptsRemoved(_)
4612 | InlayHintRefreshReason::ModifiersChanged(_)
4613 );
4614 let (invalidate_cache, required_languages) = match reason {
4615 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4616 match self.inlay_hint_cache.modifiers_override(enabled) {
4617 Some(enabled) => {
4618 if enabled {
4619 (InvalidationStrategy::RefreshRequested, None)
4620 } else {
4621 self.splice_inlays(
4622 &self
4623 .visible_inlay_hints(cx)
4624 .iter()
4625 .map(|inlay| inlay.id)
4626 .collect::<Vec<InlayId>>(),
4627 Vec::new(),
4628 cx,
4629 );
4630 return;
4631 }
4632 }
4633 None => return,
4634 }
4635 }
4636 InlayHintRefreshReason::Toggle(enabled) => {
4637 if self.inlay_hint_cache.toggle(enabled) {
4638 if enabled {
4639 (InvalidationStrategy::RefreshRequested, None)
4640 } else {
4641 self.splice_inlays(
4642 &self
4643 .visible_inlay_hints(cx)
4644 .iter()
4645 .map(|inlay| inlay.id)
4646 .collect::<Vec<InlayId>>(),
4647 Vec::new(),
4648 cx,
4649 );
4650 return;
4651 }
4652 } else {
4653 return;
4654 }
4655 }
4656 InlayHintRefreshReason::SettingsChange(new_settings) => {
4657 match self.inlay_hint_cache.update_settings(
4658 &self.buffer,
4659 new_settings,
4660 self.visible_inlay_hints(cx),
4661 cx,
4662 ) {
4663 ControlFlow::Break(Some(InlaySplice {
4664 to_remove,
4665 to_insert,
4666 })) => {
4667 self.splice_inlays(&to_remove, to_insert, cx);
4668 return;
4669 }
4670 ControlFlow::Break(None) => return,
4671 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4672 }
4673 }
4674 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4675 if let Some(InlaySplice {
4676 to_remove,
4677 to_insert,
4678 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4679 {
4680 self.splice_inlays(&to_remove, to_insert, cx);
4681 }
4682 self.display_map.update(cx, |display_map, _| {
4683 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4684 });
4685 return;
4686 }
4687 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4688 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4689 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4690 }
4691 InlayHintRefreshReason::RefreshRequested => {
4692 (InvalidationStrategy::RefreshRequested, None)
4693 }
4694 };
4695
4696 if let Some(InlaySplice {
4697 to_remove,
4698 to_insert,
4699 }) = self.inlay_hint_cache.spawn_hint_refresh(
4700 reason_description,
4701 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4702 invalidate_cache,
4703 ignore_debounce,
4704 cx,
4705 ) {
4706 self.splice_inlays(&to_remove, to_insert, cx);
4707 }
4708 }
4709
4710 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4711 self.display_map
4712 .read(cx)
4713 .current_inlays()
4714 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4715 .cloned()
4716 .collect()
4717 }
4718
4719 pub fn excerpts_for_inlay_hints_query(
4720 &self,
4721 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4722 cx: &mut Context<Editor>,
4723 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4724 let Some(project) = self.project.as_ref() else {
4725 return HashMap::default();
4726 };
4727 let project = project.read(cx);
4728 let multi_buffer = self.buffer().read(cx);
4729 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4730 let multi_buffer_visible_start = self
4731 .scroll_manager
4732 .anchor()
4733 .anchor
4734 .to_point(&multi_buffer_snapshot);
4735 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4736 multi_buffer_visible_start
4737 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4738 Bias::Left,
4739 );
4740 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4741 multi_buffer_snapshot
4742 .range_to_buffer_ranges(multi_buffer_visible_range)
4743 .into_iter()
4744 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4745 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4746 let buffer_file = project::File::from_dyn(buffer.file())?;
4747 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4748 let worktree_entry = buffer_worktree
4749 .read(cx)
4750 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4751 if worktree_entry.is_ignored {
4752 return None;
4753 }
4754
4755 let language = buffer.language()?;
4756 if let Some(restrict_to_languages) = restrict_to_languages {
4757 if !restrict_to_languages.contains(language) {
4758 return None;
4759 }
4760 }
4761 Some((
4762 excerpt_id,
4763 (
4764 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4765 buffer.version().clone(),
4766 excerpt_visible_range,
4767 ),
4768 ))
4769 })
4770 .collect()
4771 }
4772
4773 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4774 TextLayoutDetails {
4775 text_system: window.text_system().clone(),
4776 editor_style: self.style.clone().unwrap(),
4777 rem_size: window.rem_size(),
4778 scroll_anchor: self.scroll_manager.anchor(),
4779 visible_rows: self.visible_line_count(),
4780 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4781 }
4782 }
4783
4784 pub fn splice_inlays(
4785 &self,
4786 to_remove: &[InlayId],
4787 to_insert: Vec<Inlay>,
4788 cx: &mut Context<Self>,
4789 ) {
4790 self.display_map.update(cx, |display_map, cx| {
4791 display_map.splice_inlays(to_remove, to_insert, cx)
4792 });
4793 cx.notify();
4794 }
4795
4796 fn trigger_on_type_formatting(
4797 &self,
4798 input: String,
4799 window: &mut Window,
4800 cx: &mut Context<Self>,
4801 ) -> Option<Task<Result<()>>> {
4802 if input.len() != 1 {
4803 return None;
4804 }
4805
4806 let project = self.project.as_ref()?;
4807 let position = self.selections.newest_anchor().head();
4808 let (buffer, buffer_position) = self
4809 .buffer
4810 .read(cx)
4811 .text_anchor_for_position(position, cx)?;
4812
4813 let settings = language_settings::language_settings(
4814 buffer
4815 .read(cx)
4816 .language_at(buffer_position)
4817 .map(|l| l.name()),
4818 buffer.read(cx).file(),
4819 cx,
4820 );
4821 if !settings.use_on_type_format {
4822 return None;
4823 }
4824
4825 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4826 // hence we do LSP request & edit on host side only — add formats to host's history.
4827 let push_to_lsp_host_history = true;
4828 // If this is not the host, append its history with new edits.
4829 let push_to_client_history = project.read(cx).is_via_collab();
4830
4831 let on_type_formatting = project.update(cx, |project, cx| {
4832 project.on_type_format(
4833 buffer.clone(),
4834 buffer_position,
4835 input,
4836 push_to_lsp_host_history,
4837 cx,
4838 )
4839 });
4840 Some(cx.spawn_in(window, async move |editor, cx| {
4841 if let Some(transaction) = on_type_formatting.await? {
4842 if push_to_client_history {
4843 buffer
4844 .update(cx, |buffer, _| {
4845 buffer.push_transaction(transaction, Instant::now());
4846 buffer.finalize_last_transaction();
4847 })
4848 .ok();
4849 }
4850 editor.update(cx, |editor, cx| {
4851 editor.refresh_document_highlights(cx);
4852 })?;
4853 }
4854 Ok(())
4855 }))
4856 }
4857
4858 pub fn show_word_completions(
4859 &mut self,
4860 _: &ShowWordCompletions,
4861 window: &mut Window,
4862 cx: &mut Context<Self>,
4863 ) {
4864 self.open_completions_menu(true, None, window, cx);
4865 }
4866
4867 pub fn show_completions(
4868 &mut self,
4869 options: &ShowCompletions,
4870 window: &mut Window,
4871 cx: &mut Context<Self>,
4872 ) {
4873 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4874 }
4875
4876 fn open_completions_menu(
4877 &mut self,
4878 ignore_completion_provider: bool,
4879 trigger: Option<&str>,
4880 window: &mut Window,
4881 cx: &mut Context<Self>,
4882 ) {
4883 if self.pending_rename.is_some() {
4884 return;
4885 }
4886 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4887 return;
4888 }
4889
4890 let position = self.selections.newest_anchor().head();
4891 if position.diff_base_anchor.is_some() {
4892 return;
4893 }
4894 let (buffer, buffer_position) =
4895 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4896 output
4897 } else {
4898 return;
4899 };
4900 let buffer_snapshot = buffer.read(cx).snapshot();
4901 let show_completion_documentation = buffer_snapshot
4902 .settings_at(buffer_position, cx)
4903 .show_completion_documentation;
4904
4905 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4906
4907 let trigger_kind = match trigger {
4908 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4909 CompletionTriggerKind::TRIGGER_CHARACTER
4910 }
4911 _ => CompletionTriggerKind::INVOKED,
4912 };
4913 let completion_context = CompletionContext {
4914 trigger_character: trigger.and_then(|trigger| {
4915 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4916 Some(String::from(trigger))
4917 } else {
4918 None
4919 }
4920 }),
4921 trigger_kind,
4922 };
4923
4924 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4925 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4926 let word_to_exclude = buffer_snapshot
4927 .text_for_range(old_range.clone())
4928 .collect::<String>();
4929 (
4930 buffer_snapshot.anchor_before(old_range.start)
4931 ..buffer_snapshot.anchor_after(old_range.end),
4932 Some(word_to_exclude),
4933 )
4934 } else {
4935 (buffer_position..buffer_position, None)
4936 };
4937
4938 let completion_settings = language_settings(
4939 buffer_snapshot
4940 .language_at(buffer_position)
4941 .map(|language| language.name()),
4942 buffer_snapshot.file(),
4943 cx,
4944 )
4945 .completions;
4946
4947 // The document can be large, so stay in reasonable bounds when searching for words,
4948 // otherwise completion pop-up might be slow to appear.
4949 const WORD_LOOKUP_ROWS: u32 = 5_000;
4950 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4951 let min_word_search = buffer_snapshot.clip_point(
4952 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4953 Bias::Left,
4954 );
4955 let max_word_search = buffer_snapshot.clip_point(
4956 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4957 Bias::Right,
4958 );
4959 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4960 ..buffer_snapshot.point_to_offset(max_word_search);
4961
4962 let provider = self
4963 .completion_provider
4964 .as_ref()
4965 .filter(|_| !ignore_completion_provider);
4966 let skip_digits = query
4967 .as_ref()
4968 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4969
4970 let (mut words, provided_completions) = match provider {
4971 Some(provider) => {
4972 let completions = provider.completions(
4973 position.excerpt_id,
4974 &buffer,
4975 buffer_position,
4976 completion_context,
4977 window,
4978 cx,
4979 );
4980
4981 let words = match completion_settings.words {
4982 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4983 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4984 .background_spawn(async move {
4985 buffer_snapshot.words_in_range(WordsQuery {
4986 fuzzy_contents: None,
4987 range: word_search_range,
4988 skip_digits,
4989 })
4990 }),
4991 };
4992
4993 (words, completions)
4994 }
4995 None => (
4996 cx.background_spawn(async move {
4997 buffer_snapshot.words_in_range(WordsQuery {
4998 fuzzy_contents: None,
4999 range: word_search_range,
5000 skip_digits,
5001 })
5002 }),
5003 Task::ready(Ok(None)),
5004 ),
5005 };
5006
5007 let sort_completions = provider
5008 .as_ref()
5009 .map_or(false, |provider| provider.sort_completions());
5010
5011 let filter_completions = provider
5012 .as_ref()
5013 .map_or(true, |provider| provider.filter_completions());
5014
5015 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5016
5017 let id = post_inc(&mut self.next_completion_id);
5018 let task = cx.spawn_in(window, async move |editor, cx| {
5019 async move {
5020 editor.update(cx, |this, _| {
5021 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5022 })?;
5023
5024 let mut completions = Vec::new();
5025 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5026 completions.extend(provided_completions);
5027 if completion_settings.words == WordsCompletionMode::Fallback {
5028 words = Task::ready(BTreeMap::default());
5029 }
5030 }
5031
5032 let mut words = words.await;
5033 if let Some(word_to_exclude) = &word_to_exclude {
5034 words.remove(word_to_exclude);
5035 }
5036 for lsp_completion in &completions {
5037 words.remove(&lsp_completion.new_text);
5038 }
5039 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5040 replace_range: old_range.clone(),
5041 new_text: word.clone(),
5042 label: CodeLabel::plain(word, None),
5043 icon_path: None,
5044 documentation: None,
5045 source: CompletionSource::BufferWord {
5046 word_range,
5047 resolved: false,
5048 },
5049 insert_text_mode: Some(InsertTextMode::AS_IS),
5050 confirm: None,
5051 }));
5052
5053 let menu = if completions.is_empty() {
5054 None
5055 } else {
5056 let mut menu = CompletionsMenu::new(
5057 id,
5058 sort_completions,
5059 show_completion_documentation,
5060 ignore_completion_provider,
5061 position,
5062 buffer.clone(),
5063 completions.into(),
5064 snippet_sort_order,
5065 );
5066
5067 menu.filter(
5068 if filter_completions {
5069 query.as_deref()
5070 } else {
5071 None
5072 },
5073 cx.background_executor().clone(),
5074 )
5075 .await;
5076
5077 menu.visible().then_some(menu)
5078 };
5079
5080 editor.update_in(cx, |editor, window, cx| {
5081 match editor.context_menu.borrow().as_ref() {
5082 None => {}
5083 Some(CodeContextMenu::Completions(prev_menu)) => {
5084 if prev_menu.id > id {
5085 return;
5086 }
5087 }
5088 _ => return,
5089 }
5090
5091 if editor.focus_handle.is_focused(window) && menu.is_some() {
5092 let mut menu = menu.unwrap();
5093 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5094 crate::hover_popover::hide_hover(editor, cx);
5095 *editor.context_menu.borrow_mut() =
5096 Some(CodeContextMenu::Completions(menu));
5097
5098 if editor.show_edit_predictions_in_menu() {
5099 editor.update_visible_inline_completion(window, cx);
5100 } else {
5101 editor.discard_inline_completion(false, cx);
5102 }
5103
5104 cx.notify();
5105 } else if editor.completion_tasks.len() <= 1 {
5106 // If there are no more completion tasks and the last menu was
5107 // empty, we should hide it.
5108 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5109 // If it was already hidden and we don't show inline
5110 // completions in the menu, we should also show the
5111 // inline-completion when available.
5112 if was_hidden && editor.show_edit_predictions_in_menu() {
5113 editor.update_visible_inline_completion(window, cx);
5114 }
5115 }
5116 })?;
5117
5118 anyhow::Ok(())
5119 }
5120 .log_err()
5121 .await
5122 });
5123
5124 self.completion_tasks.push((id, task));
5125 }
5126
5127 #[cfg(feature = "test-support")]
5128 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5129 let menu = self.context_menu.borrow();
5130 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5131 let completions = menu.completions.borrow();
5132 Some(completions.to_vec())
5133 } else {
5134 None
5135 }
5136 }
5137
5138 pub fn confirm_completion(
5139 &mut self,
5140 action: &ConfirmCompletion,
5141 window: &mut Window,
5142 cx: &mut Context<Self>,
5143 ) -> Option<Task<Result<()>>> {
5144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5145 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5146 }
5147
5148 pub fn confirm_completion_insert(
5149 &mut self,
5150 _: &ConfirmCompletionInsert,
5151 window: &mut Window,
5152 cx: &mut Context<Self>,
5153 ) -> Option<Task<Result<()>>> {
5154 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5155 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5156 }
5157
5158 pub fn confirm_completion_replace(
5159 &mut self,
5160 _: &ConfirmCompletionReplace,
5161 window: &mut Window,
5162 cx: &mut Context<Self>,
5163 ) -> Option<Task<Result<()>>> {
5164 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5165 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5166 }
5167
5168 pub fn compose_completion(
5169 &mut self,
5170 action: &ComposeCompletion,
5171 window: &mut Window,
5172 cx: &mut Context<Self>,
5173 ) -> Option<Task<Result<()>>> {
5174 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5175 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5176 }
5177
5178 fn do_completion(
5179 &mut self,
5180 item_ix: Option<usize>,
5181 intent: CompletionIntent,
5182 window: &mut Window,
5183 cx: &mut Context<Editor>,
5184 ) -> Option<Task<Result<()>>> {
5185 use language::ToOffset as _;
5186
5187 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5188 else {
5189 return None;
5190 };
5191
5192 let candidate_id = {
5193 let entries = completions_menu.entries.borrow();
5194 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5195 if self.show_edit_predictions_in_menu() {
5196 self.discard_inline_completion(true, cx);
5197 }
5198 mat.candidate_id
5199 };
5200
5201 let buffer_handle = completions_menu.buffer;
5202 let completion = completions_menu
5203 .completions
5204 .borrow()
5205 .get(candidate_id)?
5206 .clone();
5207 cx.stop_propagation();
5208
5209 let snapshot = self.buffer.read(cx).snapshot(cx);
5210 let newest_anchor = self.selections.newest_anchor();
5211
5212 let snippet;
5213 let new_text;
5214 if completion.is_snippet() {
5215 let mut snippet_source = completion.new_text.clone();
5216 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5217 if scope.prefers_label_for_snippet_in_completion() {
5218 if let Some(label) = completion.label() {
5219 if matches!(
5220 completion.kind(),
5221 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5222 ) {
5223 snippet_source = label;
5224 }
5225 }
5226 }
5227 }
5228 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5229 new_text = snippet.as_ref().unwrap().text.clone();
5230 } else {
5231 snippet = None;
5232 new_text = completion.new_text.clone();
5233 };
5234
5235 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5236 let buffer = buffer_handle.read(cx);
5237 let replace_range_multibuffer = {
5238 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5239 let multibuffer_anchor = snapshot
5240 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5241 .unwrap()
5242 ..snapshot
5243 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5244 .unwrap();
5245 multibuffer_anchor.start.to_offset(&snapshot)
5246 ..multibuffer_anchor.end.to_offset(&snapshot)
5247 };
5248 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5249 return None;
5250 }
5251
5252 let old_text = buffer
5253 .text_for_range(replace_range.clone())
5254 .collect::<String>();
5255 let lookbehind = newest_anchor
5256 .start
5257 .text_anchor
5258 .to_offset(buffer)
5259 .saturating_sub(replace_range.start);
5260 let lookahead = replace_range
5261 .end
5262 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5263 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5264 let suffix = &old_text[lookbehind.min(old_text.len())..];
5265
5266 let selections = self.selections.all::<usize>(cx);
5267 let mut ranges = Vec::new();
5268 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5269
5270 for selection in &selections {
5271 let range = if selection.id == newest_anchor.id {
5272 replace_range_multibuffer.clone()
5273 } else {
5274 let mut range = selection.range();
5275
5276 // if prefix is present, don't duplicate it
5277 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5278 range.start = range.start.saturating_sub(lookbehind);
5279
5280 // if suffix is also present, mimic the newest cursor and replace it
5281 if selection.id != newest_anchor.id
5282 && snapshot.contains_str_at(range.end, suffix)
5283 {
5284 range.end += lookahead;
5285 }
5286 }
5287 range
5288 };
5289
5290 ranges.push(range.clone());
5291
5292 if !self.linked_edit_ranges.is_empty() {
5293 let start_anchor = snapshot.anchor_before(range.start);
5294 let end_anchor = snapshot.anchor_after(range.end);
5295 if let Some(ranges) = self
5296 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5297 {
5298 for (buffer, edits) in ranges {
5299 linked_edits
5300 .entry(buffer.clone())
5301 .or_default()
5302 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5303 }
5304 }
5305 }
5306 }
5307
5308 cx.emit(EditorEvent::InputHandled {
5309 utf16_range_to_replace: None,
5310 text: new_text.clone().into(),
5311 });
5312
5313 self.transact(window, cx, |this, window, cx| {
5314 if let Some(mut snippet) = snippet {
5315 snippet.text = new_text.to_string();
5316 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5317 } else {
5318 this.buffer.update(cx, |buffer, cx| {
5319 let auto_indent = match completion.insert_text_mode {
5320 Some(InsertTextMode::AS_IS) => None,
5321 _ => this.autoindent_mode.clone(),
5322 };
5323 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5324 buffer.edit(edits, auto_indent, cx);
5325 });
5326 }
5327 for (buffer, edits) in linked_edits {
5328 buffer.update(cx, |buffer, cx| {
5329 let snapshot = buffer.snapshot();
5330 let edits = edits
5331 .into_iter()
5332 .map(|(range, text)| {
5333 use text::ToPoint as TP;
5334 let end_point = TP::to_point(&range.end, &snapshot);
5335 let start_point = TP::to_point(&range.start, &snapshot);
5336 (start_point..end_point, text)
5337 })
5338 .sorted_by_key(|(range, _)| range.start);
5339 buffer.edit(edits, None, cx);
5340 })
5341 }
5342
5343 this.refresh_inline_completion(true, false, window, cx);
5344 });
5345
5346 let show_new_completions_on_confirm = completion
5347 .confirm
5348 .as_ref()
5349 .map_or(false, |confirm| confirm(intent, window, cx));
5350 if show_new_completions_on_confirm {
5351 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5352 }
5353
5354 let provider = self.completion_provider.as_ref()?;
5355 drop(completion);
5356 let apply_edits = provider.apply_additional_edits_for_completion(
5357 buffer_handle,
5358 completions_menu.completions.clone(),
5359 candidate_id,
5360 true,
5361 cx,
5362 );
5363
5364 let editor_settings = EditorSettings::get_global(cx);
5365 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5366 // After the code completion is finished, users often want to know what signatures are needed.
5367 // so we should automatically call signature_help
5368 self.show_signature_help(&ShowSignatureHelp, window, cx);
5369 }
5370
5371 Some(cx.foreground_executor().spawn(async move {
5372 apply_edits.await?;
5373 Ok(())
5374 }))
5375 }
5376
5377 pub fn toggle_code_actions(
5378 &mut self,
5379 action: &ToggleCodeActions,
5380 window: &mut Window,
5381 cx: &mut Context<Self>,
5382 ) {
5383 let quick_launch = action.quick_launch;
5384 let mut context_menu = self.context_menu.borrow_mut();
5385 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5386 if code_actions.deployed_from == action.deployed_from {
5387 // Toggle if we're selecting the same one
5388 *context_menu = None;
5389 cx.notify();
5390 return;
5391 } else {
5392 // Otherwise, clear it and start a new one
5393 *context_menu = None;
5394 cx.notify();
5395 }
5396 }
5397 drop(context_menu);
5398 let snapshot = self.snapshot(window, cx);
5399 let deployed_from = action.deployed_from.clone();
5400 let mut task = self.code_actions_task.take();
5401 let action = action.clone();
5402 cx.spawn_in(window, async move |editor, cx| {
5403 while let Some(prev_task) = task {
5404 prev_task.await.log_err();
5405 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5406 }
5407
5408 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5409 if editor.focus_handle.is_focused(window) {
5410 let multibuffer_point = match &action.deployed_from {
5411 Some(CodeActionSource::Indicator(row)) => {
5412 DisplayPoint::new(*row, 0).to_point(&snapshot)
5413 }
5414 _ => editor.selections.newest::<Point>(cx).head(),
5415 };
5416 let (buffer, buffer_row) = snapshot
5417 .buffer_snapshot
5418 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5419 .and_then(|(buffer_snapshot, range)| {
5420 editor
5421 .buffer
5422 .read(cx)
5423 .buffer(buffer_snapshot.remote_id())
5424 .map(|buffer| (buffer, range.start.row))
5425 })?;
5426 let (_, code_actions) = editor
5427 .available_code_actions
5428 .clone()
5429 .and_then(|(location, code_actions)| {
5430 let snapshot = location.buffer.read(cx).snapshot();
5431 let point_range = location.range.to_point(&snapshot);
5432 let point_range = point_range.start.row..=point_range.end.row;
5433 if point_range.contains(&buffer_row) {
5434 Some((location, code_actions))
5435 } else {
5436 None
5437 }
5438 })
5439 .unzip();
5440 let buffer_id = buffer.read(cx).remote_id();
5441 let tasks = editor
5442 .tasks
5443 .get(&(buffer_id, buffer_row))
5444 .map(|t| Arc::new(t.to_owned()));
5445 if tasks.is_none() && code_actions.is_none() {
5446 return None;
5447 }
5448
5449 editor.completion_tasks.clear();
5450 editor.discard_inline_completion(false, cx);
5451 let task_context =
5452 tasks
5453 .as_ref()
5454 .zip(editor.project.clone())
5455 .map(|(tasks, project)| {
5456 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5457 });
5458
5459 Some(cx.spawn_in(window, async move |editor, cx| {
5460 let task_context = match task_context {
5461 Some(task_context) => task_context.await,
5462 None => None,
5463 };
5464 let resolved_tasks =
5465 tasks
5466 .zip(task_context.clone())
5467 .map(|(tasks, task_context)| ResolvedTasks {
5468 templates: tasks.resolve(&task_context).collect(),
5469 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5470 multibuffer_point.row,
5471 tasks.column,
5472 )),
5473 });
5474 let debug_scenarios = editor.update(cx, |editor, cx| {
5475 if cx.has_flag::<DebuggerFeatureFlag>() {
5476 maybe!({
5477 let project = editor.project.as_ref()?;
5478 let dap_store = project.read(cx).dap_store();
5479 let mut scenarios = vec![];
5480 let resolved_tasks = resolved_tasks.as_ref()?;
5481 let buffer = buffer.read(cx);
5482 let language = buffer.language()?;
5483 let file = buffer.file();
5484 let debug_adapter =
5485 language_settings(language.name().into(), file, cx)
5486 .debuggers
5487 .first()
5488 .map(SharedString::from)
5489 .or_else(|| {
5490 language
5491 .config()
5492 .debuggers
5493 .first()
5494 .map(SharedString::from)
5495 })?;
5496
5497 dap_store.update(cx, |dap_store, cx| {
5498 for (_, task) in &resolved_tasks.templates {
5499 if let Some(scenario) = dap_store
5500 .debug_scenario_for_build_task(
5501 task.original_task().clone(),
5502 debug_adapter.clone().into(),
5503 task.display_label().to_owned().into(),
5504 cx,
5505 )
5506 {
5507 scenarios.push(scenario);
5508 }
5509 }
5510 });
5511 Some(scenarios)
5512 })
5513 .unwrap_or_default()
5514 } else {
5515 vec![]
5516 }
5517 })?;
5518 let spawn_straight_away = quick_launch
5519 && resolved_tasks
5520 .as_ref()
5521 .map_or(false, |tasks| tasks.templates.len() == 1)
5522 && code_actions
5523 .as_ref()
5524 .map_or(true, |actions| actions.is_empty())
5525 && debug_scenarios.is_empty();
5526 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5527 crate::hover_popover::hide_hover(editor, cx);
5528 *editor.context_menu.borrow_mut() =
5529 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5530 buffer,
5531 actions: CodeActionContents::new(
5532 resolved_tasks,
5533 code_actions,
5534 debug_scenarios,
5535 task_context.unwrap_or_default(),
5536 ),
5537 selected_item: Default::default(),
5538 scroll_handle: UniformListScrollHandle::default(),
5539 deployed_from,
5540 }));
5541 if spawn_straight_away {
5542 if let Some(task) = editor.confirm_code_action(
5543 &ConfirmCodeAction { item_ix: Some(0) },
5544 window,
5545 cx,
5546 ) {
5547 cx.notify();
5548 return task;
5549 }
5550 }
5551 cx.notify();
5552 Task::ready(Ok(()))
5553 }) {
5554 task.await
5555 } else {
5556 Ok(())
5557 }
5558 }))
5559 } else {
5560 Some(Task::ready(Ok(())))
5561 }
5562 })?;
5563 if let Some(task) = spawned_test_task {
5564 task.await?;
5565 }
5566
5567 anyhow::Ok(())
5568 })
5569 .detach_and_log_err(cx);
5570 }
5571
5572 pub fn confirm_code_action(
5573 &mut self,
5574 action: &ConfirmCodeAction,
5575 window: &mut Window,
5576 cx: &mut Context<Self>,
5577 ) -> Option<Task<Result<()>>> {
5578 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5579
5580 let actions_menu =
5581 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5582 menu
5583 } else {
5584 return None;
5585 };
5586
5587 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5588 let action = actions_menu.actions.get(action_ix)?;
5589 let title = action.label();
5590 let buffer = actions_menu.buffer;
5591 let workspace = self.workspace()?;
5592
5593 match action {
5594 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5595 workspace.update(cx, |workspace, cx| {
5596 workspace.schedule_resolved_task(
5597 task_source_kind,
5598 resolved_task,
5599 false,
5600 window,
5601 cx,
5602 );
5603
5604 Some(Task::ready(Ok(())))
5605 })
5606 }
5607 CodeActionsItem::CodeAction {
5608 excerpt_id,
5609 action,
5610 provider,
5611 } => {
5612 let apply_code_action =
5613 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5614 let workspace = workspace.downgrade();
5615 Some(cx.spawn_in(window, async move |editor, cx| {
5616 let project_transaction = apply_code_action.await?;
5617 Self::open_project_transaction(
5618 &editor,
5619 workspace,
5620 project_transaction,
5621 title,
5622 cx,
5623 )
5624 .await
5625 }))
5626 }
5627 CodeActionsItem::DebugScenario(scenario) => {
5628 let context = actions_menu.actions.context.clone();
5629
5630 workspace.update(cx, |workspace, cx| {
5631 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5632 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5633 });
5634 Some(Task::ready(Ok(())))
5635 }
5636 }
5637 }
5638
5639 pub async fn open_project_transaction(
5640 this: &WeakEntity<Editor>,
5641 workspace: WeakEntity<Workspace>,
5642 transaction: ProjectTransaction,
5643 title: String,
5644 cx: &mut AsyncWindowContext,
5645 ) -> Result<()> {
5646 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5647 cx.update(|_, cx| {
5648 entries.sort_unstable_by_key(|(buffer, _)| {
5649 buffer.read(cx).file().map(|f| f.path().clone())
5650 });
5651 })?;
5652
5653 // If the project transaction's edits are all contained within this editor, then
5654 // avoid opening a new editor to display them.
5655
5656 if let Some((buffer, transaction)) = entries.first() {
5657 if entries.len() == 1 {
5658 let excerpt = this.update(cx, |editor, cx| {
5659 editor
5660 .buffer()
5661 .read(cx)
5662 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5663 })?;
5664 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5665 if excerpted_buffer == *buffer {
5666 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5667 let excerpt_range = excerpt_range.to_offset(buffer);
5668 buffer
5669 .edited_ranges_for_transaction::<usize>(transaction)
5670 .all(|range| {
5671 excerpt_range.start <= range.start
5672 && excerpt_range.end >= range.end
5673 })
5674 })?;
5675
5676 if all_edits_within_excerpt {
5677 return Ok(());
5678 }
5679 }
5680 }
5681 }
5682 } else {
5683 return Ok(());
5684 }
5685
5686 let mut ranges_to_highlight = Vec::new();
5687 let excerpt_buffer = cx.new(|cx| {
5688 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5689 for (buffer_handle, transaction) in &entries {
5690 let edited_ranges = buffer_handle
5691 .read(cx)
5692 .edited_ranges_for_transaction::<Point>(transaction)
5693 .collect::<Vec<_>>();
5694 let (ranges, _) = multibuffer.set_excerpts_for_path(
5695 PathKey::for_buffer(buffer_handle, cx),
5696 buffer_handle.clone(),
5697 edited_ranges,
5698 DEFAULT_MULTIBUFFER_CONTEXT,
5699 cx,
5700 );
5701
5702 ranges_to_highlight.extend(ranges);
5703 }
5704 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5705 multibuffer
5706 })?;
5707
5708 workspace.update_in(cx, |workspace, window, cx| {
5709 let project = workspace.project().clone();
5710 let editor =
5711 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5712 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5713 editor.update(cx, |editor, cx| {
5714 editor.highlight_background::<Self>(
5715 &ranges_to_highlight,
5716 |theme| theme.editor_highlighted_line_background,
5717 cx,
5718 );
5719 });
5720 })?;
5721
5722 Ok(())
5723 }
5724
5725 pub fn clear_code_action_providers(&mut self) {
5726 self.code_action_providers.clear();
5727 self.available_code_actions.take();
5728 }
5729
5730 pub fn add_code_action_provider(
5731 &mut self,
5732 provider: Rc<dyn CodeActionProvider>,
5733 window: &mut Window,
5734 cx: &mut Context<Self>,
5735 ) {
5736 if self
5737 .code_action_providers
5738 .iter()
5739 .any(|existing_provider| existing_provider.id() == provider.id())
5740 {
5741 return;
5742 }
5743
5744 self.code_action_providers.push(provider);
5745 self.refresh_code_actions(window, cx);
5746 }
5747
5748 pub fn remove_code_action_provider(
5749 &mut self,
5750 id: Arc<str>,
5751 window: &mut Window,
5752 cx: &mut Context<Self>,
5753 ) {
5754 self.code_action_providers
5755 .retain(|provider| provider.id() != id);
5756 self.refresh_code_actions(window, cx);
5757 }
5758
5759 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5760 !self.code_action_providers.is_empty()
5761 && EditorSettings::get_global(cx).toolbar.code_actions
5762 }
5763
5764 pub fn has_available_code_actions(&self) -> bool {
5765 self.available_code_actions
5766 .as_ref()
5767 .is_some_and(|(_, actions)| !actions.is_empty())
5768 }
5769
5770 fn render_inline_code_actions(
5771 &self,
5772 icon_size: ui::IconSize,
5773 display_row: DisplayRow,
5774 is_active: bool,
5775 cx: &mut Context<Self>,
5776 ) -> AnyElement {
5777 let show_tooltip = !self.context_menu_visible();
5778 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5779 .icon_size(icon_size)
5780 .shape(ui::IconButtonShape::Square)
5781 .style(ButtonStyle::Transparent)
5782 .icon_color(ui::Color::Hidden)
5783 .toggle_state(is_active)
5784 .when(show_tooltip, |this| {
5785 this.tooltip({
5786 let focus_handle = self.focus_handle.clone();
5787 move |window, cx| {
5788 Tooltip::for_action_in(
5789 "Toggle Code Actions",
5790 &ToggleCodeActions {
5791 deployed_from: None,
5792 quick_launch: false,
5793 },
5794 &focus_handle,
5795 window,
5796 cx,
5797 )
5798 }
5799 })
5800 })
5801 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5802 window.focus(&editor.focus_handle(cx));
5803 editor.toggle_code_actions(
5804 &crate::actions::ToggleCodeActions {
5805 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5806 display_row,
5807 )),
5808 quick_launch: false,
5809 },
5810 window,
5811 cx,
5812 );
5813 }))
5814 .into_any_element()
5815 }
5816
5817 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5818 &self.context_menu
5819 }
5820
5821 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5822 let newest_selection = self.selections.newest_anchor().clone();
5823 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5824 let buffer = self.buffer.read(cx);
5825 if newest_selection.head().diff_base_anchor.is_some() {
5826 return None;
5827 }
5828 let (start_buffer, start) =
5829 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5830 let (end_buffer, end) =
5831 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5832 if start_buffer != end_buffer {
5833 return None;
5834 }
5835
5836 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5837 cx.background_executor()
5838 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5839 .await;
5840
5841 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5842 let providers = this.code_action_providers.clone();
5843 let tasks = this
5844 .code_action_providers
5845 .iter()
5846 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5847 .collect::<Vec<_>>();
5848 (providers, tasks)
5849 })?;
5850
5851 let mut actions = Vec::new();
5852 for (provider, provider_actions) in
5853 providers.into_iter().zip(future::join_all(tasks).await)
5854 {
5855 if let Some(provider_actions) = provider_actions.log_err() {
5856 actions.extend(provider_actions.into_iter().map(|action| {
5857 AvailableCodeAction {
5858 excerpt_id: newest_selection.start.excerpt_id,
5859 action,
5860 provider: provider.clone(),
5861 }
5862 }));
5863 }
5864 }
5865
5866 this.update(cx, |this, cx| {
5867 this.available_code_actions = if actions.is_empty() {
5868 None
5869 } else {
5870 Some((
5871 Location {
5872 buffer: start_buffer,
5873 range: start..end,
5874 },
5875 actions.into(),
5876 ))
5877 };
5878 cx.notify();
5879 })
5880 }));
5881 None
5882 }
5883
5884 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5885 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5886 self.show_git_blame_inline = false;
5887
5888 self.show_git_blame_inline_delay_task =
5889 Some(cx.spawn_in(window, async move |this, cx| {
5890 cx.background_executor().timer(delay).await;
5891
5892 this.update(cx, |this, cx| {
5893 this.show_git_blame_inline = true;
5894 cx.notify();
5895 })
5896 .log_err();
5897 }));
5898 }
5899 }
5900
5901 fn show_blame_popover(
5902 &mut self,
5903 blame_entry: &BlameEntry,
5904 position: gpui::Point<Pixels>,
5905 cx: &mut Context<Self>,
5906 ) {
5907 if let Some(state) = &mut self.inline_blame_popover {
5908 state.hide_task.take();
5909 cx.notify();
5910 } else {
5911 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5912 let show_task = cx.spawn(async move |editor, cx| {
5913 cx.background_executor()
5914 .timer(std::time::Duration::from_millis(delay))
5915 .await;
5916 editor
5917 .update(cx, |editor, cx| {
5918 if let Some(state) = &mut editor.inline_blame_popover {
5919 state.show_task = None;
5920 cx.notify();
5921 }
5922 })
5923 .ok();
5924 });
5925 let Some(blame) = self.blame.as_ref() else {
5926 return;
5927 };
5928 let blame = blame.read(cx);
5929 let details = blame.details_for_entry(&blame_entry);
5930 let markdown = cx.new(|cx| {
5931 Markdown::new(
5932 details
5933 .as_ref()
5934 .map(|message| message.message.clone())
5935 .unwrap_or_default(),
5936 None,
5937 None,
5938 cx,
5939 )
5940 });
5941 self.inline_blame_popover = Some(InlineBlamePopover {
5942 position,
5943 show_task: Some(show_task),
5944 hide_task: None,
5945 popover_bounds: None,
5946 popover_state: InlineBlamePopoverState {
5947 scroll_handle: ScrollHandle::new(),
5948 commit_message: details,
5949 markdown,
5950 },
5951 });
5952 }
5953 }
5954
5955 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5956 if let Some(state) = &mut self.inline_blame_popover {
5957 if state.show_task.is_some() {
5958 self.inline_blame_popover.take();
5959 cx.notify();
5960 } else {
5961 let hide_task = cx.spawn(async move |editor, cx| {
5962 cx.background_executor()
5963 .timer(std::time::Duration::from_millis(100))
5964 .await;
5965 editor
5966 .update(cx, |editor, cx| {
5967 editor.inline_blame_popover.take();
5968 cx.notify();
5969 })
5970 .ok();
5971 });
5972 state.hide_task = Some(hide_task);
5973 }
5974 }
5975 }
5976
5977 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5978 if self.pending_rename.is_some() {
5979 return None;
5980 }
5981
5982 let provider = self.semantics_provider.clone()?;
5983 let buffer = self.buffer.read(cx);
5984 let newest_selection = self.selections.newest_anchor().clone();
5985 let cursor_position = newest_selection.head();
5986 let (cursor_buffer, cursor_buffer_position) =
5987 buffer.text_anchor_for_position(cursor_position, cx)?;
5988 let (tail_buffer, tail_buffer_position) =
5989 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5990 if cursor_buffer != tail_buffer {
5991 return None;
5992 }
5993
5994 let snapshot = cursor_buffer.read(cx).snapshot();
5995 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5996 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5997 if start_word_range != end_word_range {
5998 self.document_highlights_task.take();
5999 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6000 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6001 return None;
6002 }
6003
6004 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6005 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6006 cx.background_executor()
6007 .timer(Duration::from_millis(debounce))
6008 .await;
6009
6010 let highlights = if let Some(highlights) = cx
6011 .update(|cx| {
6012 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6013 })
6014 .ok()
6015 .flatten()
6016 {
6017 highlights.await.log_err()
6018 } else {
6019 None
6020 };
6021
6022 if let Some(highlights) = highlights {
6023 this.update(cx, |this, cx| {
6024 if this.pending_rename.is_some() {
6025 return;
6026 }
6027
6028 let buffer_id = cursor_position.buffer_id;
6029 let buffer = this.buffer.read(cx);
6030 if !buffer
6031 .text_anchor_for_position(cursor_position, cx)
6032 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6033 {
6034 return;
6035 }
6036
6037 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6038 let mut write_ranges = Vec::new();
6039 let mut read_ranges = Vec::new();
6040 for highlight in highlights {
6041 for (excerpt_id, excerpt_range) in
6042 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6043 {
6044 let start = highlight
6045 .range
6046 .start
6047 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6048 let end = highlight
6049 .range
6050 .end
6051 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6052 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6053 continue;
6054 }
6055
6056 let range = Anchor {
6057 buffer_id,
6058 excerpt_id,
6059 text_anchor: start,
6060 diff_base_anchor: None,
6061 }..Anchor {
6062 buffer_id,
6063 excerpt_id,
6064 text_anchor: end,
6065 diff_base_anchor: None,
6066 };
6067 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6068 write_ranges.push(range);
6069 } else {
6070 read_ranges.push(range);
6071 }
6072 }
6073 }
6074
6075 this.highlight_background::<DocumentHighlightRead>(
6076 &read_ranges,
6077 |theme| theme.editor_document_highlight_read_background,
6078 cx,
6079 );
6080 this.highlight_background::<DocumentHighlightWrite>(
6081 &write_ranges,
6082 |theme| theme.editor_document_highlight_write_background,
6083 cx,
6084 );
6085 cx.notify();
6086 })
6087 .log_err();
6088 }
6089 }));
6090 None
6091 }
6092
6093 fn prepare_highlight_query_from_selection(
6094 &mut self,
6095 cx: &mut Context<Editor>,
6096 ) -> Option<(String, Range<Anchor>)> {
6097 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6098 return None;
6099 }
6100 if !EditorSettings::get_global(cx).selection_highlight {
6101 return None;
6102 }
6103 if self.selections.count() != 1 || self.selections.line_mode {
6104 return None;
6105 }
6106 let selection = self.selections.newest::<Point>(cx);
6107 if selection.is_empty() || selection.start.row != selection.end.row {
6108 return None;
6109 }
6110 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6111 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6112 let query = multi_buffer_snapshot
6113 .text_for_range(selection_anchor_range.clone())
6114 .collect::<String>();
6115 if query.trim().is_empty() {
6116 return None;
6117 }
6118 Some((query, selection_anchor_range))
6119 }
6120
6121 fn update_selection_occurrence_highlights(
6122 &mut self,
6123 query_text: String,
6124 query_range: Range<Anchor>,
6125 multi_buffer_range_to_query: Range<Point>,
6126 use_debounce: bool,
6127 window: &mut Window,
6128 cx: &mut Context<Editor>,
6129 ) -> Task<()> {
6130 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6131 cx.spawn_in(window, async move |editor, cx| {
6132 if use_debounce {
6133 cx.background_executor()
6134 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6135 .await;
6136 }
6137 let match_task = cx.background_spawn(async move {
6138 let buffer_ranges = multi_buffer_snapshot
6139 .range_to_buffer_ranges(multi_buffer_range_to_query)
6140 .into_iter()
6141 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6142 let mut match_ranges = Vec::new();
6143 let Ok(regex) = project::search::SearchQuery::text(
6144 query_text.clone(),
6145 false,
6146 false,
6147 false,
6148 Default::default(),
6149 Default::default(),
6150 false,
6151 None,
6152 ) else {
6153 return Vec::default();
6154 };
6155 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6156 match_ranges.extend(
6157 regex
6158 .search(&buffer_snapshot, Some(search_range.clone()))
6159 .await
6160 .into_iter()
6161 .filter_map(|match_range| {
6162 let match_start = buffer_snapshot
6163 .anchor_after(search_range.start + match_range.start);
6164 let match_end = buffer_snapshot
6165 .anchor_before(search_range.start + match_range.end);
6166 let match_anchor_range = Anchor::range_in_buffer(
6167 excerpt_id,
6168 buffer_snapshot.remote_id(),
6169 match_start..match_end,
6170 );
6171 (match_anchor_range != query_range).then_some(match_anchor_range)
6172 }),
6173 );
6174 }
6175 match_ranges
6176 });
6177 let match_ranges = match_task.await;
6178 editor
6179 .update_in(cx, |editor, _, cx| {
6180 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6181 if !match_ranges.is_empty() {
6182 editor.highlight_background::<SelectedTextHighlight>(
6183 &match_ranges,
6184 |theme| theme.editor_document_highlight_bracket_background,
6185 cx,
6186 )
6187 }
6188 })
6189 .log_err();
6190 })
6191 }
6192
6193 fn refresh_selected_text_highlights(
6194 &mut self,
6195 on_buffer_edit: bool,
6196 window: &mut Window,
6197 cx: &mut Context<Editor>,
6198 ) {
6199 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6200 else {
6201 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6202 self.quick_selection_highlight_task.take();
6203 self.debounced_selection_highlight_task.take();
6204 return;
6205 };
6206 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6207 if on_buffer_edit
6208 || self
6209 .quick_selection_highlight_task
6210 .as_ref()
6211 .map_or(true, |(prev_anchor_range, _)| {
6212 prev_anchor_range != &query_range
6213 })
6214 {
6215 let multi_buffer_visible_start = self
6216 .scroll_manager
6217 .anchor()
6218 .anchor
6219 .to_point(&multi_buffer_snapshot);
6220 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6221 multi_buffer_visible_start
6222 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6223 Bias::Left,
6224 );
6225 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6226 self.quick_selection_highlight_task = Some((
6227 query_range.clone(),
6228 self.update_selection_occurrence_highlights(
6229 query_text.clone(),
6230 query_range.clone(),
6231 multi_buffer_visible_range,
6232 false,
6233 window,
6234 cx,
6235 ),
6236 ));
6237 }
6238 if on_buffer_edit
6239 || self
6240 .debounced_selection_highlight_task
6241 .as_ref()
6242 .map_or(true, |(prev_anchor_range, _)| {
6243 prev_anchor_range != &query_range
6244 })
6245 {
6246 let multi_buffer_start = multi_buffer_snapshot
6247 .anchor_before(0)
6248 .to_point(&multi_buffer_snapshot);
6249 let multi_buffer_end = multi_buffer_snapshot
6250 .anchor_after(multi_buffer_snapshot.len())
6251 .to_point(&multi_buffer_snapshot);
6252 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6253 self.debounced_selection_highlight_task = Some((
6254 query_range.clone(),
6255 self.update_selection_occurrence_highlights(
6256 query_text,
6257 query_range,
6258 multi_buffer_full_range,
6259 true,
6260 window,
6261 cx,
6262 ),
6263 ));
6264 }
6265 }
6266
6267 pub fn refresh_inline_completion(
6268 &mut self,
6269 debounce: bool,
6270 user_requested: bool,
6271 window: &mut Window,
6272 cx: &mut Context<Self>,
6273 ) -> Option<()> {
6274 let provider = self.edit_prediction_provider()?;
6275 let cursor = self.selections.newest_anchor().head();
6276 let (buffer, cursor_buffer_position) =
6277 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6278
6279 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6280 self.discard_inline_completion(false, cx);
6281 return None;
6282 }
6283
6284 if !user_requested
6285 && (!self.should_show_edit_predictions()
6286 || !self.is_focused(window)
6287 || buffer.read(cx).is_empty())
6288 {
6289 self.discard_inline_completion(false, cx);
6290 return None;
6291 }
6292
6293 self.update_visible_inline_completion(window, cx);
6294 provider.refresh(
6295 self.project.clone(),
6296 buffer,
6297 cursor_buffer_position,
6298 debounce,
6299 cx,
6300 );
6301 Some(())
6302 }
6303
6304 fn show_edit_predictions_in_menu(&self) -> bool {
6305 match self.edit_prediction_settings {
6306 EditPredictionSettings::Disabled => false,
6307 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6308 }
6309 }
6310
6311 pub fn edit_predictions_enabled(&self) -> bool {
6312 match self.edit_prediction_settings {
6313 EditPredictionSettings::Disabled => false,
6314 EditPredictionSettings::Enabled { .. } => true,
6315 }
6316 }
6317
6318 fn edit_prediction_requires_modifier(&self) -> bool {
6319 match self.edit_prediction_settings {
6320 EditPredictionSettings::Disabled => false,
6321 EditPredictionSettings::Enabled {
6322 preview_requires_modifier,
6323 ..
6324 } => preview_requires_modifier,
6325 }
6326 }
6327
6328 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6329 if self.edit_prediction_provider.is_none() {
6330 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6331 } else {
6332 let selection = self.selections.newest_anchor();
6333 let cursor = selection.head();
6334
6335 if let Some((buffer, cursor_buffer_position)) =
6336 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6337 {
6338 self.edit_prediction_settings =
6339 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6340 }
6341 }
6342 }
6343
6344 fn edit_prediction_settings_at_position(
6345 &self,
6346 buffer: &Entity<Buffer>,
6347 buffer_position: language::Anchor,
6348 cx: &App,
6349 ) -> EditPredictionSettings {
6350 if !self.mode.is_full()
6351 || !self.show_inline_completions_override.unwrap_or(true)
6352 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6353 {
6354 return EditPredictionSettings::Disabled;
6355 }
6356
6357 let buffer = buffer.read(cx);
6358
6359 let file = buffer.file();
6360
6361 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6362 return EditPredictionSettings::Disabled;
6363 };
6364
6365 let by_provider = matches!(
6366 self.menu_inline_completions_policy,
6367 MenuInlineCompletionsPolicy::ByProvider
6368 );
6369
6370 let show_in_menu = by_provider
6371 && self
6372 .edit_prediction_provider
6373 .as_ref()
6374 .map_or(false, |provider| {
6375 provider.provider.show_completions_in_menu()
6376 });
6377
6378 let preview_requires_modifier =
6379 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6380
6381 EditPredictionSettings::Enabled {
6382 show_in_menu,
6383 preview_requires_modifier,
6384 }
6385 }
6386
6387 fn should_show_edit_predictions(&self) -> bool {
6388 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6389 }
6390
6391 pub fn edit_prediction_preview_is_active(&self) -> bool {
6392 matches!(
6393 self.edit_prediction_preview,
6394 EditPredictionPreview::Active { .. }
6395 )
6396 }
6397
6398 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6399 let cursor = self.selections.newest_anchor().head();
6400 if let Some((buffer, cursor_position)) =
6401 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6402 {
6403 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6404 } else {
6405 false
6406 }
6407 }
6408
6409 pub fn supports_minimap(&self, cx: &App) -> bool {
6410 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6411 }
6412
6413 fn edit_predictions_enabled_in_buffer(
6414 &self,
6415 buffer: &Entity<Buffer>,
6416 buffer_position: language::Anchor,
6417 cx: &App,
6418 ) -> bool {
6419 maybe!({
6420 if self.read_only(cx) {
6421 return Some(false);
6422 }
6423 let provider = self.edit_prediction_provider()?;
6424 if !provider.is_enabled(&buffer, buffer_position, cx) {
6425 return Some(false);
6426 }
6427 let buffer = buffer.read(cx);
6428 let Some(file) = buffer.file() else {
6429 return Some(true);
6430 };
6431 let settings = all_language_settings(Some(file), cx);
6432 Some(settings.edit_predictions_enabled_for_file(file, cx))
6433 })
6434 .unwrap_or(false)
6435 }
6436
6437 fn cycle_inline_completion(
6438 &mut self,
6439 direction: Direction,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) -> Option<()> {
6443 let provider = self.edit_prediction_provider()?;
6444 let cursor = self.selections.newest_anchor().head();
6445 let (buffer, cursor_buffer_position) =
6446 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6447 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6448 return None;
6449 }
6450
6451 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6452 self.update_visible_inline_completion(window, cx);
6453
6454 Some(())
6455 }
6456
6457 pub fn show_inline_completion(
6458 &mut self,
6459 _: &ShowEditPrediction,
6460 window: &mut Window,
6461 cx: &mut Context<Self>,
6462 ) {
6463 if !self.has_active_inline_completion() {
6464 self.refresh_inline_completion(false, true, window, cx);
6465 return;
6466 }
6467
6468 self.update_visible_inline_completion(window, cx);
6469 }
6470
6471 pub fn display_cursor_names(
6472 &mut self,
6473 _: &DisplayCursorNames,
6474 window: &mut Window,
6475 cx: &mut Context<Self>,
6476 ) {
6477 self.show_cursor_names(window, cx);
6478 }
6479
6480 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6481 self.show_cursor_names = true;
6482 cx.notify();
6483 cx.spawn_in(window, async move |this, cx| {
6484 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6485 this.update(cx, |this, cx| {
6486 this.show_cursor_names = false;
6487 cx.notify()
6488 })
6489 .ok()
6490 })
6491 .detach();
6492 }
6493
6494 pub fn next_edit_prediction(
6495 &mut self,
6496 _: &NextEditPrediction,
6497 window: &mut Window,
6498 cx: &mut Context<Self>,
6499 ) {
6500 if self.has_active_inline_completion() {
6501 self.cycle_inline_completion(Direction::Next, window, cx);
6502 } else {
6503 let is_copilot_disabled = self
6504 .refresh_inline_completion(false, true, window, cx)
6505 .is_none();
6506 if is_copilot_disabled {
6507 cx.propagate();
6508 }
6509 }
6510 }
6511
6512 pub fn previous_edit_prediction(
6513 &mut self,
6514 _: &PreviousEditPrediction,
6515 window: &mut Window,
6516 cx: &mut Context<Self>,
6517 ) {
6518 if self.has_active_inline_completion() {
6519 self.cycle_inline_completion(Direction::Prev, window, cx);
6520 } else {
6521 let is_copilot_disabled = self
6522 .refresh_inline_completion(false, true, window, cx)
6523 .is_none();
6524 if is_copilot_disabled {
6525 cx.propagate();
6526 }
6527 }
6528 }
6529
6530 pub fn accept_edit_prediction(
6531 &mut self,
6532 _: &AcceptEditPrediction,
6533 window: &mut Window,
6534 cx: &mut Context<Self>,
6535 ) {
6536 if self.show_edit_predictions_in_menu() {
6537 self.hide_context_menu(window, cx);
6538 }
6539
6540 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6541 return;
6542 };
6543
6544 self.report_inline_completion_event(
6545 active_inline_completion.completion_id.clone(),
6546 true,
6547 cx,
6548 );
6549
6550 match &active_inline_completion.completion {
6551 InlineCompletion::Move { target, .. } => {
6552 let target = *target;
6553
6554 if let Some(position_map) = &self.last_position_map {
6555 if position_map
6556 .visible_row_range
6557 .contains(&target.to_display_point(&position_map.snapshot).row())
6558 || !self.edit_prediction_requires_modifier()
6559 {
6560 self.unfold_ranges(&[target..target], true, false, cx);
6561 // Note that this is also done in vim's handler of the Tab action.
6562 self.change_selections(
6563 Some(Autoscroll::newest()),
6564 window,
6565 cx,
6566 |selections| {
6567 selections.select_anchor_ranges([target..target]);
6568 },
6569 );
6570 self.clear_row_highlights::<EditPredictionPreview>();
6571
6572 self.edit_prediction_preview
6573 .set_previous_scroll_position(None);
6574 } else {
6575 self.edit_prediction_preview
6576 .set_previous_scroll_position(Some(
6577 position_map.snapshot.scroll_anchor,
6578 ));
6579
6580 self.highlight_rows::<EditPredictionPreview>(
6581 target..target,
6582 cx.theme().colors().editor_highlighted_line_background,
6583 RowHighlightOptions {
6584 autoscroll: true,
6585 ..Default::default()
6586 },
6587 cx,
6588 );
6589 self.request_autoscroll(Autoscroll::fit(), cx);
6590 }
6591 }
6592 }
6593 InlineCompletion::Edit { edits, .. } => {
6594 if let Some(provider) = self.edit_prediction_provider() {
6595 provider.accept(cx);
6596 }
6597
6598 let snapshot = self.buffer.read(cx).snapshot(cx);
6599 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6600
6601 self.buffer.update(cx, |buffer, cx| {
6602 buffer.edit(edits.iter().cloned(), None, cx)
6603 });
6604
6605 self.change_selections(None, window, cx, |s| {
6606 s.select_anchor_ranges([last_edit_end..last_edit_end])
6607 });
6608
6609 self.update_visible_inline_completion(window, cx);
6610 if self.active_inline_completion.is_none() {
6611 self.refresh_inline_completion(true, true, window, cx);
6612 }
6613
6614 cx.notify();
6615 }
6616 }
6617
6618 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6619 }
6620
6621 pub fn accept_partial_inline_completion(
6622 &mut self,
6623 _: &AcceptPartialEditPrediction,
6624 window: &mut Window,
6625 cx: &mut Context<Self>,
6626 ) {
6627 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6628 return;
6629 };
6630 if self.selections.count() != 1 {
6631 return;
6632 }
6633
6634 self.report_inline_completion_event(
6635 active_inline_completion.completion_id.clone(),
6636 true,
6637 cx,
6638 );
6639
6640 match &active_inline_completion.completion {
6641 InlineCompletion::Move { target, .. } => {
6642 let target = *target;
6643 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6644 selections.select_anchor_ranges([target..target]);
6645 });
6646 }
6647 InlineCompletion::Edit { edits, .. } => {
6648 // Find an insertion that starts at the cursor position.
6649 let snapshot = self.buffer.read(cx).snapshot(cx);
6650 let cursor_offset = self.selections.newest::<usize>(cx).head();
6651 let insertion = edits.iter().find_map(|(range, text)| {
6652 let range = range.to_offset(&snapshot);
6653 if range.is_empty() && range.start == cursor_offset {
6654 Some(text)
6655 } else {
6656 None
6657 }
6658 });
6659
6660 if let Some(text) = insertion {
6661 let mut partial_completion = text
6662 .chars()
6663 .by_ref()
6664 .take_while(|c| c.is_alphabetic())
6665 .collect::<String>();
6666 if partial_completion.is_empty() {
6667 partial_completion = text
6668 .chars()
6669 .by_ref()
6670 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6671 .collect::<String>();
6672 }
6673
6674 cx.emit(EditorEvent::InputHandled {
6675 utf16_range_to_replace: None,
6676 text: partial_completion.clone().into(),
6677 });
6678
6679 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6680
6681 self.refresh_inline_completion(true, true, window, cx);
6682 cx.notify();
6683 } else {
6684 self.accept_edit_prediction(&Default::default(), window, cx);
6685 }
6686 }
6687 }
6688 }
6689
6690 fn discard_inline_completion(
6691 &mut self,
6692 should_report_inline_completion_event: bool,
6693 cx: &mut Context<Self>,
6694 ) -> bool {
6695 if should_report_inline_completion_event {
6696 let completion_id = self
6697 .active_inline_completion
6698 .as_ref()
6699 .and_then(|active_completion| active_completion.completion_id.clone());
6700
6701 self.report_inline_completion_event(completion_id, false, cx);
6702 }
6703
6704 if let Some(provider) = self.edit_prediction_provider() {
6705 provider.discard(cx);
6706 }
6707
6708 self.take_active_inline_completion(cx)
6709 }
6710
6711 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6712 let Some(provider) = self.edit_prediction_provider() else {
6713 return;
6714 };
6715
6716 let Some((_, buffer, _)) = self
6717 .buffer
6718 .read(cx)
6719 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6720 else {
6721 return;
6722 };
6723
6724 let extension = buffer
6725 .read(cx)
6726 .file()
6727 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6728
6729 let event_type = match accepted {
6730 true => "Edit Prediction Accepted",
6731 false => "Edit Prediction Discarded",
6732 };
6733 telemetry::event!(
6734 event_type,
6735 provider = provider.name(),
6736 prediction_id = id,
6737 suggestion_accepted = accepted,
6738 file_extension = extension,
6739 );
6740 }
6741
6742 pub fn has_active_inline_completion(&self) -> bool {
6743 self.active_inline_completion.is_some()
6744 }
6745
6746 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6747 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6748 return false;
6749 };
6750
6751 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6752 self.clear_highlights::<InlineCompletionHighlight>(cx);
6753 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6754 true
6755 }
6756
6757 /// Returns true when we're displaying the edit prediction popover below the cursor
6758 /// like we are not previewing and the LSP autocomplete menu is visible
6759 /// or we are in `when_holding_modifier` mode.
6760 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6761 if self.edit_prediction_preview_is_active()
6762 || !self.show_edit_predictions_in_menu()
6763 || !self.edit_predictions_enabled()
6764 {
6765 return false;
6766 }
6767
6768 if self.has_visible_completions_menu() {
6769 return true;
6770 }
6771
6772 has_completion && self.edit_prediction_requires_modifier()
6773 }
6774
6775 fn handle_modifiers_changed(
6776 &mut self,
6777 modifiers: Modifiers,
6778 position_map: &PositionMap,
6779 window: &mut Window,
6780 cx: &mut Context<Self>,
6781 ) {
6782 if self.show_edit_predictions_in_menu() {
6783 self.update_edit_prediction_preview(&modifiers, window, cx);
6784 }
6785
6786 self.update_selection_mode(&modifiers, position_map, window, cx);
6787
6788 let mouse_position = window.mouse_position();
6789 if !position_map.text_hitbox.is_hovered(window) {
6790 return;
6791 }
6792
6793 self.update_hovered_link(
6794 position_map.point_for_position(mouse_position),
6795 &position_map.snapshot,
6796 modifiers,
6797 window,
6798 cx,
6799 )
6800 }
6801
6802 fn update_selection_mode(
6803 &mut self,
6804 modifiers: &Modifiers,
6805 position_map: &PositionMap,
6806 window: &mut Window,
6807 cx: &mut Context<Self>,
6808 ) {
6809 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6810 return;
6811 }
6812
6813 let mouse_position = window.mouse_position();
6814 let point_for_position = position_map.point_for_position(mouse_position);
6815 let position = point_for_position.previous_valid;
6816
6817 self.select(
6818 SelectPhase::BeginColumnar {
6819 position,
6820 reset: false,
6821 goal_column: point_for_position.exact_unclipped.column(),
6822 },
6823 window,
6824 cx,
6825 );
6826 }
6827
6828 fn update_edit_prediction_preview(
6829 &mut self,
6830 modifiers: &Modifiers,
6831 window: &mut Window,
6832 cx: &mut Context<Self>,
6833 ) {
6834 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6835 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6836 return;
6837 };
6838
6839 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6840 if matches!(
6841 self.edit_prediction_preview,
6842 EditPredictionPreview::Inactive { .. }
6843 ) {
6844 self.edit_prediction_preview = EditPredictionPreview::Active {
6845 previous_scroll_position: None,
6846 since: Instant::now(),
6847 };
6848
6849 self.update_visible_inline_completion(window, cx);
6850 cx.notify();
6851 }
6852 } else if let EditPredictionPreview::Active {
6853 previous_scroll_position,
6854 since,
6855 } = self.edit_prediction_preview
6856 {
6857 if let (Some(previous_scroll_position), Some(position_map)) =
6858 (previous_scroll_position, self.last_position_map.as_ref())
6859 {
6860 self.set_scroll_position(
6861 previous_scroll_position
6862 .scroll_position(&position_map.snapshot.display_snapshot),
6863 window,
6864 cx,
6865 );
6866 }
6867
6868 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6869 released_too_fast: since.elapsed() < Duration::from_millis(200),
6870 };
6871 self.clear_row_highlights::<EditPredictionPreview>();
6872 self.update_visible_inline_completion(window, cx);
6873 cx.notify();
6874 }
6875 }
6876
6877 fn update_visible_inline_completion(
6878 &mut self,
6879 _window: &mut Window,
6880 cx: &mut Context<Self>,
6881 ) -> Option<()> {
6882 let selection = self.selections.newest_anchor();
6883 let cursor = selection.head();
6884 let multibuffer = self.buffer.read(cx).snapshot(cx);
6885 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6886 let excerpt_id = cursor.excerpt_id;
6887
6888 let show_in_menu = self.show_edit_predictions_in_menu();
6889 let completions_menu_has_precedence = !show_in_menu
6890 && (self.context_menu.borrow().is_some()
6891 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6892
6893 if completions_menu_has_precedence
6894 || !offset_selection.is_empty()
6895 || self
6896 .active_inline_completion
6897 .as_ref()
6898 .map_or(false, |completion| {
6899 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6900 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6901 !invalidation_range.contains(&offset_selection.head())
6902 })
6903 {
6904 self.discard_inline_completion(false, cx);
6905 return None;
6906 }
6907
6908 self.take_active_inline_completion(cx);
6909 let Some(provider) = self.edit_prediction_provider() else {
6910 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6911 return None;
6912 };
6913
6914 let (buffer, cursor_buffer_position) =
6915 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6916
6917 self.edit_prediction_settings =
6918 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6919
6920 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6921
6922 if self.edit_prediction_indent_conflict {
6923 let cursor_point = cursor.to_point(&multibuffer);
6924
6925 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6926
6927 if let Some((_, indent)) = indents.iter().next() {
6928 if indent.len == cursor_point.column {
6929 self.edit_prediction_indent_conflict = false;
6930 }
6931 }
6932 }
6933
6934 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6935 let edits = inline_completion
6936 .edits
6937 .into_iter()
6938 .flat_map(|(range, new_text)| {
6939 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6940 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6941 Some((start..end, new_text))
6942 })
6943 .collect::<Vec<_>>();
6944 if edits.is_empty() {
6945 return None;
6946 }
6947
6948 let first_edit_start = edits.first().unwrap().0.start;
6949 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6950 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6951
6952 let last_edit_end = edits.last().unwrap().0.end;
6953 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6954 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6955
6956 let cursor_row = cursor.to_point(&multibuffer).row;
6957
6958 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6959
6960 let mut inlay_ids = Vec::new();
6961 let invalidation_row_range;
6962 let move_invalidation_row_range = if cursor_row < edit_start_row {
6963 Some(cursor_row..edit_end_row)
6964 } else if cursor_row > edit_end_row {
6965 Some(edit_start_row..cursor_row)
6966 } else {
6967 None
6968 };
6969 let is_move =
6970 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6971 let completion = if is_move {
6972 invalidation_row_range =
6973 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6974 let target = first_edit_start;
6975 InlineCompletion::Move { target, snapshot }
6976 } else {
6977 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6978 && !self.inline_completions_hidden_for_vim_mode;
6979
6980 if show_completions_in_buffer {
6981 if edits
6982 .iter()
6983 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6984 {
6985 let mut inlays = Vec::new();
6986 for (range, new_text) in &edits {
6987 let inlay = Inlay::inline_completion(
6988 post_inc(&mut self.next_inlay_id),
6989 range.start,
6990 new_text.as_str(),
6991 );
6992 inlay_ids.push(inlay.id);
6993 inlays.push(inlay);
6994 }
6995
6996 self.splice_inlays(&[], inlays, cx);
6997 } else {
6998 let background_color = cx.theme().status().deleted_background;
6999 self.highlight_text::<InlineCompletionHighlight>(
7000 edits.iter().map(|(range, _)| range.clone()).collect(),
7001 HighlightStyle {
7002 background_color: Some(background_color),
7003 ..Default::default()
7004 },
7005 cx,
7006 );
7007 }
7008 }
7009
7010 invalidation_row_range = edit_start_row..edit_end_row;
7011
7012 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7013 if provider.show_tab_accept_marker() {
7014 EditDisplayMode::TabAccept
7015 } else {
7016 EditDisplayMode::Inline
7017 }
7018 } else {
7019 EditDisplayMode::DiffPopover
7020 };
7021
7022 InlineCompletion::Edit {
7023 edits,
7024 edit_preview: inline_completion.edit_preview,
7025 display_mode,
7026 snapshot,
7027 }
7028 };
7029
7030 let invalidation_range = multibuffer
7031 .anchor_before(Point::new(invalidation_row_range.start, 0))
7032 ..multibuffer.anchor_after(Point::new(
7033 invalidation_row_range.end,
7034 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7035 ));
7036
7037 self.stale_inline_completion_in_menu = None;
7038 self.active_inline_completion = Some(InlineCompletionState {
7039 inlay_ids,
7040 completion,
7041 completion_id: inline_completion.id,
7042 invalidation_range,
7043 });
7044
7045 cx.notify();
7046
7047 Some(())
7048 }
7049
7050 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7051 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7052 }
7053
7054 fn clear_tasks(&mut self) {
7055 self.tasks.clear()
7056 }
7057
7058 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7059 if self.tasks.insert(key, value).is_some() {
7060 // This case should hopefully be rare, but just in case...
7061 log::error!(
7062 "multiple different run targets found on a single line, only the last target will be rendered"
7063 )
7064 }
7065 }
7066
7067 /// Get all display points of breakpoints that will be rendered within editor
7068 ///
7069 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7070 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7071 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7072 fn active_breakpoints(
7073 &self,
7074 range: Range<DisplayRow>,
7075 window: &mut Window,
7076 cx: &mut Context<Self>,
7077 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7078 let mut breakpoint_display_points = HashMap::default();
7079
7080 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7081 return breakpoint_display_points;
7082 };
7083
7084 let snapshot = self.snapshot(window, cx);
7085
7086 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7087 let Some(project) = self.project.as_ref() else {
7088 return breakpoint_display_points;
7089 };
7090
7091 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7092 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7093
7094 for (buffer_snapshot, range, excerpt_id) in
7095 multi_buffer_snapshot.range_to_buffer_ranges(range)
7096 {
7097 let Some(buffer) = project.read_with(cx, |this, cx| {
7098 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7099 }) else {
7100 continue;
7101 };
7102 let breakpoints = breakpoint_store.read(cx).breakpoints(
7103 &buffer,
7104 Some(
7105 buffer_snapshot.anchor_before(range.start)
7106 ..buffer_snapshot.anchor_after(range.end),
7107 ),
7108 buffer_snapshot,
7109 cx,
7110 );
7111 for (breakpoint, state) in breakpoints {
7112 let multi_buffer_anchor =
7113 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7114 let position = multi_buffer_anchor
7115 .to_point(&multi_buffer_snapshot)
7116 .to_display_point(&snapshot);
7117
7118 breakpoint_display_points.insert(
7119 position.row(),
7120 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7121 );
7122 }
7123 }
7124
7125 breakpoint_display_points
7126 }
7127
7128 fn breakpoint_context_menu(
7129 &self,
7130 anchor: Anchor,
7131 window: &mut Window,
7132 cx: &mut Context<Self>,
7133 ) -> Entity<ui::ContextMenu> {
7134 let weak_editor = cx.weak_entity();
7135 let focus_handle = self.focus_handle(cx);
7136
7137 let row = self
7138 .buffer
7139 .read(cx)
7140 .snapshot(cx)
7141 .summary_for_anchor::<Point>(&anchor)
7142 .row;
7143
7144 let breakpoint = self
7145 .breakpoint_at_row(row, window, cx)
7146 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7147
7148 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7149 "Edit Log Breakpoint"
7150 } else {
7151 "Set Log Breakpoint"
7152 };
7153
7154 let condition_breakpoint_msg = if breakpoint
7155 .as_ref()
7156 .is_some_and(|bp| bp.1.condition.is_some())
7157 {
7158 "Edit Condition Breakpoint"
7159 } else {
7160 "Set Condition Breakpoint"
7161 };
7162
7163 let hit_condition_breakpoint_msg = if breakpoint
7164 .as_ref()
7165 .is_some_and(|bp| bp.1.hit_condition.is_some())
7166 {
7167 "Edit Hit Condition Breakpoint"
7168 } else {
7169 "Set Hit Condition Breakpoint"
7170 };
7171
7172 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7173 "Unset Breakpoint"
7174 } else {
7175 "Set Breakpoint"
7176 };
7177
7178 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7179 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7180
7181 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7182 BreakpointState::Enabled => Some("Disable"),
7183 BreakpointState::Disabled => Some("Enable"),
7184 });
7185
7186 let (anchor, breakpoint) =
7187 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7188
7189 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7190 menu.on_blur_subscription(Subscription::new(|| {}))
7191 .context(focus_handle)
7192 .when(run_to_cursor, |this| {
7193 let weak_editor = weak_editor.clone();
7194 this.entry("Run to cursor", None, move |window, cx| {
7195 weak_editor
7196 .update(cx, |editor, cx| {
7197 editor.change_selections(None, window, cx, |s| {
7198 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7199 });
7200 })
7201 .ok();
7202
7203 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7204 })
7205 .separator()
7206 })
7207 .when_some(toggle_state_msg, |this, msg| {
7208 this.entry(msg, None, {
7209 let weak_editor = weak_editor.clone();
7210 let breakpoint = breakpoint.clone();
7211 move |_window, cx| {
7212 weak_editor
7213 .update(cx, |this, cx| {
7214 this.edit_breakpoint_at_anchor(
7215 anchor,
7216 breakpoint.as_ref().clone(),
7217 BreakpointEditAction::InvertState,
7218 cx,
7219 );
7220 })
7221 .log_err();
7222 }
7223 })
7224 })
7225 .entry(set_breakpoint_msg, None, {
7226 let weak_editor = weak_editor.clone();
7227 let breakpoint = breakpoint.clone();
7228 move |_window, cx| {
7229 weak_editor
7230 .update(cx, |this, cx| {
7231 this.edit_breakpoint_at_anchor(
7232 anchor,
7233 breakpoint.as_ref().clone(),
7234 BreakpointEditAction::Toggle,
7235 cx,
7236 );
7237 })
7238 .log_err();
7239 }
7240 })
7241 .entry(log_breakpoint_msg, None, {
7242 let breakpoint = breakpoint.clone();
7243 let weak_editor = weak_editor.clone();
7244 move |window, cx| {
7245 weak_editor
7246 .update(cx, |this, cx| {
7247 this.add_edit_breakpoint_block(
7248 anchor,
7249 breakpoint.as_ref(),
7250 BreakpointPromptEditAction::Log,
7251 window,
7252 cx,
7253 );
7254 })
7255 .log_err();
7256 }
7257 })
7258 .entry(condition_breakpoint_msg, None, {
7259 let breakpoint = breakpoint.clone();
7260 let weak_editor = weak_editor.clone();
7261 move |window, cx| {
7262 weak_editor
7263 .update(cx, |this, cx| {
7264 this.add_edit_breakpoint_block(
7265 anchor,
7266 breakpoint.as_ref(),
7267 BreakpointPromptEditAction::Condition,
7268 window,
7269 cx,
7270 );
7271 })
7272 .log_err();
7273 }
7274 })
7275 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7276 weak_editor
7277 .update(cx, |this, cx| {
7278 this.add_edit_breakpoint_block(
7279 anchor,
7280 breakpoint.as_ref(),
7281 BreakpointPromptEditAction::HitCondition,
7282 window,
7283 cx,
7284 );
7285 })
7286 .log_err();
7287 })
7288 })
7289 }
7290
7291 fn render_breakpoint(
7292 &self,
7293 position: Anchor,
7294 row: DisplayRow,
7295 breakpoint: &Breakpoint,
7296 state: Option<BreakpointSessionState>,
7297 cx: &mut Context<Self>,
7298 ) -> IconButton {
7299 let is_rejected = state.is_some_and(|s| !s.verified);
7300 // Is it a breakpoint that shows up when hovering over gutter?
7301 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7302 (false, false),
7303 |PhantomBreakpointIndicator {
7304 is_active,
7305 display_row,
7306 collides_with_existing_breakpoint,
7307 }| {
7308 (
7309 is_active && display_row == row,
7310 collides_with_existing_breakpoint,
7311 )
7312 },
7313 );
7314
7315 let (color, icon) = {
7316 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7317 (false, false) => ui::IconName::DebugBreakpoint,
7318 (true, false) => ui::IconName::DebugLogBreakpoint,
7319 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7320 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7321 };
7322
7323 let color = if is_phantom {
7324 Color::Hint
7325 } else if is_rejected {
7326 Color::Disabled
7327 } else {
7328 Color::Debugger
7329 };
7330
7331 (color, icon)
7332 };
7333
7334 let breakpoint = Arc::from(breakpoint.clone());
7335
7336 let alt_as_text = gpui::Keystroke {
7337 modifiers: Modifiers::secondary_key(),
7338 ..Default::default()
7339 };
7340 let primary_action_text = if breakpoint.is_disabled() {
7341 "Enable breakpoint"
7342 } else if is_phantom && !collides_with_existing {
7343 "Set breakpoint"
7344 } else {
7345 "Unset breakpoint"
7346 };
7347 let focus_handle = self.focus_handle.clone();
7348
7349 let meta = if is_rejected {
7350 SharedString::from("No executable code is associated with this line.")
7351 } else if collides_with_existing && !breakpoint.is_disabled() {
7352 SharedString::from(format!(
7353 "{alt_as_text}-click to disable,\nright-click for more options."
7354 ))
7355 } else {
7356 SharedString::from("Right-click for more options.")
7357 };
7358 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7359 .icon_size(IconSize::XSmall)
7360 .size(ui::ButtonSize::None)
7361 .when(is_rejected, |this| {
7362 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7363 })
7364 .icon_color(color)
7365 .style(ButtonStyle::Transparent)
7366 .on_click(cx.listener({
7367 let breakpoint = breakpoint.clone();
7368
7369 move |editor, event: &ClickEvent, window, cx| {
7370 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7371 BreakpointEditAction::InvertState
7372 } else {
7373 BreakpointEditAction::Toggle
7374 };
7375
7376 window.focus(&editor.focus_handle(cx));
7377 editor.edit_breakpoint_at_anchor(
7378 position,
7379 breakpoint.as_ref().clone(),
7380 edit_action,
7381 cx,
7382 );
7383 }
7384 }))
7385 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7386 editor.set_breakpoint_context_menu(
7387 row,
7388 Some(position),
7389 event.down.position,
7390 window,
7391 cx,
7392 );
7393 }))
7394 .tooltip(move |window, cx| {
7395 Tooltip::with_meta_in(
7396 primary_action_text,
7397 Some(&ToggleBreakpoint),
7398 meta.clone(),
7399 &focus_handle,
7400 window,
7401 cx,
7402 )
7403 })
7404 }
7405
7406 fn build_tasks_context(
7407 project: &Entity<Project>,
7408 buffer: &Entity<Buffer>,
7409 buffer_row: u32,
7410 tasks: &Arc<RunnableTasks>,
7411 cx: &mut Context<Self>,
7412 ) -> Task<Option<task::TaskContext>> {
7413 let position = Point::new(buffer_row, tasks.column);
7414 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7415 let location = Location {
7416 buffer: buffer.clone(),
7417 range: range_start..range_start,
7418 };
7419 // Fill in the environmental variables from the tree-sitter captures
7420 let mut captured_task_variables = TaskVariables::default();
7421 for (capture_name, value) in tasks.extra_variables.clone() {
7422 captured_task_variables.insert(
7423 task::VariableName::Custom(capture_name.into()),
7424 value.clone(),
7425 );
7426 }
7427 project.update(cx, |project, cx| {
7428 project.task_store().update(cx, |task_store, cx| {
7429 task_store.task_context_for_location(captured_task_variables, location, cx)
7430 })
7431 })
7432 }
7433
7434 pub fn spawn_nearest_task(
7435 &mut self,
7436 action: &SpawnNearestTask,
7437 window: &mut Window,
7438 cx: &mut Context<Self>,
7439 ) {
7440 let Some((workspace, _)) = self.workspace.clone() else {
7441 return;
7442 };
7443 let Some(project) = self.project.clone() else {
7444 return;
7445 };
7446
7447 // Try to find a closest, enclosing node using tree-sitter that has a
7448 // task
7449 let Some((buffer, buffer_row, tasks)) = self
7450 .find_enclosing_node_task(cx)
7451 // Or find the task that's closest in row-distance.
7452 .or_else(|| self.find_closest_task(cx))
7453 else {
7454 return;
7455 };
7456
7457 let reveal_strategy = action.reveal;
7458 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7459 cx.spawn_in(window, async move |_, cx| {
7460 let context = task_context.await?;
7461 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7462
7463 let resolved = &mut resolved_task.resolved;
7464 resolved.reveal = reveal_strategy;
7465
7466 workspace
7467 .update_in(cx, |workspace, window, cx| {
7468 workspace.schedule_resolved_task(
7469 task_source_kind,
7470 resolved_task,
7471 false,
7472 window,
7473 cx,
7474 );
7475 })
7476 .ok()
7477 })
7478 .detach();
7479 }
7480
7481 fn find_closest_task(
7482 &mut self,
7483 cx: &mut Context<Self>,
7484 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7485 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7486
7487 let ((buffer_id, row), tasks) = self
7488 .tasks
7489 .iter()
7490 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7491
7492 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7493 let tasks = Arc::new(tasks.to_owned());
7494 Some((buffer, *row, tasks))
7495 }
7496
7497 fn find_enclosing_node_task(
7498 &mut self,
7499 cx: &mut Context<Self>,
7500 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7501 let snapshot = self.buffer.read(cx).snapshot(cx);
7502 let offset = self.selections.newest::<usize>(cx).head();
7503 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7504 let buffer_id = excerpt.buffer().remote_id();
7505
7506 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7507 let mut cursor = layer.node().walk();
7508
7509 while cursor.goto_first_child_for_byte(offset).is_some() {
7510 if cursor.node().end_byte() == offset {
7511 cursor.goto_next_sibling();
7512 }
7513 }
7514
7515 // Ascend to the smallest ancestor that contains the range and has a task.
7516 loop {
7517 let node = cursor.node();
7518 let node_range = node.byte_range();
7519 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7520
7521 // Check if this node contains our offset
7522 if node_range.start <= offset && node_range.end >= offset {
7523 // If it contains offset, check for task
7524 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7525 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7526 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7527 }
7528 }
7529
7530 if !cursor.goto_parent() {
7531 break;
7532 }
7533 }
7534 None
7535 }
7536
7537 fn render_run_indicator(
7538 &self,
7539 _style: &EditorStyle,
7540 is_active: bool,
7541 row: DisplayRow,
7542 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7543 cx: &mut Context<Self>,
7544 ) -> IconButton {
7545 let color = Color::Muted;
7546 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7547
7548 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7549 .shape(ui::IconButtonShape::Square)
7550 .icon_size(IconSize::XSmall)
7551 .icon_color(color)
7552 .toggle_state(is_active)
7553 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7554 let quick_launch = e.down.button == MouseButton::Left;
7555 window.focus(&editor.focus_handle(cx));
7556 editor.toggle_code_actions(
7557 &ToggleCodeActions {
7558 deployed_from: Some(CodeActionSource::Indicator(row)),
7559 quick_launch,
7560 },
7561 window,
7562 cx,
7563 );
7564 }))
7565 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7566 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7567 }))
7568 }
7569
7570 pub fn context_menu_visible(&self) -> bool {
7571 !self.edit_prediction_preview_is_active()
7572 && self
7573 .context_menu
7574 .borrow()
7575 .as_ref()
7576 .map_or(false, |menu| menu.visible())
7577 }
7578
7579 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7580 self.context_menu
7581 .borrow()
7582 .as_ref()
7583 .map(|menu| menu.origin())
7584 }
7585
7586 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7587 self.context_menu_options = Some(options);
7588 }
7589
7590 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7591 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7592
7593 fn render_edit_prediction_popover(
7594 &mut self,
7595 text_bounds: &Bounds<Pixels>,
7596 content_origin: gpui::Point<Pixels>,
7597 right_margin: Pixels,
7598 editor_snapshot: &EditorSnapshot,
7599 visible_row_range: Range<DisplayRow>,
7600 scroll_top: f32,
7601 scroll_bottom: f32,
7602 line_layouts: &[LineWithInvisibles],
7603 line_height: Pixels,
7604 scroll_pixel_position: gpui::Point<Pixels>,
7605 newest_selection_head: Option<DisplayPoint>,
7606 editor_width: Pixels,
7607 style: &EditorStyle,
7608 window: &mut Window,
7609 cx: &mut App,
7610 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7611 if self.mode().is_minimap() {
7612 return None;
7613 }
7614 let active_inline_completion = self.active_inline_completion.as_ref()?;
7615
7616 if self.edit_prediction_visible_in_cursor_popover(true) {
7617 return None;
7618 }
7619
7620 match &active_inline_completion.completion {
7621 InlineCompletion::Move { target, .. } => {
7622 let target_display_point = target.to_display_point(editor_snapshot);
7623
7624 if self.edit_prediction_requires_modifier() {
7625 if !self.edit_prediction_preview_is_active() {
7626 return None;
7627 }
7628
7629 self.render_edit_prediction_modifier_jump_popover(
7630 text_bounds,
7631 content_origin,
7632 visible_row_range,
7633 line_layouts,
7634 line_height,
7635 scroll_pixel_position,
7636 newest_selection_head,
7637 target_display_point,
7638 window,
7639 cx,
7640 )
7641 } else {
7642 self.render_edit_prediction_eager_jump_popover(
7643 text_bounds,
7644 content_origin,
7645 editor_snapshot,
7646 visible_row_range,
7647 scroll_top,
7648 scroll_bottom,
7649 line_height,
7650 scroll_pixel_position,
7651 target_display_point,
7652 editor_width,
7653 window,
7654 cx,
7655 )
7656 }
7657 }
7658 InlineCompletion::Edit {
7659 display_mode: EditDisplayMode::Inline,
7660 ..
7661 } => None,
7662 InlineCompletion::Edit {
7663 display_mode: EditDisplayMode::TabAccept,
7664 edits,
7665 ..
7666 } => {
7667 let range = &edits.first()?.0;
7668 let target_display_point = range.end.to_display_point(editor_snapshot);
7669
7670 self.render_edit_prediction_end_of_line_popover(
7671 "Accept",
7672 editor_snapshot,
7673 visible_row_range,
7674 target_display_point,
7675 line_height,
7676 scroll_pixel_position,
7677 content_origin,
7678 editor_width,
7679 window,
7680 cx,
7681 )
7682 }
7683 InlineCompletion::Edit {
7684 edits,
7685 edit_preview,
7686 display_mode: EditDisplayMode::DiffPopover,
7687 snapshot,
7688 } => self.render_edit_prediction_diff_popover(
7689 text_bounds,
7690 content_origin,
7691 right_margin,
7692 editor_snapshot,
7693 visible_row_range,
7694 line_layouts,
7695 line_height,
7696 scroll_pixel_position,
7697 newest_selection_head,
7698 editor_width,
7699 style,
7700 edits,
7701 edit_preview,
7702 snapshot,
7703 window,
7704 cx,
7705 ),
7706 }
7707 }
7708
7709 fn render_edit_prediction_modifier_jump_popover(
7710 &mut self,
7711 text_bounds: &Bounds<Pixels>,
7712 content_origin: gpui::Point<Pixels>,
7713 visible_row_range: Range<DisplayRow>,
7714 line_layouts: &[LineWithInvisibles],
7715 line_height: Pixels,
7716 scroll_pixel_position: gpui::Point<Pixels>,
7717 newest_selection_head: Option<DisplayPoint>,
7718 target_display_point: DisplayPoint,
7719 window: &mut Window,
7720 cx: &mut App,
7721 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7722 let scrolled_content_origin =
7723 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7724
7725 const SCROLL_PADDING_Y: Pixels = px(12.);
7726
7727 if target_display_point.row() < visible_row_range.start {
7728 return self.render_edit_prediction_scroll_popover(
7729 |_| SCROLL_PADDING_Y,
7730 IconName::ArrowUp,
7731 visible_row_range,
7732 line_layouts,
7733 newest_selection_head,
7734 scrolled_content_origin,
7735 window,
7736 cx,
7737 );
7738 } else if target_display_point.row() >= visible_row_range.end {
7739 return self.render_edit_prediction_scroll_popover(
7740 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7741 IconName::ArrowDown,
7742 visible_row_range,
7743 line_layouts,
7744 newest_selection_head,
7745 scrolled_content_origin,
7746 window,
7747 cx,
7748 );
7749 }
7750
7751 const POLE_WIDTH: Pixels = px(2.);
7752
7753 let line_layout =
7754 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7755 let target_column = target_display_point.column() as usize;
7756
7757 let target_x = line_layout.x_for_index(target_column);
7758 let target_y =
7759 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7760
7761 let flag_on_right = target_x < text_bounds.size.width / 2.;
7762
7763 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7764 border_color.l += 0.001;
7765
7766 let mut element = v_flex()
7767 .items_end()
7768 .when(flag_on_right, |el| el.items_start())
7769 .child(if flag_on_right {
7770 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7771 .rounded_bl(px(0.))
7772 .rounded_tl(px(0.))
7773 .border_l_2()
7774 .border_color(border_color)
7775 } else {
7776 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7777 .rounded_br(px(0.))
7778 .rounded_tr(px(0.))
7779 .border_r_2()
7780 .border_color(border_color)
7781 })
7782 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7783 .into_any();
7784
7785 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7786
7787 let mut origin = scrolled_content_origin + point(target_x, target_y)
7788 - point(
7789 if flag_on_right {
7790 POLE_WIDTH
7791 } else {
7792 size.width - POLE_WIDTH
7793 },
7794 size.height - line_height,
7795 );
7796
7797 origin.x = origin.x.max(content_origin.x);
7798
7799 element.prepaint_at(origin, window, cx);
7800
7801 Some((element, origin))
7802 }
7803
7804 fn render_edit_prediction_scroll_popover(
7805 &mut self,
7806 to_y: impl Fn(Size<Pixels>) -> Pixels,
7807 scroll_icon: IconName,
7808 visible_row_range: Range<DisplayRow>,
7809 line_layouts: &[LineWithInvisibles],
7810 newest_selection_head: Option<DisplayPoint>,
7811 scrolled_content_origin: gpui::Point<Pixels>,
7812 window: &mut Window,
7813 cx: &mut App,
7814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7815 let mut element = self
7816 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7817 .into_any();
7818
7819 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7820
7821 let cursor = newest_selection_head?;
7822 let cursor_row_layout =
7823 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7824 let cursor_column = cursor.column() as usize;
7825
7826 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7827
7828 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7829
7830 element.prepaint_at(origin, window, cx);
7831 Some((element, origin))
7832 }
7833
7834 fn render_edit_prediction_eager_jump_popover(
7835 &mut self,
7836 text_bounds: &Bounds<Pixels>,
7837 content_origin: gpui::Point<Pixels>,
7838 editor_snapshot: &EditorSnapshot,
7839 visible_row_range: Range<DisplayRow>,
7840 scroll_top: f32,
7841 scroll_bottom: f32,
7842 line_height: Pixels,
7843 scroll_pixel_position: gpui::Point<Pixels>,
7844 target_display_point: DisplayPoint,
7845 editor_width: Pixels,
7846 window: &mut Window,
7847 cx: &mut App,
7848 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7849 if target_display_point.row().as_f32() < scroll_top {
7850 let mut element = self
7851 .render_edit_prediction_line_popover(
7852 "Jump to Edit",
7853 Some(IconName::ArrowUp),
7854 window,
7855 cx,
7856 )?
7857 .into_any();
7858
7859 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7860 let offset = point(
7861 (text_bounds.size.width - size.width) / 2.,
7862 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7863 );
7864
7865 let origin = text_bounds.origin + offset;
7866 element.prepaint_at(origin, window, cx);
7867 Some((element, origin))
7868 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7869 let mut element = self
7870 .render_edit_prediction_line_popover(
7871 "Jump to Edit",
7872 Some(IconName::ArrowDown),
7873 window,
7874 cx,
7875 )?
7876 .into_any();
7877
7878 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7879 let offset = point(
7880 (text_bounds.size.width - size.width) / 2.,
7881 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7882 );
7883
7884 let origin = text_bounds.origin + offset;
7885 element.prepaint_at(origin, window, cx);
7886 Some((element, origin))
7887 } else {
7888 self.render_edit_prediction_end_of_line_popover(
7889 "Jump to Edit",
7890 editor_snapshot,
7891 visible_row_range,
7892 target_display_point,
7893 line_height,
7894 scroll_pixel_position,
7895 content_origin,
7896 editor_width,
7897 window,
7898 cx,
7899 )
7900 }
7901 }
7902
7903 fn render_edit_prediction_end_of_line_popover(
7904 self: &mut Editor,
7905 label: &'static str,
7906 editor_snapshot: &EditorSnapshot,
7907 visible_row_range: Range<DisplayRow>,
7908 target_display_point: DisplayPoint,
7909 line_height: Pixels,
7910 scroll_pixel_position: gpui::Point<Pixels>,
7911 content_origin: gpui::Point<Pixels>,
7912 editor_width: Pixels,
7913 window: &mut Window,
7914 cx: &mut App,
7915 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7916 let target_line_end = DisplayPoint::new(
7917 target_display_point.row(),
7918 editor_snapshot.line_len(target_display_point.row()),
7919 );
7920
7921 let mut element = self
7922 .render_edit_prediction_line_popover(label, None, window, cx)?
7923 .into_any();
7924
7925 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7926
7927 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7928
7929 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7930 let mut origin = start_point
7931 + line_origin
7932 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7933 origin.x = origin.x.max(content_origin.x);
7934
7935 let max_x = content_origin.x + editor_width - size.width;
7936
7937 if origin.x > max_x {
7938 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7939
7940 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7941 origin.y += offset;
7942 IconName::ArrowUp
7943 } else {
7944 origin.y -= offset;
7945 IconName::ArrowDown
7946 };
7947
7948 element = self
7949 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7950 .into_any();
7951
7952 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7953
7954 origin.x = content_origin.x + editor_width - size.width - px(2.);
7955 }
7956
7957 element.prepaint_at(origin, window, cx);
7958 Some((element, origin))
7959 }
7960
7961 fn render_edit_prediction_diff_popover(
7962 self: &Editor,
7963 text_bounds: &Bounds<Pixels>,
7964 content_origin: gpui::Point<Pixels>,
7965 right_margin: Pixels,
7966 editor_snapshot: &EditorSnapshot,
7967 visible_row_range: Range<DisplayRow>,
7968 line_layouts: &[LineWithInvisibles],
7969 line_height: Pixels,
7970 scroll_pixel_position: gpui::Point<Pixels>,
7971 newest_selection_head: Option<DisplayPoint>,
7972 editor_width: Pixels,
7973 style: &EditorStyle,
7974 edits: &Vec<(Range<Anchor>, String)>,
7975 edit_preview: &Option<language::EditPreview>,
7976 snapshot: &language::BufferSnapshot,
7977 window: &mut Window,
7978 cx: &mut App,
7979 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7980 let edit_start = edits
7981 .first()
7982 .unwrap()
7983 .0
7984 .start
7985 .to_display_point(editor_snapshot);
7986 let edit_end = edits
7987 .last()
7988 .unwrap()
7989 .0
7990 .end
7991 .to_display_point(editor_snapshot);
7992
7993 let is_visible = visible_row_range.contains(&edit_start.row())
7994 || visible_row_range.contains(&edit_end.row());
7995 if !is_visible {
7996 return None;
7997 }
7998
7999 let highlighted_edits =
8000 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8001
8002 let styled_text = highlighted_edits.to_styled_text(&style.text);
8003 let line_count = highlighted_edits.text.lines().count();
8004
8005 const BORDER_WIDTH: Pixels = px(1.);
8006
8007 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8008 let has_keybind = keybind.is_some();
8009
8010 let mut element = h_flex()
8011 .items_start()
8012 .child(
8013 h_flex()
8014 .bg(cx.theme().colors().editor_background)
8015 .border(BORDER_WIDTH)
8016 .shadow_sm()
8017 .border_color(cx.theme().colors().border)
8018 .rounded_l_lg()
8019 .when(line_count > 1, |el| el.rounded_br_lg())
8020 .pr_1()
8021 .child(styled_text),
8022 )
8023 .child(
8024 h_flex()
8025 .h(line_height + BORDER_WIDTH * 2.)
8026 .px_1p5()
8027 .gap_1()
8028 // Workaround: For some reason, there's a gap if we don't do this
8029 .ml(-BORDER_WIDTH)
8030 .shadow(smallvec![gpui::BoxShadow {
8031 color: gpui::black().opacity(0.05),
8032 offset: point(px(1.), px(1.)),
8033 blur_radius: px(2.),
8034 spread_radius: px(0.),
8035 }])
8036 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8037 .border(BORDER_WIDTH)
8038 .border_color(cx.theme().colors().border)
8039 .rounded_r_lg()
8040 .id("edit_prediction_diff_popover_keybind")
8041 .when(!has_keybind, |el| {
8042 let status_colors = cx.theme().status();
8043
8044 el.bg(status_colors.error_background)
8045 .border_color(status_colors.error.opacity(0.6))
8046 .child(Icon::new(IconName::Info).color(Color::Error))
8047 .cursor_default()
8048 .hoverable_tooltip(move |_window, cx| {
8049 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8050 })
8051 })
8052 .children(keybind),
8053 )
8054 .into_any();
8055
8056 let longest_row =
8057 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8058 let longest_line_width = if visible_row_range.contains(&longest_row) {
8059 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8060 } else {
8061 layout_line(
8062 longest_row,
8063 editor_snapshot,
8064 style,
8065 editor_width,
8066 |_| false,
8067 window,
8068 cx,
8069 )
8070 .width
8071 };
8072
8073 let viewport_bounds =
8074 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8075 right: -right_margin,
8076 ..Default::default()
8077 });
8078
8079 let x_after_longest =
8080 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8081 - scroll_pixel_position.x;
8082
8083 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8084
8085 // Fully visible if it can be displayed within the window (allow overlapping other
8086 // panes). However, this is only allowed if the popover starts within text_bounds.
8087 let can_position_to_the_right = x_after_longest < text_bounds.right()
8088 && x_after_longest + element_bounds.width < viewport_bounds.right();
8089
8090 let mut origin = if can_position_to_the_right {
8091 point(
8092 x_after_longest,
8093 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8094 - scroll_pixel_position.y,
8095 )
8096 } else {
8097 let cursor_row = newest_selection_head.map(|head| head.row());
8098 let above_edit = edit_start
8099 .row()
8100 .0
8101 .checked_sub(line_count as u32)
8102 .map(DisplayRow);
8103 let below_edit = Some(edit_end.row() + 1);
8104 let above_cursor =
8105 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8106 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8107
8108 // Place the edit popover adjacent to the edit if there is a location
8109 // available that is onscreen and does not obscure the cursor. Otherwise,
8110 // place it adjacent to the cursor.
8111 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8112 .into_iter()
8113 .flatten()
8114 .find(|&start_row| {
8115 let end_row = start_row + line_count as u32;
8116 visible_row_range.contains(&start_row)
8117 && visible_row_range.contains(&end_row)
8118 && cursor_row.map_or(true, |cursor_row| {
8119 !((start_row..end_row).contains(&cursor_row))
8120 })
8121 })?;
8122
8123 content_origin
8124 + point(
8125 -scroll_pixel_position.x,
8126 row_target.as_f32() * line_height - scroll_pixel_position.y,
8127 )
8128 };
8129
8130 origin.x -= BORDER_WIDTH;
8131
8132 window.defer_draw(element, origin, 1);
8133
8134 // Do not return an element, since it will already be drawn due to defer_draw.
8135 None
8136 }
8137
8138 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8139 px(30.)
8140 }
8141
8142 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8143 if self.read_only(cx) {
8144 cx.theme().players().read_only()
8145 } else {
8146 self.style.as_ref().unwrap().local_player
8147 }
8148 }
8149
8150 fn render_edit_prediction_accept_keybind(
8151 &self,
8152 window: &mut Window,
8153 cx: &App,
8154 ) -> Option<AnyElement> {
8155 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8156 let accept_keystroke = accept_binding.keystroke()?;
8157
8158 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8159
8160 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8161 Color::Accent
8162 } else {
8163 Color::Muted
8164 };
8165
8166 h_flex()
8167 .px_0p5()
8168 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8169 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8170 .text_size(TextSize::XSmall.rems(cx))
8171 .child(h_flex().children(ui::render_modifiers(
8172 &accept_keystroke.modifiers,
8173 PlatformStyle::platform(),
8174 Some(modifiers_color),
8175 Some(IconSize::XSmall.rems().into()),
8176 true,
8177 )))
8178 .when(is_platform_style_mac, |parent| {
8179 parent.child(accept_keystroke.key.clone())
8180 })
8181 .when(!is_platform_style_mac, |parent| {
8182 parent.child(
8183 Key::new(
8184 util::capitalize(&accept_keystroke.key),
8185 Some(Color::Default),
8186 )
8187 .size(Some(IconSize::XSmall.rems().into())),
8188 )
8189 })
8190 .into_any()
8191 .into()
8192 }
8193
8194 fn render_edit_prediction_line_popover(
8195 &self,
8196 label: impl Into<SharedString>,
8197 icon: Option<IconName>,
8198 window: &mut Window,
8199 cx: &App,
8200 ) -> Option<Stateful<Div>> {
8201 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8202
8203 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8204 let has_keybind = keybind.is_some();
8205
8206 let result = h_flex()
8207 .id("ep-line-popover")
8208 .py_0p5()
8209 .pl_1()
8210 .pr(padding_right)
8211 .gap_1()
8212 .rounded_md()
8213 .border_1()
8214 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8215 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8216 .shadow_sm()
8217 .when(!has_keybind, |el| {
8218 let status_colors = cx.theme().status();
8219
8220 el.bg(status_colors.error_background)
8221 .border_color(status_colors.error.opacity(0.6))
8222 .pl_2()
8223 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8224 .cursor_default()
8225 .hoverable_tooltip(move |_window, cx| {
8226 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8227 })
8228 })
8229 .children(keybind)
8230 .child(
8231 Label::new(label)
8232 .size(LabelSize::Small)
8233 .when(!has_keybind, |el| {
8234 el.color(cx.theme().status().error.into()).strikethrough()
8235 }),
8236 )
8237 .when(!has_keybind, |el| {
8238 el.child(
8239 h_flex().ml_1().child(
8240 Icon::new(IconName::Info)
8241 .size(IconSize::Small)
8242 .color(cx.theme().status().error.into()),
8243 ),
8244 )
8245 })
8246 .when_some(icon, |element, icon| {
8247 element.child(
8248 div()
8249 .mt(px(1.5))
8250 .child(Icon::new(icon).size(IconSize::Small)),
8251 )
8252 });
8253
8254 Some(result)
8255 }
8256
8257 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8258 let accent_color = cx.theme().colors().text_accent;
8259 let editor_bg_color = cx.theme().colors().editor_background;
8260 editor_bg_color.blend(accent_color.opacity(0.1))
8261 }
8262
8263 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8264 let accent_color = cx.theme().colors().text_accent;
8265 let editor_bg_color = cx.theme().colors().editor_background;
8266 editor_bg_color.blend(accent_color.opacity(0.6))
8267 }
8268
8269 fn render_edit_prediction_cursor_popover(
8270 &self,
8271 min_width: Pixels,
8272 max_width: Pixels,
8273 cursor_point: Point,
8274 style: &EditorStyle,
8275 accept_keystroke: Option<&gpui::Keystroke>,
8276 _window: &Window,
8277 cx: &mut Context<Editor>,
8278 ) -> Option<AnyElement> {
8279 let provider = self.edit_prediction_provider.as_ref()?;
8280
8281 if provider.provider.needs_terms_acceptance(cx) {
8282 return Some(
8283 h_flex()
8284 .min_w(min_width)
8285 .flex_1()
8286 .px_2()
8287 .py_1()
8288 .gap_3()
8289 .elevation_2(cx)
8290 .hover(|style| style.bg(cx.theme().colors().element_hover))
8291 .id("accept-terms")
8292 .cursor_pointer()
8293 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8294 .on_click(cx.listener(|this, _event, window, cx| {
8295 cx.stop_propagation();
8296 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8297 window.dispatch_action(
8298 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8299 cx,
8300 );
8301 }))
8302 .child(
8303 h_flex()
8304 .flex_1()
8305 .gap_2()
8306 .child(Icon::new(IconName::ZedPredict))
8307 .child(Label::new("Accept Terms of Service"))
8308 .child(div().w_full())
8309 .child(
8310 Icon::new(IconName::ArrowUpRight)
8311 .color(Color::Muted)
8312 .size(IconSize::Small),
8313 )
8314 .into_any_element(),
8315 )
8316 .into_any(),
8317 );
8318 }
8319
8320 let is_refreshing = provider.provider.is_refreshing(cx);
8321
8322 fn pending_completion_container() -> Div {
8323 h_flex()
8324 .h_full()
8325 .flex_1()
8326 .gap_2()
8327 .child(Icon::new(IconName::ZedPredict))
8328 }
8329
8330 let completion = match &self.active_inline_completion {
8331 Some(prediction) => {
8332 if !self.has_visible_completions_menu() {
8333 const RADIUS: Pixels = px(6.);
8334 const BORDER_WIDTH: Pixels = px(1.);
8335
8336 return Some(
8337 h_flex()
8338 .elevation_2(cx)
8339 .border(BORDER_WIDTH)
8340 .border_color(cx.theme().colors().border)
8341 .when(accept_keystroke.is_none(), |el| {
8342 el.border_color(cx.theme().status().error)
8343 })
8344 .rounded(RADIUS)
8345 .rounded_tl(px(0.))
8346 .overflow_hidden()
8347 .child(div().px_1p5().child(match &prediction.completion {
8348 InlineCompletion::Move { target, snapshot } => {
8349 use text::ToPoint as _;
8350 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8351 {
8352 Icon::new(IconName::ZedPredictDown)
8353 } else {
8354 Icon::new(IconName::ZedPredictUp)
8355 }
8356 }
8357 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8358 }))
8359 .child(
8360 h_flex()
8361 .gap_1()
8362 .py_1()
8363 .px_2()
8364 .rounded_r(RADIUS - BORDER_WIDTH)
8365 .border_l_1()
8366 .border_color(cx.theme().colors().border)
8367 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8368 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8369 el.child(
8370 Label::new("Hold")
8371 .size(LabelSize::Small)
8372 .when(accept_keystroke.is_none(), |el| {
8373 el.strikethrough()
8374 })
8375 .line_height_style(LineHeightStyle::UiLabel),
8376 )
8377 })
8378 .id("edit_prediction_cursor_popover_keybind")
8379 .when(accept_keystroke.is_none(), |el| {
8380 let status_colors = cx.theme().status();
8381
8382 el.bg(status_colors.error_background)
8383 .border_color(status_colors.error.opacity(0.6))
8384 .child(Icon::new(IconName::Info).color(Color::Error))
8385 .cursor_default()
8386 .hoverable_tooltip(move |_window, cx| {
8387 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8388 .into()
8389 })
8390 })
8391 .when_some(
8392 accept_keystroke.as_ref(),
8393 |el, accept_keystroke| {
8394 el.child(h_flex().children(ui::render_modifiers(
8395 &accept_keystroke.modifiers,
8396 PlatformStyle::platform(),
8397 Some(Color::Default),
8398 Some(IconSize::XSmall.rems().into()),
8399 false,
8400 )))
8401 },
8402 ),
8403 )
8404 .into_any(),
8405 );
8406 }
8407
8408 self.render_edit_prediction_cursor_popover_preview(
8409 prediction,
8410 cursor_point,
8411 style,
8412 cx,
8413 )?
8414 }
8415
8416 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8417 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8418 stale_completion,
8419 cursor_point,
8420 style,
8421 cx,
8422 )?,
8423
8424 None => {
8425 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8426 }
8427 },
8428
8429 None => pending_completion_container().child(Label::new("No Prediction")),
8430 };
8431
8432 let completion = if is_refreshing {
8433 completion
8434 .with_animation(
8435 "loading-completion",
8436 Animation::new(Duration::from_secs(2))
8437 .repeat()
8438 .with_easing(pulsating_between(0.4, 0.8)),
8439 |label, delta| label.opacity(delta),
8440 )
8441 .into_any_element()
8442 } else {
8443 completion.into_any_element()
8444 };
8445
8446 let has_completion = self.active_inline_completion.is_some();
8447
8448 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8449 Some(
8450 h_flex()
8451 .min_w(min_width)
8452 .max_w(max_width)
8453 .flex_1()
8454 .elevation_2(cx)
8455 .border_color(cx.theme().colors().border)
8456 .child(
8457 div()
8458 .flex_1()
8459 .py_1()
8460 .px_2()
8461 .overflow_hidden()
8462 .child(completion),
8463 )
8464 .when_some(accept_keystroke, |el, accept_keystroke| {
8465 if !accept_keystroke.modifiers.modified() {
8466 return el;
8467 }
8468
8469 el.child(
8470 h_flex()
8471 .h_full()
8472 .border_l_1()
8473 .rounded_r_lg()
8474 .border_color(cx.theme().colors().border)
8475 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8476 .gap_1()
8477 .py_1()
8478 .px_2()
8479 .child(
8480 h_flex()
8481 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8482 .when(is_platform_style_mac, |parent| parent.gap_1())
8483 .child(h_flex().children(ui::render_modifiers(
8484 &accept_keystroke.modifiers,
8485 PlatformStyle::platform(),
8486 Some(if !has_completion {
8487 Color::Muted
8488 } else {
8489 Color::Default
8490 }),
8491 None,
8492 false,
8493 ))),
8494 )
8495 .child(Label::new("Preview").into_any_element())
8496 .opacity(if has_completion { 1.0 } else { 0.4 }),
8497 )
8498 })
8499 .into_any(),
8500 )
8501 }
8502
8503 fn render_edit_prediction_cursor_popover_preview(
8504 &self,
8505 completion: &InlineCompletionState,
8506 cursor_point: Point,
8507 style: &EditorStyle,
8508 cx: &mut Context<Editor>,
8509 ) -> Option<Div> {
8510 use text::ToPoint as _;
8511
8512 fn render_relative_row_jump(
8513 prefix: impl Into<String>,
8514 current_row: u32,
8515 target_row: u32,
8516 ) -> Div {
8517 let (row_diff, arrow) = if target_row < current_row {
8518 (current_row - target_row, IconName::ArrowUp)
8519 } else {
8520 (target_row - current_row, IconName::ArrowDown)
8521 };
8522
8523 h_flex()
8524 .child(
8525 Label::new(format!("{}{}", prefix.into(), row_diff))
8526 .color(Color::Muted)
8527 .size(LabelSize::Small),
8528 )
8529 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8530 }
8531
8532 match &completion.completion {
8533 InlineCompletion::Move {
8534 target, snapshot, ..
8535 } => Some(
8536 h_flex()
8537 .px_2()
8538 .gap_2()
8539 .flex_1()
8540 .child(
8541 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8542 Icon::new(IconName::ZedPredictDown)
8543 } else {
8544 Icon::new(IconName::ZedPredictUp)
8545 },
8546 )
8547 .child(Label::new("Jump to Edit")),
8548 ),
8549
8550 InlineCompletion::Edit {
8551 edits,
8552 edit_preview,
8553 snapshot,
8554 display_mode: _,
8555 } => {
8556 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8557
8558 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8559 &snapshot,
8560 &edits,
8561 edit_preview.as_ref()?,
8562 true,
8563 cx,
8564 )
8565 .first_line_preview();
8566
8567 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8568 .with_default_highlights(&style.text, highlighted_edits.highlights);
8569
8570 let preview = h_flex()
8571 .gap_1()
8572 .min_w_16()
8573 .child(styled_text)
8574 .when(has_more_lines, |parent| parent.child("…"));
8575
8576 let left = if first_edit_row != cursor_point.row {
8577 render_relative_row_jump("", cursor_point.row, first_edit_row)
8578 .into_any_element()
8579 } else {
8580 Icon::new(IconName::ZedPredict).into_any_element()
8581 };
8582
8583 Some(
8584 h_flex()
8585 .h_full()
8586 .flex_1()
8587 .gap_2()
8588 .pr_1()
8589 .overflow_x_hidden()
8590 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8591 .child(left)
8592 .child(preview),
8593 )
8594 }
8595 }
8596 }
8597
8598 pub fn render_context_menu(
8599 &self,
8600 style: &EditorStyle,
8601 max_height_in_lines: u32,
8602 window: &mut Window,
8603 cx: &mut Context<Editor>,
8604 ) -> Option<AnyElement> {
8605 let menu = self.context_menu.borrow();
8606 let menu = menu.as_ref()?;
8607 if !menu.visible() {
8608 return None;
8609 };
8610 Some(menu.render(style, max_height_in_lines, window, cx))
8611 }
8612
8613 fn render_context_menu_aside(
8614 &mut self,
8615 max_size: Size<Pixels>,
8616 window: &mut Window,
8617 cx: &mut Context<Editor>,
8618 ) -> Option<AnyElement> {
8619 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8620 if menu.visible() {
8621 menu.render_aside(self, max_size, window, cx)
8622 } else {
8623 None
8624 }
8625 })
8626 }
8627
8628 fn hide_context_menu(
8629 &mut self,
8630 window: &mut Window,
8631 cx: &mut Context<Self>,
8632 ) -> Option<CodeContextMenu> {
8633 cx.notify();
8634 self.completion_tasks.clear();
8635 let context_menu = self.context_menu.borrow_mut().take();
8636 self.stale_inline_completion_in_menu.take();
8637 self.update_visible_inline_completion(window, cx);
8638 context_menu
8639 }
8640
8641 fn show_snippet_choices(
8642 &mut self,
8643 choices: &Vec<String>,
8644 selection: Range<Anchor>,
8645 cx: &mut Context<Self>,
8646 ) {
8647 if selection.start.buffer_id.is_none() {
8648 return;
8649 }
8650 let buffer_id = selection.start.buffer_id.unwrap();
8651 let buffer = self.buffer().read(cx).buffer(buffer_id);
8652 let id = post_inc(&mut self.next_completion_id);
8653 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8654
8655 if let Some(buffer) = buffer {
8656 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8657 CompletionsMenu::new_snippet_choices(
8658 id,
8659 true,
8660 choices,
8661 selection,
8662 buffer,
8663 snippet_sort_order,
8664 ),
8665 ));
8666 }
8667 }
8668
8669 pub fn insert_snippet(
8670 &mut self,
8671 insertion_ranges: &[Range<usize>],
8672 snippet: Snippet,
8673 window: &mut Window,
8674 cx: &mut Context<Self>,
8675 ) -> Result<()> {
8676 struct Tabstop<T> {
8677 is_end_tabstop: bool,
8678 ranges: Vec<Range<T>>,
8679 choices: Option<Vec<String>>,
8680 }
8681
8682 let tabstops = self.buffer.update(cx, |buffer, cx| {
8683 let snippet_text: Arc<str> = snippet.text.clone().into();
8684 let edits = insertion_ranges
8685 .iter()
8686 .cloned()
8687 .map(|range| (range, snippet_text.clone()));
8688 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8689
8690 let snapshot = &*buffer.read(cx);
8691 let snippet = &snippet;
8692 snippet
8693 .tabstops
8694 .iter()
8695 .map(|tabstop| {
8696 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8697 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8698 });
8699 let mut tabstop_ranges = tabstop
8700 .ranges
8701 .iter()
8702 .flat_map(|tabstop_range| {
8703 let mut delta = 0_isize;
8704 insertion_ranges.iter().map(move |insertion_range| {
8705 let insertion_start = insertion_range.start as isize + delta;
8706 delta +=
8707 snippet.text.len() as isize - insertion_range.len() as isize;
8708
8709 let start = ((insertion_start + tabstop_range.start) as usize)
8710 .min(snapshot.len());
8711 let end = ((insertion_start + tabstop_range.end) as usize)
8712 .min(snapshot.len());
8713 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8714 })
8715 })
8716 .collect::<Vec<_>>();
8717 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8718
8719 Tabstop {
8720 is_end_tabstop,
8721 ranges: tabstop_ranges,
8722 choices: tabstop.choices.clone(),
8723 }
8724 })
8725 .collect::<Vec<_>>()
8726 });
8727 if let Some(tabstop) = tabstops.first() {
8728 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8729 s.select_ranges(tabstop.ranges.iter().cloned());
8730 });
8731
8732 if let Some(choices) = &tabstop.choices {
8733 if let Some(selection) = tabstop.ranges.first() {
8734 self.show_snippet_choices(choices, selection.clone(), cx)
8735 }
8736 }
8737
8738 // If we're already at the last tabstop and it's at the end of the snippet,
8739 // we're done, we don't need to keep the state around.
8740 if !tabstop.is_end_tabstop {
8741 let choices = tabstops
8742 .iter()
8743 .map(|tabstop| tabstop.choices.clone())
8744 .collect();
8745
8746 let ranges = tabstops
8747 .into_iter()
8748 .map(|tabstop| tabstop.ranges)
8749 .collect::<Vec<_>>();
8750
8751 self.snippet_stack.push(SnippetState {
8752 active_index: 0,
8753 ranges,
8754 choices,
8755 });
8756 }
8757
8758 // Check whether the just-entered snippet ends with an auto-closable bracket.
8759 if self.autoclose_regions.is_empty() {
8760 let snapshot = self.buffer.read(cx).snapshot(cx);
8761 for selection in &mut self.selections.all::<Point>(cx) {
8762 let selection_head = selection.head();
8763 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8764 continue;
8765 };
8766
8767 let mut bracket_pair = None;
8768 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8769 let prev_chars = snapshot
8770 .reversed_chars_at(selection_head)
8771 .collect::<String>();
8772 for (pair, enabled) in scope.brackets() {
8773 if enabled
8774 && pair.close
8775 && prev_chars.starts_with(pair.start.as_str())
8776 && next_chars.starts_with(pair.end.as_str())
8777 {
8778 bracket_pair = Some(pair.clone());
8779 break;
8780 }
8781 }
8782 if let Some(pair) = bracket_pair {
8783 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8784 let autoclose_enabled =
8785 self.use_autoclose && snapshot_settings.use_autoclose;
8786 if autoclose_enabled {
8787 let start = snapshot.anchor_after(selection_head);
8788 let end = snapshot.anchor_after(selection_head);
8789 self.autoclose_regions.push(AutocloseRegion {
8790 selection_id: selection.id,
8791 range: start..end,
8792 pair,
8793 });
8794 }
8795 }
8796 }
8797 }
8798 }
8799 Ok(())
8800 }
8801
8802 pub fn move_to_next_snippet_tabstop(
8803 &mut self,
8804 window: &mut Window,
8805 cx: &mut Context<Self>,
8806 ) -> bool {
8807 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8808 }
8809
8810 pub fn move_to_prev_snippet_tabstop(
8811 &mut self,
8812 window: &mut Window,
8813 cx: &mut Context<Self>,
8814 ) -> bool {
8815 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8816 }
8817
8818 pub fn move_to_snippet_tabstop(
8819 &mut self,
8820 bias: Bias,
8821 window: &mut Window,
8822 cx: &mut Context<Self>,
8823 ) -> bool {
8824 if let Some(mut snippet) = self.snippet_stack.pop() {
8825 match bias {
8826 Bias::Left => {
8827 if snippet.active_index > 0 {
8828 snippet.active_index -= 1;
8829 } else {
8830 self.snippet_stack.push(snippet);
8831 return false;
8832 }
8833 }
8834 Bias::Right => {
8835 if snippet.active_index + 1 < snippet.ranges.len() {
8836 snippet.active_index += 1;
8837 } else {
8838 self.snippet_stack.push(snippet);
8839 return false;
8840 }
8841 }
8842 }
8843 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8844 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8845 s.select_anchor_ranges(current_ranges.iter().cloned())
8846 });
8847
8848 if let Some(choices) = &snippet.choices[snippet.active_index] {
8849 if let Some(selection) = current_ranges.first() {
8850 self.show_snippet_choices(&choices, selection.clone(), cx);
8851 }
8852 }
8853
8854 // If snippet state is not at the last tabstop, push it back on the stack
8855 if snippet.active_index + 1 < snippet.ranges.len() {
8856 self.snippet_stack.push(snippet);
8857 }
8858 return true;
8859 }
8860 }
8861
8862 false
8863 }
8864
8865 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8866 self.transact(window, cx, |this, window, cx| {
8867 this.select_all(&SelectAll, window, cx);
8868 this.insert("", window, cx);
8869 });
8870 }
8871
8872 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8873 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8874 self.transact(window, cx, |this, window, cx| {
8875 this.select_autoclose_pair(window, cx);
8876 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8877 if !this.linked_edit_ranges.is_empty() {
8878 let selections = this.selections.all::<MultiBufferPoint>(cx);
8879 let snapshot = this.buffer.read(cx).snapshot(cx);
8880
8881 for selection in selections.iter() {
8882 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8883 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8884 if selection_start.buffer_id != selection_end.buffer_id {
8885 continue;
8886 }
8887 if let Some(ranges) =
8888 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8889 {
8890 for (buffer, entries) in ranges {
8891 linked_ranges.entry(buffer).or_default().extend(entries);
8892 }
8893 }
8894 }
8895 }
8896
8897 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8898 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8899 for selection in &mut selections {
8900 if selection.is_empty() {
8901 let old_head = selection.head();
8902 let mut new_head =
8903 movement::left(&display_map, old_head.to_display_point(&display_map))
8904 .to_point(&display_map);
8905 if let Some((buffer, line_buffer_range)) = display_map
8906 .buffer_snapshot
8907 .buffer_line_for_row(MultiBufferRow(old_head.row))
8908 {
8909 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8910 let indent_len = match indent_size.kind {
8911 IndentKind::Space => {
8912 buffer.settings_at(line_buffer_range.start, cx).tab_size
8913 }
8914 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8915 };
8916 if old_head.column <= indent_size.len && old_head.column > 0 {
8917 let indent_len = indent_len.get();
8918 new_head = cmp::min(
8919 new_head,
8920 MultiBufferPoint::new(
8921 old_head.row,
8922 ((old_head.column - 1) / indent_len) * indent_len,
8923 ),
8924 );
8925 }
8926 }
8927
8928 selection.set_head(new_head, SelectionGoal::None);
8929 }
8930 }
8931
8932 this.signature_help_state.set_backspace_pressed(true);
8933 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8934 s.select(selections)
8935 });
8936 this.insert("", window, cx);
8937 let empty_str: Arc<str> = Arc::from("");
8938 for (buffer, edits) in linked_ranges {
8939 let snapshot = buffer.read(cx).snapshot();
8940 use text::ToPoint as TP;
8941
8942 let edits = edits
8943 .into_iter()
8944 .map(|range| {
8945 let end_point = TP::to_point(&range.end, &snapshot);
8946 let mut start_point = TP::to_point(&range.start, &snapshot);
8947
8948 if end_point == start_point {
8949 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8950 .saturating_sub(1);
8951 start_point =
8952 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8953 };
8954
8955 (start_point..end_point, empty_str.clone())
8956 })
8957 .sorted_by_key(|(range, _)| range.start)
8958 .collect::<Vec<_>>();
8959 buffer.update(cx, |this, cx| {
8960 this.edit(edits, None, cx);
8961 })
8962 }
8963 this.refresh_inline_completion(true, false, window, cx);
8964 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8965 });
8966 }
8967
8968 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8970 self.transact(window, cx, |this, window, cx| {
8971 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8972 s.move_with(|map, selection| {
8973 if selection.is_empty() {
8974 let cursor = movement::right(map, selection.head());
8975 selection.end = cursor;
8976 selection.reversed = true;
8977 selection.goal = SelectionGoal::None;
8978 }
8979 })
8980 });
8981 this.insert("", window, cx);
8982 this.refresh_inline_completion(true, false, window, cx);
8983 });
8984 }
8985
8986 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8987 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8988 if self.move_to_prev_snippet_tabstop(window, cx) {
8989 return;
8990 }
8991 self.outdent(&Outdent, window, cx);
8992 }
8993
8994 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8995 if self.move_to_next_snippet_tabstop(window, cx) {
8996 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8997 return;
8998 }
8999 if self.read_only(cx) {
9000 return;
9001 }
9002 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9003 let mut selections = self.selections.all_adjusted(cx);
9004 let buffer = self.buffer.read(cx);
9005 let snapshot = buffer.snapshot(cx);
9006 let rows_iter = selections.iter().map(|s| s.head().row);
9007 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9008
9009 let has_some_cursor_in_whitespace = selections
9010 .iter()
9011 .filter(|selection| selection.is_empty())
9012 .any(|selection| {
9013 let cursor = selection.head();
9014 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9015 cursor.column < current_indent.len
9016 });
9017
9018 let mut edits = Vec::new();
9019 let mut prev_edited_row = 0;
9020 let mut row_delta = 0;
9021 for selection in &mut selections {
9022 if selection.start.row != prev_edited_row {
9023 row_delta = 0;
9024 }
9025 prev_edited_row = selection.end.row;
9026
9027 // If the selection is non-empty, then increase the indentation of the selected lines.
9028 if !selection.is_empty() {
9029 row_delta =
9030 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9031 continue;
9032 }
9033
9034 let cursor = selection.head();
9035 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9036 if let Some(suggested_indent) =
9037 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9038 {
9039 // Don't do anything if already at suggested indent
9040 // and there is any other cursor which is not
9041 if has_some_cursor_in_whitespace
9042 && cursor.column == current_indent.len
9043 && current_indent.len == suggested_indent.len
9044 {
9045 continue;
9046 }
9047
9048 // Adjust line and move cursor to suggested indent
9049 // if cursor is not at suggested indent
9050 if cursor.column < suggested_indent.len
9051 && cursor.column <= current_indent.len
9052 && current_indent.len <= suggested_indent.len
9053 {
9054 selection.start = Point::new(cursor.row, suggested_indent.len);
9055 selection.end = selection.start;
9056 if row_delta == 0 {
9057 edits.extend(Buffer::edit_for_indent_size_adjustment(
9058 cursor.row,
9059 current_indent,
9060 suggested_indent,
9061 ));
9062 row_delta = suggested_indent.len - current_indent.len;
9063 }
9064 continue;
9065 }
9066
9067 // If current indent is more than suggested indent
9068 // only move cursor to current indent and skip indent
9069 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9070 selection.start = Point::new(cursor.row, current_indent.len);
9071 selection.end = selection.start;
9072 continue;
9073 }
9074 }
9075
9076 // Otherwise, insert a hard or soft tab.
9077 let settings = buffer.language_settings_at(cursor, cx);
9078 let tab_size = if settings.hard_tabs {
9079 IndentSize::tab()
9080 } else {
9081 let tab_size = settings.tab_size.get();
9082 let indent_remainder = snapshot
9083 .text_for_range(Point::new(cursor.row, 0)..cursor)
9084 .flat_map(str::chars)
9085 .fold(row_delta % tab_size, |counter: u32, c| {
9086 if c == '\t' {
9087 0
9088 } else {
9089 (counter + 1) % tab_size
9090 }
9091 });
9092
9093 let chars_to_next_tab_stop = tab_size - indent_remainder;
9094 IndentSize::spaces(chars_to_next_tab_stop)
9095 };
9096 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9097 selection.end = selection.start;
9098 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9099 row_delta += tab_size.len;
9100 }
9101
9102 self.transact(window, cx, |this, window, cx| {
9103 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9104 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9105 s.select(selections)
9106 });
9107 this.refresh_inline_completion(true, false, window, cx);
9108 });
9109 }
9110
9111 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9112 if self.read_only(cx) {
9113 return;
9114 }
9115 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9116 let mut selections = self.selections.all::<Point>(cx);
9117 let mut prev_edited_row = 0;
9118 let mut row_delta = 0;
9119 let mut edits = Vec::new();
9120 let buffer = self.buffer.read(cx);
9121 let snapshot = buffer.snapshot(cx);
9122 for selection in &mut selections {
9123 if selection.start.row != prev_edited_row {
9124 row_delta = 0;
9125 }
9126 prev_edited_row = selection.end.row;
9127
9128 row_delta =
9129 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9130 }
9131
9132 self.transact(window, cx, |this, window, cx| {
9133 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9134 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9135 s.select(selections)
9136 });
9137 });
9138 }
9139
9140 fn indent_selection(
9141 buffer: &MultiBuffer,
9142 snapshot: &MultiBufferSnapshot,
9143 selection: &mut Selection<Point>,
9144 edits: &mut Vec<(Range<Point>, String)>,
9145 delta_for_start_row: u32,
9146 cx: &App,
9147 ) -> u32 {
9148 let settings = buffer.language_settings_at(selection.start, cx);
9149 let tab_size = settings.tab_size.get();
9150 let indent_kind = if settings.hard_tabs {
9151 IndentKind::Tab
9152 } else {
9153 IndentKind::Space
9154 };
9155 let mut start_row = selection.start.row;
9156 let mut end_row = selection.end.row + 1;
9157
9158 // If a selection ends at the beginning of a line, don't indent
9159 // that last line.
9160 if selection.end.column == 0 && selection.end.row > selection.start.row {
9161 end_row -= 1;
9162 }
9163
9164 // Avoid re-indenting a row that has already been indented by a
9165 // previous selection, but still update this selection's column
9166 // to reflect that indentation.
9167 if delta_for_start_row > 0 {
9168 start_row += 1;
9169 selection.start.column += delta_for_start_row;
9170 if selection.end.row == selection.start.row {
9171 selection.end.column += delta_for_start_row;
9172 }
9173 }
9174
9175 let mut delta_for_end_row = 0;
9176 let has_multiple_rows = start_row + 1 != end_row;
9177 for row in start_row..end_row {
9178 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9179 let indent_delta = match (current_indent.kind, indent_kind) {
9180 (IndentKind::Space, IndentKind::Space) => {
9181 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9182 IndentSize::spaces(columns_to_next_tab_stop)
9183 }
9184 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9185 (_, IndentKind::Tab) => IndentSize::tab(),
9186 };
9187
9188 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9189 0
9190 } else {
9191 selection.start.column
9192 };
9193 let row_start = Point::new(row, start);
9194 edits.push((
9195 row_start..row_start,
9196 indent_delta.chars().collect::<String>(),
9197 ));
9198
9199 // Update this selection's endpoints to reflect the indentation.
9200 if row == selection.start.row {
9201 selection.start.column += indent_delta.len;
9202 }
9203 if row == selection.end.row {
9204 selection.end.column += indent_delta.len;
9205 delta_for_end_row = indent_delta.len;
9206 }
9207 }
9208
9209 if selection.start.row == selection.end.row {
9210 delta_for_start_row + delta_for_end_row
9211 } else {
9212 delta_for_end_row
9213 }
9214 }
9215
9216 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9217 if self.read_only(cx) {
9218 return;
9219 }
9220 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9222 let selections = self.selections.all::<Point>(cx);
9223 let mut deletion_ranges = Vec::new();
9224 let mut last_outdent = None;
9225 {
9226 let buffer = self.buffer.read(cx);
9227 let snapshot = buffer.snapshot(cx);
9228 for selection in &selections {
9229 let settings = buffer.language_settings_at(selection.start, cx);
9230 let tab_size = settings.tab_size.get();
9231 let mut rows = selection.spanned_rows(false, &display_map);
9232
9233 // Avoid re-outdenting a row that has already been outdented by a
9234 // previous selection.
9235 if let Some(last_row) = last_outdent {
9236 if last_row == rows.start {
9237 rows.start = rows.start.next_row();
9238 }
9239 }
9240 let has_multiple_rows = rows.len() > 1;
9241 for row in rows.iter_rows() {
9242 let indent_size = snapshot.indent_size_for_line(row);
9243 if indent_size.len > 0 {
9244 let deletion_len = match indent_size.kind {
9245 IndentKind::Space => {
9246 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9247 if columns_to_prev_tab_stop == 0 {
9248 tab_size
9249 } else {
9250 columns_to_prev_tab_stop
9251 }
9252 }
9253 IndentKind::Tab => 1,
9254 };
9255 let start = if has_multiple_rows
9256 || deletion_len > selection.start.column
9257 || indent_size.len < selection.start.column
9258 {
9259 0
9260 } else {
9261 selection.start.column - deletion_len
9262 };
9263 deletion_ranges.push(
9264 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9265 );
9266 last_outdent = Some(row);
9267 }
9268 }
9269 }
9270 }
9271
9272 self.transact(window, cx, |this, window, cx| {
9273 this.buffer.update(cx, |buffer, cx| {
9274 let empty_str: Arc<str> = Arc::default();
9275 buffer.edit(
9276 deletion_ranges
9277 .into_iter()
9278 .map(|range| (range, empty_str.clone())),
9279 None,
9280 cx,
9281 );
9282 });
9283 let selections = this.selections.all::<usize>(cx);
9284 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9285 s.select(selections)
9286 });
9287 });
9288 }
9289
9290 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9291 if self.read_only(cx) {
9292 return;
9293 }
9294 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9295 let selections = self
9296 .selections
9297 .all::<usize>(cx)
9298 .into_iter()
9299 .map(|s| s.range());
9300
9301 self.transact(window, cx, |this, window, cx| {
9302 this.buffer.update(cx, |buffer, cx| {
9303 buffer.autoindent_ranges(selections, cx);
9304 });
9305 let selections = this.selections.all::<usize>(cx);
9306 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9307 s.select(selections)
9308 });
9309 });
9310 }
9311
9312 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9313 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9315 let selections = self.selections.all::<Point>(cx);
9316
9317 let mut new_cursors = Vec::new();
9318 let mut edit_ranges = Vec::new();
9319 let mut selections = selections.iter().peekable();
9320 while let Some(selection) = selections.next() {
9321 let mut rows = selection.spanned_rows(false, &display_map);
9322 let goal_display_column = selection.head().to_display_point(&display_map).column();
9323
9324 // Accumulate contiguous regions of rows that we want to delete.
9325 while let Some(next_selection) = selections.peek() {
9326 let next_rows = next_selection.spanned_rows(false, &display_map);
9327 if next_rows.start <= rows.end {
9328 rows.end = next_rows.end;
9329 selections.next().unwrap();
9330 } else {
9331 break;
9332 }
9333 }
9334
9335 let buffer = &display_map.buffer_snapshot;
9336 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9337 let edit_end;
9338 let cursor_buffer_row;
9339 if buffer.max_point().row >= rows.end.0 {
9340 // If there's a line after the range, delete the \n from the end of the row range
9341 // and position the cursor on the next line.
9342 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9343 cursor_buffer_row = rows.end;
9344 } else {
9345 // If there isn't a line after the range, delete the \n from the line before the
9346 // start of the row range and position the cursor there.
9347 edit_start = edit_start.saturating_sub(1);
9348 edit_end = buffer.len();
9349 cursor_buffer_row = rows.start.previous_row();
9350 }
9351
9352 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9353 *cursor.column_mut() =
9354 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9355
9356 new_cursors.push((
9357 selection.id,
9358 buffer.anchor_after(cursor.to_point(&display_map)),
9359 ));
9360 edit_ranges.push(edit_start..edit_end);
9361 }
9362
9363 self.transact(window, cx, |this, window, cx| {
9364 let buffer = this.buffer.update(cx, |buffer, cx| {
9365 let empty_str: Arc<str> = Arc::default();
9366 buffer.edit(
9367 edit_ranges
9368 .into_iter()
9369 .map(|range| (range, empty_str.clone())),
9370 None,
9371 cx,
9372 );
9373 buffer.snapshot(cx)
9374 });
9375 let new_selections = new_cursors
9376 .into_iter()
9377 .map(|(id, cursor)| {
9378 let cursor = cursor.to_point(&buffer);
9379 Selection {
9380 id,
9381 start: cursor,
9382 end: cursor,
9383 reversed: false,
9384 goal: SelectionGoal::None,
9385 }
9386 })
9387 .collect();
9388
9389 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9390 s.select(new_selections);
9391 });
9392 });
9393 }
9394
9395 pub fn join_lines_impl(
9396 &mut self,
9397 insert_whitespace: bool,
9398 window: &mut Window,
9399 cx: &mut Context<Self>,
9400 ) {
9401 if self.read_only(cx) {
9402 return;
9403 }
9404 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9405 for selection in self.selections.all::<Point>(cx) {
9406 let start = MultiBufferRow(selection.start.row);
9407 // Treat single line selections as if they include the next line. Otherwise this action
9408 // would do nothing for single line selections individual cursors.
9409 let end = if selection.start.row == selection.end.row {
9410 MultiBufferRow(selection.start.row + 1)
9411 } else {
9412 MultiBufferRow(selection.end.row)
9413 };
9414
9415 if let Some(last_row_range) = row_ranges.last_mut() {
9416 if start <= last_row_range.end {
9417 last_row_range.end = end;
9418 continue;
9419 }
9420 }
9421 row_ranges.push(start..end);
9422 }
9423
9424 let snapshot = self.buffer.read(cx).snapshot(cx);
9425 let mut cursor_positions = Vec::new();
9426 for row_range in &row_ranges {
9427 let anchor = snapshot.anchor_before(Point::new(
9428 row_range.end.previous_row().0,
9429 snapshot.line_len(row_range.end.previous_row()),
9430 ));
9431 cursor_positions.push(anchor..anchor);
9432 }
9433
9434 self.transact(window, cx, |this, window, cx| {
9435 for row_range in row_ranges.into_iter().rev() {
9436 for row in row_range.iter_rows().rev() {
9437 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9438 let next_line_row = row.next_row();
9439 let indent = snapshot.indent_size_for_line(next_line_row);
9440 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9441
9442 let replace =
9443 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9444 " "
9445 } else {
9446 ""
9447 };
9448
9449 this.buffer.update(cx, |buffer, cx| {
9450 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9451 });
9452 }
9453 }
9454
9455 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9456 s.select_anchor_ranges(cursor_positions)
9457 });
9458 });
9459 }
9460
9461 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9462 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9463 self.join_lines_impl(true, window, cx);
9464 }
9465
9466 pub fn sort_lines_case_sensitive(
9467 &mut self,
9468 _: &SortLinesCaseSensitive,
9469 window: &mut Window,
9470 cx: &mut Context<Self>,
9471 ) {
9472 self.manipulate_lines(window, cx, |lines| lines.sort())
9473 }
9474
9475 pub fn sort_lines_case_insensitive(
9476 &mut self,
9477 _: &SortLinesCaseInsensitive,
9478 window: &mut Window,
9479 cx: &mut Context<Self>,
9480 ) {
9481 self.manipulate_lines(window, cx, |lines| {
9482 lines.sort_by_key(|line| line.to_lowercase())
9483 })
9484 }
9485
9486 pub fn unique_lines_case_insensitive(
9487 &mut self,
9488 _: &UniqueLinesCaseInsensitive,
9489 window: &mut Window,
9490 cx: &mut Context<Self>,
9491 ) {
9492 self.manipulate_lines(window, cx, |lines| {
9493 let mut seen = HashSet::default();
9494 lines.retain(|line| seen.insert(line.to_lowercase()));
9495 })
9496 }
9497
9498 pub fn unique_lines_case_sensitive(
9499 &mut self,
9500 _: &UniqueLinesCaseSensitive,
9501 window: &mut Window,
9502 cx: &mut Context<Self>,
9503 ) {
9504 self.manipulate_lines(window, cx, |lines| {
9505 let mut seen = HashSet::default();
9506 lines.retain(|line| seen.insert(*line));
9507 })
9508 }
9509
9510 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9511 let Some(project) = self.project.clone() else {
9512 return;
9513 };
9514 self.reload(project, window, cx)
9515 .detach_and_notify_err(window, cx);
9516 }
9517
9518 pub fn restore_file(
9519 &mut self,
9520 _: &::git::RestoreFile,
9521 window: &mut Window,
9522 cx: &mut Context<Self>,
9523 ) {
9524 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9525 let mut buffer_ids = HashSet::default();
9526 let snapshot = self.buffer().read(cx).snapshot(cx);
9527 for selection in self.selections.all::<usize>(cx) {
9528 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9529 }
9530
9531 let buffer = self.buffer().read(cx);
9532 let ranges = buffer_ids
9533 .into_iter()
9534 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9535 .collect::<Vec<_>>();
9536
9537 self.restore_hunks_in_ranges(ranges, window, cx);
9538 }
9539
9540 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9542 let selections = self
9543 .selections
9544 .all(cx)
9545 .into_iter()
9546 .map(|s| s.range())
9547 .collect();
9548 self.restore_hunks_in_ranges(selections, window, cx);
9549 }
9550
9551 pub fn restore_hunks_in_ranges(
9552 &mut self,
9553 ranges: Vec<Range<Point>>,
9554 window: &mut Window,
9555 cx: &mut Context<Editor>,
9556 ) {
9557 let mut revert_changes = HashMap::default();
9558 let chunk_by = self
9559 .snapshot(window, cx)
9560 .hunks_for_ranges(ranges)
9561 .into_iter()
9562 .chunk_by(|hunk| hunk.buffer_id);
9563 for (buffer_id, hunks) in &chunk_by {
9564 let hunks = hunks.collect::<Vec<_>>();
9565 for hunk in &hunks {
9566 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9567 }
9568 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9569 }
9570 drop(chunk_by);
9571 if !revert_changes.is_empty() {
9572 self.transact(window, cx, |editor, window, cx| {
9573 editor.restore(revert_changes, window, cx);
9574 });
9575 }
9576 }
9577
9578 pub fn open_active_item_in_terminal(
9579 &mut self,
9580 _: &OpenInTerminal,
9581 window: &mut Window,
9582 cx: &mut Context<Self>,
9583 ) {
9584 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9585 let project_path = buffer.read(cx).project_path(cx)?;
9586 let project = self.project.as_ref()?.read(cx);
9587 let entry = project.entry_for_path(&project_path, cx)?;
9588 let parent = match &entry.canonical_path {
9589 Some(canonical_path) => canonical_path.to_path_buf(),
9590 None => project.absolute_path(&project_path, cx)?,
9591 }
9592 .parent()?
9593 .to_path_buf();
9594 Some(parent)
9595 }) {
9596 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9597 }
9598 }
9599
9600 fn set_breakpoint_context_menu(
9601 &mut self,
9602 display_row: DisplayRow,
9603 position: Option<Anchor>,
9604 clicked_point: gpui::Point<Pixels>,
9605 window: &mut Window,
9606 cx: &mut Context<Self>,
9607 ) {
9608 if !cx.has_flag::<DebuggerFeatureFlag>() {
9609 return;
9610 }
9611 let source = self
9612 .buffer
9613 .read(cx)
9614 .snapshot(cx)
9615 .anchor_before(Point::new(display_row.0, 0u32));
9616
9617 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9618
9619 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9620 self,
9621 source,
9622 clicked_point,
9623 context_menu,
9624 window,
9625 cx,
9626 );
9627 }
9628
9629 fn add_edit_breakpoint_block(
9630 &mut self,
9631 anchor: Anchor,
9632 breakpoint: &Breakpoint,
9633 edit_action: BreakpointPromptEditAction,
9634 window: &mut Window,
9635 cx: &mut Context<Self>,
9636 ) {
9637 let weak_editor = cx.weak_entity();
9638 let bp_prompt = cx.new(|cx| {
9639 BreakpointPromptEditor::new(
9640 weak_editor,
9641 anchor,
9642 breakpoint.clone(),
9643 edit_action,
9644 window,
9645 cx,
9646 )
9647 });
9648
9649 let height = bp_prompt.update(cx, |this, cx| {
9650 this.prompt
9651 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9652 });
9653 let cloned_prompt = bp_prompt.clone();
9654 let blocks = vec![BlockProperties {
9655 style: BlockStyle::Sticky,
9656 placement: BlockPlacement::Above(anchor),
9657 height: Some(height),
9658 render: Arc::new(move |cx| {
9659 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9660 cloned_prompt.clone().into_any_element()
9661 }),
9662 priority: 0,
9663 render_in_minimap: true,
9664 }];
9665
9666 let focus_handle = bp_prompt.focus_handle(cx);
9667 window.focus(&focus_handle);
9668
9669 let block_ids = self.insert_blocks(blocks, None, cx);
9670 bp_prompt.update(cx, |prompt, _| {
9671 prompt.add_block_ids(block_ids);
9672 });
9673 }
9674
9675 pub(crate) fn breakpoint_at_row(
9676 &self,
9677 row: u32,
9678 window: &mut Window,
9679 cx: &mut Context<Self>,
9680 ) -> Option<(Anchor, Breakpoint)> {
9681 let snapshot = self.snapshot(window, cx);
9682 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9683
9684 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9685 }
9686
9687 pub(crate) fn breakpoint_at_anchor(
9688 &self,
9689 breakpoint_position: Anchor,
9690 snapshot: &EditorSnapshot,
9691 cx: &mut Context<Self>,
9692 ) -> Option<(Anchor, Breakpoint)> {
9693 let project = self.project.clone()?;
9694
9695 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9696 snapshot
9697 .buffer_snapshot
9698 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9699 })?;
9700
9701 let enclosing_excerpt = breakpoint_position.excerpt_id;
9702 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9703 let buffer_snapshot = buffer.read(cx).snapshot();
9704
9705 let row = buffer_snapshot
9706 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9707 .row;
9708
9709 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9710 let anchor_end = snapshot
9711 .buffer_snapshot
9712 .anchor_after(Point::new(row, line_len));
9713
9714 let bp = self
9715 .breakpoint_store
9716 .as_ref()?
9717 .read_with(cx, |breakpoint_store, cx| {
9718 breakpoint_store
9719 .breakpoints(
9720 &buffer,
9721 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9722 &buffer_snapshot,
9723 cx,
9724 )
9725 .next()
9726 .and_then(|(bp, _)| {
9727 let breakpoint_row = buffer_snapshot
9728 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9729 .row;
9730
9731 if breakpoint_row == row {
9732 snapshot
9733 .buffer_snapshot
9734 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9735 .map(|position| (position, bp.bp.clone()))
9736 } else {
9737 None
9738 }
9739 })
9740 });
9741 bp
9742 }
9743
9744 pub fn edit_log_breakpoint(
9745 &mut self,
9746 _: &EditLogBreakpoint,
9747 window: &mut Window,
9748 cx: &mut Context<Self>,
9749 ) {
9750 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9751 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9752 message: None,
9753 state: BreakpointState::Enabled,
9754 condition: None,
9755 hit_condition: None,
9756 });
9757
9758 self.add_edit_breakpoint_block(
9759 anchor,
9760 &breakpoint,
9761 BreakpointPromptEditAction::Log,
9762 window,
9763 cx,
9764 );
9765 }
9766 }
9767
9768 fn breakpoints_at_cursors(
9769 &self,
9770 window: &mut Window,
9771 cx: &mut Context<Self>,
9772 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9773 let snapshot = self.snapshot(window, cx);
9774 let cursors = self
9775 .selections
9776 .disjoint_anchors()
9777 .into_iter()
9778 .map(|selection| {
9779 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9780
9781 let breakpoint_position = self
9782 .breakpoint_at_row(cursor_position.row, window, cx)
9783 .map(|bp| bp.0)
9784 .unwrap_or_else(|| {
9785 snapshot
9786 .display_snapshot
9787 .buffer_snapshot
9788 .anchor_after(Point::new(cursor_position.row, 0))
9789 });
9790
9791 let breakpoint = self
9792 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9793 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9794
9795 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9796 })
9797 // 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.
9798 .collect::<HashMap<Anchor, _>>();
9799
9800 cursors.into_iter().collect()
9801 }
9802
9803 pub fn enable_breakpoint(
9804 &mut self,
9805 _: &crate::actions::EnableBreakpoint,
9806 window: &mut Window,
9807 cx: &mut Context<Self>,
9808 ) {
9809 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9810 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9811 continue;
9812 };
9813 self.edit_breakpoint_at_anchor(
9814 anchor,
9815 breakpoint,
9816 BreakpointEditAction::InvertState,
9817 cx,
9818 );
9819 }
9820 }
9821
9822 pub fn disable_breakpoint(
9823 &mut self,
9824 _: &crate::actions::DisableBreakpoint,
9825 window: &mut Window,
9826 cx: &mut Context<Self>,
9827 ) {
9828 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9829 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9830 continue;
9831 };
9832 self.edit_breakpoint_at_anchor(
9833 anchor,
9834 breakpoint,
9835 BreakpointEditAction::InvertState,
9836 cx,
9837 );
9838 }
9839 }
9840
9841 pub fn toggle_breakpoint(
9842 &mut self,
9843 _: &crate::actions::ToggleBreakpoint,
9844 window: &mut Window,
9845 cx: &mut Context<Self>,
9846 ) {
9847 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9848 if let Some(breakpoint) = breakpoint {
9849 self.edit_breakpoint_at_anchor(
9850 anchor,
9851 breakpoint,
9852 BreakpointEditAction::Toggle,
9853 cx,
9854 );
9855 } else {
9856 self.edit_breakpoint_at_anchor(
9857 anchor,
9858 Breakpoint::new_standard(),
9859 BreakpointEditAction::Toggle,
9860 cx,
9861 );
9862 }
9863 }
9864 }
9865
9866 pub fn edit_breakpoint_at_anchor(
9867 &mut self,
9868 breakpoint_position: Anchor,
9869 breakpoint: Breakpoint,
9870 edit_action: BreakpointEditAction,
9871 cx: &mut Context<Self>,
9872 ) {
9873 let Some(breakpoint_store) = &self.breakpoint_store else {
9874 return;
9875 };
9876
9877 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9878 if breakpoint_position == Anchor::min() {
9879 self.buffer()
9880 .read(cx)
9881 .excerpt_buffer_ids()
9882 .into_iter()
9883 .next()
9884 } else {
9885 None
9886 }
9887 }) else {
9888 return;
9889 };
9890
9891 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9892 return;
9893 };
9894
9895 breakpoint_store.update(cx, |breakpoint_store, cx| {
9896 breakpoint_store.toggle_breakpoint(
9897 buffer,
9898 BreakpointWithPosition {
9899 position: breakpoint_position.text_anchor,
9900 bp: breakpoint,
9901 },
9902 edit_action,
9903 cx,
9904 );
9905 });
9906
9907 cx.notify();
9908 }
9909
9910 #[cfg(any(test, feature = "test-support"))]
9911 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9912 self.breakpoint_store.clone()
9913 }
9914
9915 pub fn prepare_restore_change(
9916 &self,
9917 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9918 hunk: &MultiBufferDiffHunk,
9919 cx: &mut App,
9920 ) -> Option<()> {
9921 if hunk.is_created_file() {
9922 return None;
9923 }
9924 let buffer = self.buffer.read(cx);
9925 let diff = buffer.diff_for(hunk.buffer_id)?;
9926 let buffer = buffer.buffer(hunk.buffer_id)?;
9927 let buffer = buffer.read(cx);
9928 let original_text = diff
9929 .read(cx)
9930 .base_text()
9931 .as_rope()
9932 .slice(hunk.diff_base_byte_range.clone());
9933 let buffer_snapshot = buffer.snapshot();
9934 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9935 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9936 probe
9937 .0
9938 .start
9939 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9940 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9941 }) {
9942 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9943 Some(())
9944 } else {
9945 None
9946 }
9947 }
9948
9949 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9950 self.manipulate_lines(window, cx, |lines| lines.reverse())
9951 }
9952
9953 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9954 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9955 }
9956
9957 fn manipulate_lines<Fn>(
9958 &mut self,
9959 window: &mut Window,
9960 cx: &mut Context<Self>,
9961 mut callback: Fn,
9962 ) where
9963 Fn: FnMut(&mut Vec<&str>),
9964 {
9965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9966
9967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9968 let buffer = self.buffer.read(cx).snapshot(cx);
9969
9970 let mut edits = Vec::new();
9971
9972 let selections = self.selections.all::<Point>(cx);
9973 let mut selections = selections.iter().peekable();
9974 let mut contiguous_row_selections = Vec::new();
9975 let mut new_selections = Vec::new();
9976 let mut added_lines = 0;
9977 let mut removed_lines = 0;
9978
9979 while let Some(selection) = selections.next() {
9980 let (start_row, end_row) = consume_contiguous_rows(
9981 &mut contiguous_row_selections,
9982 selection,
9983 &display_map,
9984 &mut selections,
9985 );
9986
9987 let start_point = Point::new(start_row.0, 0);
9988 let end_point = Point::new(
9989 end_row.previous_row().0,
9990 buffer.line_len(end_row.previous_row()),
9991 );
9992 let text = buffer
9993 .text_for_range(start_point..end_point)
9994 .collect::<String>();
9995
9996 let mut lines = text.split('\n').collect_vec();
9997
9998 let lines_before = lines.len();
9999 callback(&mut lines);
10000 let lines_after = lines.len();
10001
10002 edits.push((start_point..end_point, lines.join("\n")));
10003
10004 // Selections must change based on added and removed line count
10005 let start_row =
10006 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10007 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10008 new_selections.push(Selection {
10009 id: selection.id,
10010 start: start_row,
10011 end: end_row,
10012 goal: SelectionGoal::None,
10013 reversed: selection.reversed,
10014 });
10015
10016 if lines_after > lines_before {
10017 added_lines += lines_after - lines_before;
10018 } else if lines_before > lines_after {
10019 removed_lines += lines_before - lines_after;
10020 }
10021 }
10022
10023 self.transact(window, cx, |this, window, cx| {
10024 let buffer = this.buffer.update(cx, |buffer, cx| {
10025 buffer.edit(edits, None, cx);
10026 buffer.snapshot(cx)
10027 });
10028
10029 // Recalculate offsets on newly edited buffer
10030 let new_selections = new_selections
10031 .iter()
10032 .map(|s| {
10033 let start_point = Point::new(s.start.0, 0);
10034 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10035 Selection {
10036 id: s.id,
10037 start: buffer.point_to_offset(start_point),
10038 end: buffer.point_to_offset(end_point),
10039 goal: s.goal,
10040 reversed: s.reversed,
10041 }
10042 })
10043 .collect();
10044
10045 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10046 s.select(new_selections);
10047 });
10048
10049 this.request_autoscroll(Autoscroll::fit(), cx);
10050 });
10051 }
10052
10053 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10054 self.manipulate_text(window, cx, |text| {
10055 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10056 if has_upper_case_characters {
10057 text.to_lowercase()
10058 } else {
10059 text.to_uppercase()
10060 }
10061 })
10062 }
10063
10064 pub fn convert_to_upper_case(
10065 &mut self,
10066 _: &ConvertToUpperCase,
10067 window: &mut Window,
10068 cx: &mut Context<Self>,
10069 ) {
10070 self.manipulate_text(window, cx, |text| text.to_uppercase())
10071 }
10072
10073 pub fn convert_to_lower_case(
10074 &mut self,
10075 _: &ConvertToLowerCase,
10076 window: &mut Window,
10077 cx: &mut Context<Self>,
10078 ) {
10079 self.manipulate_text(window, cx, |text| text.to_lowercase())
10080 }
10081
10082 pub fn convert_to_title_case(
10083 &mut self,
10084 _: &ConvertToTitleCase,
10085 window: &mut Window,
10086 cx: &mut Context<Self>,
10087 ) {
10088 self.manipulate_text(window, cx, |text| {
10089 text.split('\n')
10090 .map(|line| line.to_case(Case::Title))
10091 .join("\n")
10092 })
10093 }
10094
10095 pub fn convert_to_snake_case(
10096 &mut self,
10097 _: &ConvertToSnakeCase,
10098 window: &mut Window,
10099 cx: &mut Context<Self>,
10100 ) {
10101 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10102 }
10103
10104 pub fn convert_to_kebab_case(
10105 &mut self,
10106 _: &ConvertToKebabCase,
10107 window: &mut Window,
10108 cx: &mut Context<Self>,
10109 ) {
10110 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10111 }
10112
10113 pub fn convert_to_upper_camel_case(
10114 &mut self,
10115 _: &ConvertToUpperCamelCase,
10116 window: &mut Window,
10117 cx: &mut Context<Self>,
10118 ) {
10119 self.manipulate_text(window, cx, |text| {
10120 text.split('\n')
10121 .map(|line| line.to_case(Case::UpperCamel))
10122 .join("\n")
10123 })
10124 }
10125
10126 pub fn convert_to_lower_camel_case(
10127 &mut self,
10128 _: &ConvertToLowerCamelCase,
10129 window: &mut Window,
10130 cx: &mut Context<Self>,
10131 ) {
10132 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10133 }
10134
10135 pub fn convert_to_opposite_case(
10136 &mut self,
10137 _: &ConvertToOppositeCase,
10138 window: &mut Window,
10139 cx: &mut Context<Self>,
10140 ) {
10141 self.manipulate_text(window, cx, |text| {
10142 text.chars()
10143 .fold(String::with_capacity(text.len()), |mut t, c| {
10144 if c.is_uppercase() {
10145 t.extend(c.to_lowercase());
10146 } else {
10147 t.extend(c.to_uppercase());
10148 }
10149 t
10150 })
10151 })
10152 }
10153
10154 pub fn convert_to_rot13(
10155 &mut self,
10156 _: &ConvertToRot13,
10157 window: &mut Window,
10158 cx: &mut Context<Self>,
10159 ) {
10160 self.manipulate_text(window, cx, |text| {
10161 text.chars()
10162 .map(|c| match c {
10163 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10164 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10165 _ => c,
10166 })
10167 .collect()
10168 })
10169 }
10170
10171 pub fn convert_to_rot47(
10172 &mut self,
10173 _: &ConvertToRot47,
10174 window: &mut Window,
10175 cx: &mut Context<Self>,
10176 ) {
10177 self.manipulate_text(window, cx, |text| {
10178 text.chars()
10179 .map(|c| {
10180 let code_point = c as u32;
10181 if code_point >= 33 && code_point <= 126 {
10182 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10183 }
10184 c
10185 })
10186 .collect()
10187 })
10188 }
10189
10190 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10191 where
10192 Fn: FnMut(&str) -> String,
10193 {
10194 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10195 let buffer = self.buffer.read(cx).snapshot(cx);
10196
10197 let mut new_selections = Vec::new();
10198 let mut edits = Vec::new();
10199 let mut selection_adjustment = 0i32;
10200
10201 for selection in self.selections.all::<usize>(cx) {
10202 let selection_is_empty = selection.is_empty();
10203
10204 let (start, end) = if selection_is_empty {
10205 let word_range = movement::surrounding_word(
10206 &display_map,
10207 selection.start.to_display_point(&display_map),
10208 );
10209 let start = word_range.start.to_offset(&display_map, Bias::Left);
10210 let end = word_range.end.to_offset(&display_map, Bias::Left);
10211 (start, end)
10212 } else {
10213 (selection.start, selection.end)
10214 };
10215
10216 let text = buffer.text_for_range(start..end).collect::<String>();
10217 let old_length = text.len() as i32;
10218 let text = callback(&text);
10219
10220 new_selections.push(Selection {
10221 start: (start as i32 - selection_adjustment) as usize,
10222 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10223 goal: SelectionGoal::None,
10224 ..selection
10225 });
10226
10227 selection_adjustment += old_length - text.len() as i32;
10228
10229 edits.push((start..end, text));
10230 }
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |buffer, cx| {
10234 buffer.edit(edits, None, cx);
10235 });
10236
10237 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10238 s.select(new_selections);
10239 });
10240
10241 this.request_autoscroll(Autoscroll::fit(), cx);
10242 });
10243 }
10244
10245 pub fn duplicate(
10246 &mut self,
10247 upwards: bool,
10248 whole_lines: bool,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) {
10252 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10253
10254 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10255 let buffer = &display_map.buffer_snapshot;
10256 let selections = self.selections.all::<Point>(cx);
10257
10258 let mut edits = Vec::new();
10259 let mut selections_iter = selections.iter().peekable();
10260 while let Some(selection) = selections_iter.next() {
10261 let mut rows = selection.spanned_rows(false, &display_map);
10262 // duplicate line-wise
10263 if whole_lines || selection.start == selection.end {
10264 // Avoid duplicating the same lines twice.
10265 while let Some(next_selection) = selections_iter.peek() {
10266 let next_rows = next_selection.spanned_rows(false, &display_map);
10267 if next_rows.start < rows.end {
10268 rows.end = next_rows.end;
10269 selections_iter.next().unwrap();
10270 } else {
10271 break;
10272 }
10273 }
10274
10275 // Copy the text from the selected row region and splice it either at the start
10276 // or end of the region.
10277 let start = Point::new(rows.start.0, 0);
10278 let end = Point::new(
10279 rows.end.previous_row().0,
10280 buffer.line_len(rows.end.previous_row()),
10281 );
10282 let text = buffer
10283 .text_for_range(start..end)
10284 .chain(Some("\n"))
10285 .collect::<String>();
10286 let insert_location = if upwards {
10287 Point::new(rows.end.0, 0)
10288 } else {
10289 start
10290 };
10291 edits.push((insert_location..insert_location, text));
10292 } else {
10293 // duplicate character-wise
10294 let start = selection.start;
10295 let end = selection.end;
10296 let text = buffer.text_for_range(start..end).collect::<String>();
10297 edits.push((selection.end..selection.end, text));
10298 }
10299 }
10300
10301 self.transact(window, cx, |this, _, cx| {
10302 this.buffer.update(cx, |buffer, cx| {
10303 buffer.edit(edits, None, cx);
10304 });
10305
10306 this.request_autoscroll(Autoscroll::fit(), cx);
10307 });
10308 }
10309
10310 pub fn duplicate_line_up(
10311 &mut self,
10312 _: &DuplicateLineUp,
10313 window: &mut Window,
10314 cx: &mut Context<Self>,
10315 ) {
10316 self.duplicate(true, true, window, cx);
10317 }
10318
10319 pub fn duplicate_line_down(
10320 &mut self,
10321 _: &DuplicateLineDown,
10322 window: &mut Window,
10323 cx: &mut Context<Self>,
10324 ) {
10325 self.duplicate(false, true, window, cx);
10326 }
10327
10328 pub fn duplicate_selection(
10329 &mut self,
10330 _: &DuplicateSelection,
10331 window: &mut Window,
10332 cx: &mut Context<Self>,
10333 ) {
10334 self.duplicate(false, false, window, cx);
10335 }
10336
10337 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10338 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10339
10340 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10341 let buffer = self.buffer.read(cx).snapshot(cx);
10342
10343 let mut edits = Vec::new();
10344 let mut unfold_ranges = Vec::new();
10345 let mut refold_creases = Vec::new();
10346
10347 let selections = self.selections.all::<Point>(cx);
10348 let mut selections = selections.iter().peekable();
10349 let mut contiguous_row_selections = Vec::new();
10350 let mut new_selections = Vec::new();
10351
10352 while let Some(selection) = selections.next() {
10353 // Find all the selections that span a contiguous row range
10354 let (start_row, end_row) = consume_contiguous_rows(
10355 &mut contiguous_row_selections,
10356 selection,
10357 &display_map,
10358 &mut selections,
10359 );
10360
10361 // Move the text spanned by the row range to be before the line preceding the row range
10362 if start_row.0 > 0 {
10363 let range_to_move = Point::new(
10364 start_row.previous_row().0,
10365 buffer.line_len(start_row.previous_row()),
10366 )
10367 ..Point::new(
10368 end_row.previous_row().0,
10369 buffer.line_len(end_row.previous_row()),
10370 );
10371 let insertion_point = display_map
10372 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10373 .0;
10374
10375 // Don't move lines across excerpts
10376 if buffer
10377 .excerpt_containing(insertion_point..range_to_move.end)
10378 .is_some()
10379 {
10380 let text = buffer
10381 .text_for_range(range_to_move.clone())
10382 .flat_map(|s| s.chars())
10383 .skip(1)
10384 .chain(['\n'])
10385 .collect::<String>();
10386
10387 edits.push((
10388 buffer.anchor_after(range_to_move.start)
10389 ..buffer.anchor_before(range_to_move.end),
10390 String::new(),
10391 ));
10392 let insertion_anchor = buffer.anchor_after(insertion_point);
10393 edits.push((insertion_anchor..insertion_anchor, text));
10394
10395 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10396
10397 // Move selections up
10398 new_selections.extend(contiguous_row_selections.drain(..).map(
10399 |mut selection| {
10400 selection.start.row -= row_delta;
10401 selection.end.row -= row_delta;
10402 selection
10403 },
10404 ));
10405
10406 // Move folds up
10407 unfold_ranges.push(range_to_move.clone());
10408 for fold in display_map.folds_in_range(
10409 buffer.anchor_before(range_to_move.start)
10410 ..buffer.anchor_after(range_to_move.end),
10411 ) {
10412 let mut start = fold.range.start.to_point(&buffer);
10413 let mut end = fold.range.end.to_point(&buffer);
10414 start.row -= row_delta;
10415 end.row -= row_delta;
10416 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10417 }
10418 }
10419 }
10420
10421 // If we didn't move line(s), preserve the existing selections
10422 new_selections.append(&mut contiguous_row_selections);
10423 }
10424
10425 self.transact(window, cx, |this, window, cx| {
10426 this.unfold_ranges(&unfold_ranges, true, true, cx);
10427 this.buffer.update(cx, |buffer, cx| {
10428 for (range, text) in edits {
10429 buffer.edit([(range, text)], None, cx);
10430 }
10431 });
10432 this.fold_creases(refold_creases, true, window, cx);
10433 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10434 s.select(new_selections);
10435 })
10436 });
10437 }
10438
10439 pub fn move_line_down(
10440 &mut self,
10441 _: &MoveLineDown,
10442 window: &mut Window,
10443 cx: &mut Context<Self>,
10444 ) {
10445 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10446
10447 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10448 let buffer = self.buffer.read(cx).snapshot(cx);
10449
10450 let mut edits = Vec::new();
10451 let mut unfold_ranges = Vec::new();
10452 let mut refold_creases = Vec::new();
10453
10454 let selections = self.selections.all::<Point>(cx);
10455 let mut selections = selections.iter().peekable();
10456 let mut contiguous_row_selections = Vec::new();
10457 let mut new_selections = Vec::new();
10458
10459 while let Some(selection) = selections.next() {
10460 // Find all the selections that span a contiguous row range
10461 let (start_row, end_row) = consume_contiguous_rows(
10462 &mut contiguous_row_selections,
10463 selection,
10464 &display_map,
10465 &mut selections,
10466 );
10467
10468 // Move the text spanned by the row range to be after the last line of the row range
10469 if end_row.0 <= buffer.max_point().row {
10470 let range_to_move =
10471 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10472 let insertion_point = display_map
10473 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10474 .0;
10475
10476 // Don't move lines across excerpt boundaries
10477 if buffer
10478 .excerpt_containing(range_to_move.start..insertion_point)
10479 .is_some()
10480 {
10481 let mut text = String::from("\n");
10482 text.extend(buffer.text_for_range(range_to_move.clone()));
10483 text.pop(); // Drop trailing newline
10484 edits.push((
10485 buffer.anchor_after(range_to_move.start)
10486 ..buffer.anchor_before(range_to_move.end),
10487 String::new(),
10488 ));
10489 let insertion_anchor = buffer.anchor_after(insertion_point);
10490 edits.push((insertion_anchor..insertion_anchor, text));
10491
10492 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10493
10494 // Move selections down
10495 new_selections.extend(contiguous_row_selections.drain(..).map(
10496 |mut selection| {
10497 selection.start.row += row_delta;
10498 selection.end.row += row_delta;
10499 selection
10500 },
10501 ));
10502
10503 // Move folds down
10504 unfold_ranges.push(range_to_move.clone());
10505 for fold in display_map.folds_in_range(
10506 buffer.anchor_before(range_to_move.start)
10507 ..buffer.anchor_after(range_to_move.end),
10508 ) {
10509 let mut start = fold.range.start.to_point(&buffer);
10510 let mut end = fold.range.end.to_point(&buffer);
10511 start.row += row_delta;
10512 end.row += row_delta;
10513 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10514 }
10515 }
10516 }
10517
10518 // If we didn't move line(s), preserve the existing selections
10519 new_selections.append(&mut contiguous_row_selections);
10520 }
10521
10522 self.transact(window, cx, |this, window, cx| {
10523 this.unfold_ranges(&unfold_ranges, true, true, cx);
10524 this.buffer.update(cx, |buffer, cx| {
10525 for (range, text) in edits {
10526 buffer.edit([(range, text)], None, cx);
10527 }
10528 });
10529 this.fold_creases(refold_creases, true, window, cx);
10530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10531 s.select(new_selections)
10532 });
10533 });
10534 }
10535
10536 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10537 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10538 let text_layout_details = &self.text_layout_details(window);
10539 self.transact(window, cx, |this, window, cx| {
10540 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10541 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10542 s.move_with(|display_map, selection| {
10543 if !selection.is_empty() {
10544 return;
10545 }
10546
10547 let mut head = selection.head();
10548 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10549 if head.column() == display_map.line_len(head.row()) {
10550 transpose_offset = display_map
10551 .buffer_snapshot
10552 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10553 }
10554
10555 if transpose_offset == 0 {
10556 return;
10557 }
10558
10559 *head.column_mut() += 1;
10560 head = display_map.clip_point(head, Bias::Right);
10561 let goal = SelectionGoal::HorizontalPosition(
10562 display_map
10563 .x_for_display_point(head, text_layout_details)
10564 .into(),
10565 );
10566 selection.collapse_to(head, goal);
10567
10568 let transpose_start = display_map
10569 .buffer_snapshot
10570 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10571 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10572 let transpose_end = display_map
10573 .buffer_snapshot
10574 .clip_offset(transpose_offset + 1, Bias::Right);
10575 if let Some(ch) =
10576 display_map.buffer_snapshot.chars_at(transpose_start).next()
10577 {
10578 edits.push((transpose_start..transpose_offset, String::new()));
10579 edits.push((transpose_end..transpose_end, ch.to_string()));
10580 }
10581 }
10582 });
10583 edits
10584 });
10585 this.buffer
10586 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10587 let selections = this.selections.all::<usize>(cx);
10588 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10589 s.select(selections);
10590 });
10591 });
10592 }
10593
10594 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10595 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10596 self.rewrap_impl(RewrapOptions::default(), cx)
10597 }
10598
10599 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10600 let buffer = self.buffer.read(cx).snapshot(cx);
10601 let selections = self.selections.all::<Point>(cx);
10602 let mut selections = selections.iter().peekable();
10603
10604 let mut edits = Vec::new();
10605 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10606
10607 while let Some(selection) = selections.next() {
10608 let mut start_row = selection.start.row;
10609 let mut end_row = selection.end.row;
10610
10611 // Skip selections that overlap with a range that has already been rewrapped.
10612 let selection_range = start_row..end_row;
10613 if rewrapped_row_ranges
10614 .iter()
10615 .any(|range| range.overlaps(&selection_range))
10616 {
10617 continue;
10618 }
10619
10620 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10621
10622 // Since not all lines in the selection may be at the same indent
10623 // level, choose the indent size that is the most common between all
10624 // of the lines.
10625 //
10626 // If there is a tie, we use the deepest indent.
10627 let (indent_size, indent_end) = {
10628 let mut indent_size_occurrences = HashMap::default();
10629 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10630
10631 for row in start_row..=end_row {
10632 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10633 rows_by_indent_size.entry(indent).or_default().push(row);
10634 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10635 }
10636
10637 let indent_size = indent_size_occurrences
10638 .into_iter()
10639 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10640 .map(|(indent, _)| indent)
10641 .unwrap_or_default();
10642 let row = rows_by_indent_size[&indent_size][0];
10643 let indent_end = Point::new(row, indent_size.len);
10644
10645 (indent_size, indent_end)
10646 };
10647
10648 let mut line_prefix = indent_size.chars().collect::<String>();
10649
10650 let mut inside_comment = false;
10651 if let Some(comment_prefix) =
10652 buffer
10653 .language_scope_at(selection.head())
10654 .and_then(|language| {
10655 language
10656 .line_comment_prefixes()
10657 .iter()
10658 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10659 .cloned()
10660 })
10661 {
10662 line_prefix.push_str(&comment_prefix);
10663 inside_comment = true;
10664 }
10665
10666 let language_settings = buffer.language_settings_at(selection.head(), cx);
10667 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10668 RewrapBehavior::InComments => inside_comment,
10669 RewrapBehavior::InSelections => !selection.is_empty(),
10670 RewrapBehavior::Anywhere => true,
10671 };
10672
10673 let should_rewrap = options.override_language_settings
10674 || allow_rewrap_based_on_language
10675 || self.hard_wrap.is_some();
10676 if !should_rewrap {
10677 continue;
10678 }
10679
10680 if selection.is_empty() {
10681 'expand_upwards: while start_row > 0 {
10682 let prev_row = start_row - 1;
10683 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10684 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10685 {
10686 start_row = prev_row;
10687 } else {
10688 break 'expand_upwards;
10689 }
10690 }
10691
10692 'expand_downwards: while end_row < buffer.max_point().row {
10693 let next_row = end_row + 1;
10694 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10695 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10696 {
10697 end_row = next_row;
10698 } else {
10699 break 'expand_downwards;
10700 }
10701 }
10702 }
10703
10704 let start = Point::new(start_row, 0);
10705 let start_offset = start.to_offset(&buffer);
10706 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10707 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10708 let Some(lines_without_prefixes) = selection_text
10709 .lines()
10710 .map(|line| {
10711 line.strip_prefix(&line_prefix)
10712 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10713 .with_context(|| {
10714 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10715 })
10716 })
10717 .collect::<Result<Vec<_>, _>>()
10718 .log_err()
10719 else {
10720 continue;
10721 };
10722
10723 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10724 buffer
10725 .language_settings_at(Point::new(start_row, 0), cx)
10726 .preferred_line_length as usize
10727 });
10728 let wrapped_text = wrap_with_prefix(
10729 line_prefix,
10730 lines_without_prefixes.join("\n"),
10731 wrap_column,
10732 tab_size,
10733 options.preserve_existing_whitespace,
10734 );
10735
10736 // TODO: should always use char-based diff while still supporting cursor behavior that
10737 // matches vim.
10738 let mut diff_options = DiffOptions::default();
10739 if options.override_language_settings {
10740 diff_options.max_word_diff_len = 0;
10741 diff_options.max_word_diff_line_count = 0;
10742 } else {
10743 diff_options.max_word_diff_len = usize::MAX;
10744 diff_options.max_word_diff_line_count = usize::MAX;
10745 }
10746
10747 for (old_range, new_text) in
10748 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10749 {
10750 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10751 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10752 edits.push((edit_start..edit_end, new_text));
10753 }
10754
10755 rewrapped_row_ranges.push(start_row..=end_row);
10756 }
10757
10758 self.buffer
10759 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10760 }
10761
10762 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10763 let mut text = String::new();
10764 let buffer = self.buffer.read(cx).snapshot(cx);
10765 let mut selections = self.selections.all::<Point>(cx);
10766 let mut clipboard_selections = Vec::with_capacity(selections.len());
10767 {
10768 let max_point = buffer.max_point();
10769 let mut is_first = true;
10770 for selection in &mut selections {
10771 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10772 if is_entire_line {
10773 selection.start = Point::new(selection.start.row, 0);
10774 if !selection.is_empty() && selection.end.column == 0 {
10775 selection.end = cmp::min(max_point, selection.end);
10776 } else {
10777 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10778 }
10779 selection.goal = SelectionGoal::None;
10780 }
10781 if is_first {
10782 is_first = false;
10783 } else {
10784 text += "\n";
10785 }
10786 let mut len = 0;
10787 for chunk in buffer.text_for_range(selection.start..selection.end) {
10788 text.push_str(chunk);
10789 len += chunk.len();
10790 }
10791 clipboard_selections.push(ClipboardSelection {
10792 len,
10793 is_entire_line,
10794 first_line_indent: buffer
10795 .indent_size_for_line(MultiBufferRow(selection.start.row))
10796 .len,
10797 });
10798 }
10799 }
10800
10801 self.transact(window, cx, |this, window, cx| {
10802 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10803 s.select(selections);
10804 });
10805 this.insert("", window, cx);
10806 });
10807 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10808 }
10809
10810 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10811 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10812 let item = self.cut_common(window, cx);
10813 cx.write_to_clipboard(item);
10814 }
10815
10816 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10817 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10818 self.change_selections(None, window, cx, |s| {
10819 s.move_with(|snapshot, sel| {
10820 if sel.is_empty() {
10821 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10822 }
10823 });
10824 });
10825 let item = self.cut_common(window, cx);
10826 cx.set_global(KillRing(item))
10827 }
10828
10829 pub fn kill_ring_yank(
10830 &mut self,
10831 _: &KillRingYank,
10832 window: &mut Window,
10833 cx: &mut Context<Self>,
10834 ) {
10835 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10836 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10837 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10838 (kill_ring.text().to_string(), kill_ring.metadata_json())
10839 } else {
10840 return;
10841 }
10842 } else {
10843 return;
10844 };
10845 self.do_paste(&text, metadata, false, window, cx);
10846 }
10847
10848 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10849 self.do_copy(true, cx);
10850 }
10851
10852 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10853 self.do_copy(false, cx);
10854 }
10855
10856 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10857 let selections = self.selections.all::<Point>(cx);
10858 let buffer = self.buffer.read(cx).read(cx);
10859 let mut text = String::new();
10860
10861 let mut clipboard_selections = Vec::with_capacity(selections.len());
10862 {
10863 let max_point = buffer.max_point();
10864 let mut is_first = true;
10865 for selection in &selections {
10866 let mut start = selection.start;
10867 let mut end = selection.end;
10868 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10869 if is_entire_line {
10870 start = Point::new(start.row, 0);
10871 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10872 }
10873
10874 let mut trimmed_selections = Vec::new();
10875 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10876 let row = MultiBufferRow(start.row);
10877 let first_indent = buffer.indent_size_for_line(row);
10878 if first_indent.len == 0 || start.column > first_indent.len {
10879 trimmed_selections.push(start..end);
10880 } else {
10881 trimmed_selections.push(
10882 Point::new(row.0, first_indent.len)
10883 ..Point::new(row.0, buffer.line_len(row)),
10884 );
10885 for row in start.row + 1..=end.row {
10886 let mut line_len = buffer.line_len(MultiBufferRow(row));
10887 if row == end.row {
10888 line_len = end.column;
10889 }
10890 if line_len == 0 {
10891 trimmed_selections
10892 .push(Point::new(row, 0)..Point::new(row, line_len));
10893 continue;
10894 }
10895 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10896 if row_indent_size.len >= first_indent.len {
10897 trimmed_selections.push(
10898 Point::new(row, first_indent.len)..Point::new(row, line_len),
10899 );
10900 } else {
10901 trimmed_selections.clear();
10902 trimmed_selections.push(start..end);
10903 break;
10904 }
10905 }
10906 }
10907 } else {
10908 trimmed_selections.push(start..end);
10909 }
10910
10911 for trimmed_range in trimmed_selections {
10912 if is_first {
10913 is_first = false;
10914 } else {
10915 text += "\n";
10916 }
10917 let mut len = 0;
10918 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10919 text.push_str(chunk);
10920 len += chunk.len();
10921 }
10922 clipboard_selections.push(ClipboardSelection {
10923 len,
10924 is_entire_line,
10925 first_line_indent: buffer
10926 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10927 .len,
10928 });
10929 }
10930 }
10931 }
10932
10933 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10934 text,
10935 clipboard_selections,
10936 ));
10937 }
10938
10939 pub fn do_paste(
10940 &mut self,
10941 text: &String,
10942 clipboard_selections: Option<Vec<ClipboardSelection>>,
10943 handle_entire_lines: bool,
10944 window: &mut Window,
10945 cx: &mut Context<Self>,
10946 ) {
10947 if self.read_only(cx) {
10948 return;
10949 }
10950
10951 let clipboard_text = Cow::Borrowed(text);
10952
10953 self.transact(window, cx, |this, window, cx| {
10954 if let Some(mut clipboard_selections) = clipboard_selections {
10955 let old_selections = this.selections.all::<usize>(cx);
10956 let all_selections_were_entire_line =
10957 clipboard_selections.iter().all(|s| s.is_entire_line);
10958 let first_selection_indent_column =
10959 clipboard_selections.first().map(|s| s.first_line_indent);
10960 if clipboard_selections.len() != old_selections.len() {
10961 clipboard_selections.drain(..);
10962 }
10963 let cursor_offset = this.selections.last::<usize>(cx).head();
10964 let mut auto_indent_on_paste = true;
10965
10966 this.buffer.update(cx, |buffer, cx| {
10967 let snapshot = buffer.read(cx);
10968 auto_indent_on_paste = snapshot
10969 .language_settings_at(cursor_offset, cx)
10970 .auto_indent_on_paste;
10971
10972 let mut start_offset = 0;
10973 let mut edits = Vec::new();
10974 let mut original_indent_columns = Vec::new();
10975 for (ix, selection) in old_selections.iter().enumerate() {
10976 let to_insert;
10977 let entire_line;
10978 let original_indent_column;
10979 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10980 let end_offset = start_offset + clipboard_selection.len;
10981 to_insert = &clipboard_text[start_offset..end_offset];
10982 entire_line = clipboard_selection.is_entire_line;
10983 start_offset = end_offset + 1;
10984 original_indent_column = Some(clipboard_selection.first_line_indent);
10985 } else {
10986 to_insert = clipboard_text.as_str();
10987 entire_line = all_selections_were_entire_line;
10988 original_indent_column = first_selection_indent_column
10989 }
10990
10991 // If the corresponding selection was empty when this slice of the
10992 // clipboard text was written, then the entire line containing the
10993 // selection was copied. If this selection is also currently empty,
10994 // then paste the line before the current line of the buffer.
10995 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10996 let column = selection.start.to_point(&snapshot).column as usize;
10997 let line_start = selection.start - column;
10998 line_start..line_start
10999 } else {
11000 selection.range()
11001 };
11002
11003 edits.push((range, to_insert));
11004 original_indent_columns.push(original_indent_column);
11005 }
11006 drop(snapshot);
11007
11008 buffer.edit(
11009 edits,
11010 if auto_indent_on_paste {
11011 Some(AutoindentMode::Block {
11012 original_indent_columns,
11013 })
11014 } else {
11015 None
11016 },
11017 cx,
11018 );
11019 });
11020
11021 let selections = this.selections.all::<usize>(cx);
11022 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11023 s.select(selections)
11024 });
11025 } else {
11026 this.insert(&clipboard_text, window, cx);
11027 }
11028 });
11029 }
11030
11031 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11032 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11033 if let Some(item) = cx.read_from_clipboard() {
11034 let entries = item.entries();
11035
11036 match entries.first() {
11037 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11038 // of all the pasted entries.
11039 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11040 .do_paste(
11041 clipboard_string.text(),
11042 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11043 true,
11044 window,
11045 cx,
11046 ),
11047 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11048 }
11049 }
11050 }
11051
11052 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11053 if self.read_only(cx) {
11054 return;
11055 }
11056
11057 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11058
11059 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11060 if let Some((selections, _)) =
11061 self.selection_history.transaction(transaction_id).cloned()
11062 {
11063 self.change_selections(None, window, cx, |s| {
11064 s.select_anchors(selections.to_vec());
11065 });
11066 } else {
11067 log::error!(
11068 "No entry in selection_history found for undo. \
11069 This may correspond to a bug where undo does not update the selection. \
11070 If this is occurring, please add details to \
11071 https://github.com/zed-industries/zed/issues/22692"
11072 );
11073 }
11074 self.request_autoscroll(Autoscroll::fit(), cx);
11075 self.unmark_text(window, cx);
11076 self.refresh_inline_completion(true, false, window, cx);
11077 cx.emit(EditorEvent::Edited { transaction_id });
11078 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11079 }
11080 }
11081
11082 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11083 if self.read_only(cx) {
11084 return;
11085 }
11086
11087 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11088
11089 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11090 if let Some((_, Some(selections))) =
11091 self.selection_history.transaction(transaction_id).cloned()
11092 {
11093 self.change_selections(None, window, cx, |s| {
11094 s.select_anchors(selections.to_vec());
11095 });
11096 } else {
11097 log::error!(
11098 "No entry in selection_history found for redo. \
11099 This may correspond to a bug where undo does not update the selection. \
11100 If this is occurring, please add details to \
11101 https://github.com/zed-industries/zed/issues/22692"
11102 );
11103 }
11104 self.request_autoscroll(Autoscroll::fit(), cx);
11105 self.unmark_text(window, cx);
11106 self.refresh_inline_completion(true, false, window, cx);
11107 cx.emit(EditorEvent::Edited { transaction_id });
11108 }
11109 }
11110
11111 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11112 self.buffer
11113 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11114 }
11115
11116 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11117 self.buffer
11118 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11119 }
11120
11121 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11122 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11123 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11124 s.move_with(|map, selection| {
11125 let cursor = if selection.is_empty() {
11126 movement::left(map, selection.start)
11127 } else {
11128 selection.start
11129 };
11130 selection.collapse_to(cursor, SelectionGoal::None);
11131 });
11132 })
11133 }
11134
11135 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11138 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11139 })
11140 }
11141
11142 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11143 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11144 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11145 s.move_with(|map, selection| {
11146 let cursor = if selection.is_empty() {
11147 movement::right(map, selection.end)
11148 } else {
11149 selection.end
11150 };
11151 selection.collapse_to(cursor, SelectionGoal::None)
11152 });
11153 })
11154 }
11155
11156 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11157 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11159 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11160 })
11161 }
11162
11163 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11164 if self.take_rename(true, window, cx).is_some() {
11165 return;
11166 }
11167
11168 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11169 cx.propagate();
11170 return;
11171 }
11172
11173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11174
11175 let text_layout_details = &self.text_layout_details(window);
11176 let selection_count = self.selections.count();
11177 let first_selection = self.selections.first_anchor();
11178
11179 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11180 s.move_with(|map, selection| {
11181 if !selection.is_empty() {
11182 selection.goal = SelectionGoal::None;
11183 }
11184 let (cursor, goal) = movement::up(
11185 map,
11186 selection.start,
11187 selection.goal,
11188 false,
11189 text_layout_details,
11190 );
11191 selection.collapse_to(cursor, goal);
11192 });
11193 });
11194
11195 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11196 {
11197 cx.propagate();
11198 }
11199 }
11200
11201 pub fn move_up_by_lines(
11202 &mut self,
11203 action: &MoveUpByLines,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 if self.take_rename(true, window, cx).is_some() {
11208 return;
11209 }
11210
11211 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11212 cx.propagate();
11213 return;
11214 }
11215
11216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11217
11218 let text_layout_details = &self.text_layout_details(window);
11219
11220 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11221 s.move_with(|map, selection| {
11222 if !selection.is_empty() {
11223 selection.goal = SelectionGoal::None;
11224 }
11225 let (cursor, goal) = movement::up_by_rows(
11226 map,
11227 selection.start,
11228 action.lines,
11229 selection.goal,
11230 false,
11231 text_layout_details,
11232 );
11233 selection.collapse_to(cursor, goal);
11234 });
11235 })
11236 }
11237
11238 pub fn move_down_by_lines(
11239 &mut self,
11240 action: &MoveDownByLines,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) {
11244 if self.take_rename(true, window, cx).is_some() {
11245 return;
11246 }
11247
11248 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11249 cx.propagate();
11250 return;
11251 }
11252
11253 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11254
11255 let text_layout_details = &self.text_layout_details(window);
11256
11257 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11258 s.move_with(|map, selection| {
11259 if !selection.is_empty() {
11260 selection.goal = SelectionGoal::None;
11261 }
11262 let (cursor, goal) = movement::down_by_rows(
11263 map,
11264 selection.start,
11265 action.lines,
11266 selection.goal,
11267 false,
11268 text_layout_details,
11269 );
11270 selection.collapse_to(cursor, goal);
11271 });
11272 })
11273 }
11274
11275 pub fn select_down_by_lines(
11276 &mut self,
11277 action: &SelectDownByLines,
11278 window: &mut Window,
11279 cx: &mut Context<Self>,
11280 ) {
11281 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11282 let text_layout_details = &self.text_layout_details(window);
11283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11284 s.move_heads_with(|map, head, goal| {
11285 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11286 })
11287 })
11288 }
11289
11290 pub fn select_up_by_lines(
11291 &mut self,
11292 action: &SelectUpByLines,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) {
11296 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11297 let text_layout_details = &self.text_layout_details(window);
11298 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11299 s.move_heads_with(|map, head, goal| {
11300 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11301 })
11302 })
11303 }
11304
11305 pub fn select_page_up(
11306 &mut self,
11307 _: &SelectPageUp,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let Some(row_count) = self.visible_row_count() else {
11312 return;
11313 };
11314
11315 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11316
11317 let text_layout_details = &self.text_layout_details(window);
11318
11319 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11320 s.move_heads_with(|map, head, goal| {
11321 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11322 })
11323 })
11324 }
11325
11326 pub fn move_page_up(
11327 &mut self,
11328 action: &MovePageUp,
11329 window: &mut Window,
11330 cx: &mut Context<Self>,
11331 ) {
11332 if self.take_rename(true, window, cx).is_some() {
11333 return;
11334 }
11335
11336 if self
11337 .context_menu
11338 .borrow_mut()
11339 .as_mut()
11340 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11341 .unwrap_or(false)
11342 {
11343 return;
11344 }
11345
11346 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11347 cx.propagate();
11348 return;
11349 }
11350
11351 let Some(row_count) = self.visible_row_count() else {
11352 return;
11353 };
11354
11355 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11356
11357 let autoscroll = if action.center_cursor {
11358 Autoscroll::center()
11359 } else {
11360 Autoscroll::fit()
11361 };
11362
11363 let text_layout_details = &self.text_layout_details(window);
11364
11365 self.change_selections(Some(autoscroll), window, cx, |s| {
11366 s.move_with(|map, selection| {
11367 if !selection.is_empty() {
11368 selection.goal = SelectionGoal::None;
11369 }
11370 let (cursor, goal) = movement::up_by_rows(
11371 map,
11372 selection.end,
11373 row_count,
11374 selection.goal,
11375 false,
11376 text_layout_details,
11377 );
11378 selection.collapse_to(cursor, goal);
11379 });
11380 });
11381 }
11382
11383 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11385 let text_layout_details = &self.text_layout_details(window);
11386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11387 s.move_heads_with(|map, head, goal| {
11388 movement::up(map, head, goal, false, text_layout_details)
11389 })
11390 })
11391 }
11392
11393 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11394 self.take_rename(true, window, cx);
11395
11396 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11397 cx.propagate();
11398 return;
11399 }
11400
11401 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11402
11403 let text_layout_details = &self.text_layout_details(window);
11404 let selection_count = self.selections.count();
11405 let first_selection = self.selections.first_anchor();
11406
11407 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11408 s.move_with(|map, selection| {
11409 if !selection.is_empty() {
11410 selection.goal = SelectionGoal::None;
11411 }
11412 let (cursor, goal) = movement::down(
11413 map,
11414 selection.end,
11415 selection.goal,
11416 false,
11417 text_layout_details,
11418 );
11419 selection.collapse_to(cursor, goal);
11420 });
11421 });
11422
11423 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11424 {
11425 cx.propagate();
11426 }
11427 }
11428
11429 pub fn select_page_down(
11430 &mut self,
11431 _: &SelectPageDown,
11432 window: &mut Window,
11433 cx: &mut Context<Self>,
11434 ) {
11435 let Some(row_count) = self.visible_row_count() else {
11436 return;
11437 };
11438
11439 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11440
11441 let text_layout_details = &self.text_layout_details(window);
11442
11443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11444 s.move_heads_with(|map, head, goal| {
11445 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11446 })
11447 })
11448 }
11449
11450 pub fn move_page_down(
11451 &mut self,
11452 action: &MovePageDown,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 if self.take_rename(true, window, cx).is_some() {
11457 return;
11458 }
11459
11460 if self
11461 .context_menu
11462 .borrow_mut()
11463 .as_mut()
11464 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11465 .unwrap_or(false)
11466 {
11467 return;
11468 }
11469
11470 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11471 cx.propagate();
11472 return;
11473 }
11474
11475 let Some(row_count) = self.visible_row_count() else {
11476 return;
11477 };
11478
11479 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11480
11481 let autoscroll = if action.center_cursor {
11482 Autoscroll::center()
11483 } else {
11484 Autoscroll::fit()
11485 };
11486
11487 let text_layout_details = &self.text_layout_details(window);
11488 self.change_selections(Some(autoscroll), window, cx, |s| {
11489 s.move_with(|map, selection| {
11490 if !selection.is_empty() {
11491 selection.goal = SelectionGoal::None;
11492 }
11493 let (cursor, goal) = movement::down_by_rows(
11494 map,
11495 selection.end,
11496 row_count,
11497 selection.goal,
11498 false,
11499 text_layout_details,
11500 );
11501 selection.collapse_to(cursor, goal);
11502 });
11503 });
11504 }
11505
11506 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11507 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11508 let text_layout_details = &self.text_layout_details(window);
11509 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11510 s.move_heads_with(|map, head, goal| {
11511 movement::down(map, head, goal, false, text_layout_details)
11512 })
11513 });
11514 }
11515
11516 pub fn context_menu_first(
11517 &mut self,
11518 _: &ContextMenuFirst,
11519 _window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11523 context_menu.select_first(self.completion_provider.as_deref(), cx);
11524 }
11525 }
11526
11527 pub fn context_menu_prev(
11528 &mut self,
11529 _: &ContextMenuPrevious,
11530 _window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11534 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11535 }
11536 }
11537
11538 pub fn context_menu_next(
11539 &mut self,
11540 _: &ContextMenuNext,
11541 _window: &mut Window,
11542 cx: &mut Context<Self>,
11543 ) {
11544 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11545 context_menu.select_next(self.completion_provider.as_deref(), cx);
11546 }
11547 }
11548
11549 pub fn context_menu_last(
11550 &mut self,
11551 _: &ContextMenuLast,
11552 _window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11556 context_menu.select_last(self.completion_provider.as_deref(), cx);
11557 }
11558 }
11559
11560 pub fn move_to_previous_word_start(
11561 &mut self,
11562 _: &MoveToPreviousWordStart,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11567 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11568 s.move_cursors_with(|map, head, _| {
11569 (
11570 movement::previous_word_start(map, head),
11571 SelectionGoal::None,
11572 )
11573 });
11574 })
11575 }
11576
11577 pub fn move_to_previous_subword_start(
11578 &mut self,
11579 _: &MoveToPreviousSubwordStart,
11580 window: &mut Window,
11581 cx: &mut Context<Self>,
11582 ) {
11583 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11584 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11585 s.move_cursors_with(|map, head, _| {
11586 (
11587 movement::previous_subword_start(map, head),
11588 SelectionGoal::None,
11589 )
11590 });
11591 })
11592 }
11593
11594 pub fn select_to_previous_word_start(
11595 &mut self,
11596 _: &SelectToPreviousWordStart,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11601 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11602 s.move_heads_with(|map, head, _| {
11603 (
11604 movement::previous_word_start(map, head),
11605 SelectionGoal::None,
11606 )
11607 });
11608 })
11609 }
11610
11611 pub fn select_to_previous_subword_start(
11612 &mut self,
11613 _: &SelectToPreviousSubwordStart,
11614 window: &mut Window,
11615 cx: &mut Context<Self>,
11616 ) {
11617 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11618 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11619 s.move_heads_with(|map, head, _| {
11620 (
11621 movement::previous_subword_start(map, head),
11622 SelectionGoal::None,
11623 )
11624 });
11625 })
11626 }
11627
11628 pub fn delete_to_previous_word_start(
11629 &mut self,
11630 action: &DeleteToPreviousWordStart,
11631 window: &mut Window,
11632 cx: &mut Context<Self>,
11633 ) {
11634 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11635 self.transact(window, cx, |this, window, cx| {
11636 this.select_autoclose_pair(window, cx);
11637 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11638 s.move_with(|map, selection| {
11639 if selection.is_empty() {
11640 let cursor = if action.ignore_newlines {
11641 movement::previous_word_start(map, selection.head())
11642 } else {
11643 movement::previous_word_start_or_newline(map, selection.head())
11644 };
11645 selection.set_head(cursor, SelectionGoal::None);
11646 }
11647 });
11648 });
11649 this.insert("", window, cx);
11650 });
11651 }
11652
11653 pub fn delete_to_previous_subword_start(
11654 &mut self,
11655 _: &DeleteToPreviousSubwordStart,
11656 window: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11660 self.transact(window, cx, |this, window, cx| {
11661 this.select_autoclose_pair(window, cx);
11662 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11663 s.move_with(|map, selection| {
11664 if selection.is_empty() {
11665 let cursor = movement::previous_subword_start(map, selection.head());
11666 selection.set_head(cursor, SelectionGoal::None);
11667 }
11668 });
11669 });
11670 this.insert("", window, cx);
11671 });
11672 }
11673
11674 pub fn move_to_next_word_end(
11675 &mut self,
11676 _: &MoveToNextWordEnd,
11677 window: &mut Window,
11678 cx: &mut Context<Self>,
11679 ) {
11680 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11682 s.move_cursors_with(|map, head, _| {
11683 (movement::next_word_end(map, head), SelectionGoal::None)
11684 });
11685 })
11686 }
11687
11688 pub fn move_to_next_subword_end(
11689 &mut self,
11690 _: &MoveToNextSubwordEnd,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 ) {
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11695 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11696 s.move_cursors_with(|map, head, _| {
11697 (movement::next_subword_end(map, head), SelectionGoal::None)
11698 });
11699 })
11700 }
11701
11702 pub fn select_to_next_word_end(
11703 &mut self,
11704 _: &SelectToNextWordEnd,
11705 window: &mut Window,
11706 cx: &mut Context<Self>,
11707 ) {
11708 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11710 s.move_heads_with(|map, head, _| {
11711 (movement::next_word_end(map, head), SelectionGoal::None)
11712 });
11713 })
11714 }
11715
11716 pub fn select_to_next_subword_end(
11717 &mut self,
11718 _: &SelectToNextSubwordEnd,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11724 s.move_heads_with(|map, head, _| {
11725 (movement::next_subword_end(map, head), SelectionGoal::None)
11726 });
11727 })
11728 }
11729
11730 pub fn delete_to_next_word_end(
11731 &mut self,
11732 action: &DeleteToNextWordEnd,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11737 self.transact(window, cx, |this, window, cx| {
11738 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11739 s.move_with(|map, selection| {
11740 if selection.is_empty() {
11741 let cursor = if action.ignore_newlines {
11742 movement::next_word_end(map, selection.head())
11743 } else {
11744 movement::next_word_end_or_newline(map, selection.head())
11745 };
11746 selection.set_head(cursor, SelectionGoal::None);
11747 }
11748 });
11749 });
11750 this.insert("", window, cx);
11751 });
11752 }
11753
11754 pub fn delete_to_next_subword_end(
11755 &mut self,
11756 _: &DeleteToNextSubwordEnd,
11757 window: &mut Window,
11758 cx: &mut Context<Self>,
11759 ) {
11760 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11761 self.transact(window, cx, |this, window, cx| {
11762 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11763 s.move_with(|map, selection| {
11764 if selection.is_empty() {
11765 let cursor = movement::next_subword_end(map, selection.head());
11766 selection.set_head(cursor, SelectionGoal::None);
11767 }
11768 });
11769 });
11770 this.insert("", window, cx);
11771 });
11772 }
11773
11774 pub fn move_to_beginning_of_line(
11775 &mut self,
11776 action: &MoveToBeginningOfLine,
11777 window: &mut Window,
11778 cx: &mut Context<Self>,
11779 ) {
11780 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11781 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11782 s.move_cursors_with(|map, head, _| {
11783 (
11784 movement::indented_line_beginning(
11785 map,
11786 head,
11787 action.stop_at_soft_wraps,
11788 action.stop_at_indent,
11789 ),
11790 SelectionGoal::None,
11791 )
11792 });
11793 })
11794 }
11795
11796 pub fn select_to_beginning_of_line(
11797 &mut self,
11798 action: &SelectToBeginningOfLine,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11803 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11804 s.move_heads_with(|map, head, _| {
11805 (
11806 movement::indented_line_beginning(
11807 map,
11808 head,
11809 action.stop_at_soft_wraps,
11810 action.stop_at_indent,
11811 ),
11812 SelectionGoal::None,
11813 )
11814 });
11815 });
11816 }
11817
11818 pub fn delete_to_beginning_of_line(
11819 &mut self,
11820 action: &DeleteToBeginningOfLine,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11825 self.transact(window, cx, |this, window, cx| {
11826 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11827 s.move_with(|_, selection| {
11828 selection.reversed = true;
11829 });
11830 });
11831
11832 this.select_to_beginning_of_line(
11833 &SelectToBeginningOfLine {
11834 stop_at_soft_wraps: false,
11835 stop_at_indent: action.stop_at_indent,
11836 },
11837 window,
11838 cx,
11839 );
11840 this.backspace(&Backspace, window, cx);
11841 });
11842 }
11843
11844 pub fn move_to_end_of_line(
11845 &mut self,
11846 action: &MoveToEndOfLine,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11851 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11852 s.move_cursors_with(|map, head, _| {
11853 (
11854 movement::line_end(map, head, action.stop_at_soft_wraps),
11855 SelectionGoal::None,
11856 )
11857 });
11858 })
11859 }
11860
11861 pub fn select_to_end_of_line(
11862 &mut self,
11863 action: &SelectToEndOfLine,
11864 window: &mut Window,
11865 cx: &mut Context<Self>,
11866 ) {
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11869 s.move_heads_with(|map, head, _| {
11870 (
11871 movement::line_end(map, head, action.stop_at_soft_wraps),
11872 SelectionGoal::None,
11873 )
11874 });
11875 })
11876 }
11877
11878 pub fn delete_to_end_of_line(
11879 &mut self,
11880 _: &DeleteToEndOfLine,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11885 self.transact(window, cx, |this, window, cx| {
11886 this.select_to_end_of_line(
11887 &SelectToEndOfLine {
11888 stop_at_soft_wraps: false,
11889 },
11890 window,
11891 cx,
11892 );
11893 this.delete(&Delete, window, cx);
11894 });
11895 }
11896
11897 pub fn cut_to_end_of_line(
11898 &mut self,
11899 _: &CutToEndOfLine,
11900 window: &mut Window,
11901 cx: &mut Context<Self>,
11902 ) {
11903 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11904 self.transact(window, cx, |this, window, cx| {
11905 this.select_to_end_of_line(
11906 &SelectToEndOfLine {
11907 stop_at_soft_wraps: false,
11908 },
11909 window,
11910 cx,
11911 );
11912 this.cut(&Cut, window, cx);
11913 });
11914 }
11915
11916 pub fn move_to_start_of_paragraph(
11917 &mut self,
11918 _: &MoveToStartOfParagraph,
11919 window: &mut Window,
11920 cx: &mut Context<Self>,
11921 ) {
11922 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11923 cx.propagate();
11924 return;
11925 }
11926 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11927 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11928 s.move_with(|map, selection| {
11929 selection.collapse_to(
11930 movement::start_of_paragraph(map, selection.head(), 1),
11931 SelectionGoal::None,
11932 )
11933 });
11934 })
11935 }
11936
11937 pub fn move_to_end_of_paragraph(
11938 &mut self,
11939 _: &MoveToEndOfParagraph,
11940 window: &mut Window,
11941 cx: &mut Context<Self>,
11942 ) {
11943 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11944 cx.propagate();
11945 return;
11946 }
11947 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11949 s.move_with(|map, selection| {
11950 selection.collapse_to(
11951 movement::end_of_paragraph(map, selection.head(), 1),
11952 SelectionGoal::None,
11953 )
11954 });
11955 })
11956 }
11957
11958 pub fn select_to_start_of_paragraph(
11959 &mut self,
11960 _: &SelectToStartOfParagraph,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11965 cx.propagate();
11966 return;
11967 }
11968 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11969 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11970 s.move_heads_with(|map, head, _| {
11971 (
11972 movement::start_of_paragraph(map, head, 1),
11973 SelectionGoal::None,
11974 )
11975 });
11976 })
11977 }
11978
11979 pub fn select_to_end_of_paragraph(
11980 &mut self,
11981 _: &SelectToEndOfParagraph,
11982 window: &mut Window,
11983 cx: &mut Context<Self>,
11984 ) {
11985 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11986 cx.propagate();
11987 return;
11988 }
11989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11990 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11991 s.move_heads_with(|map, head, _| {
11992 (
11993 movement::end_of_paragraph(map, head, 1),
11994 SelectionGoal::None,
11995 )
11996 });
11997 })
11998 }
11999
12000 pub fn move_to_start_of_excerpt(
12001 &mut self,
12002 _: &MoveToStartOfExcerpt,
12003 window: &mut Window,
12004 cx: &mut Context<Self>,
12005 ) {
12006 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12007 cx.propagate();
12008 return;
12009 }
12010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12011 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12012 s.move_with(|map, selection| {
12013 selection.collapse_to(
12014 movement::start_of_excerpt(
12015 map,
12016 selection.head(),
12017 workspace::searchable::Direction::Prev,
12018 ),
12019 SelectionGoal::None,
12020 )
12021 });
12022 })
12023 }
12024
12025 pub fn move_to_start_of_next_excerpt(
12026 &mut self,
12027 _: &MoveToStartOfNextExcerpt,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12032 cx.propagate();
12033 return;
12034 }
12035
12036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12037 s.move_with(|map, selection| {
12038 selection.collapse_to(
12039 movement::start_of_excerpt(
12040 map,
12041 selection.head(),
12042 workspace::searchable::Direction::Next,
12043 ),
12044 SelectionGoal::None,
12045 )
12046 });
12047 })
12048 }
12049
12050 pub fn move_to_end_of_excerpt(
12051 &mut self,
12052 _: &MoveToEndOfExcerpt,
12053 window: &mut Window,
12054 cx: &mut Context<Self>,
12055 ) {
12056 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12057 cx.propagate();
12058 return;
12059 }
12060 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12061 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12062 s.move_with(|map, selection| {
12063 selection.collapse_to(
12064 movement::end_of_excerpt(
12065 map,
12066 selection.head(),
12067 workspace::searchable::Direction::Next,
12068 ),
12069 SelectionGoal::None,
12070 )
12071 });
12072 })
12073 }
12074
12075 pub fn move_to_end_of_previous_excerpt(
12076 &mut self,
12077 _: &MoveToEndOfPreviousExcerpt,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12082 cx.propagate();
12083 return;
12084 }
12085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12086 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12087 s.move_with(|map, selection| {
12088 selection.collapse_to(
12089 movement::end_of_excerpt(
12090 map,
12091 selection.head(),
12092 workspace::searchable::Direction::Prev,
12093 ),
12094 SelectionGoal::None,
12095 )
12096 });
12097 })
12098 }
12099
12100 pub fn select_to_start_of_excerpt(
12101 &mut self,
12102 _: &SelectToStartOfExcerpt,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12107 cx.propagate();
12108 return;
12109 }
12110 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.move_heads_with(|map, head, _| {
12113 (
12114 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12115 SelectionGoal::None,
12116 )
12117 });
12118 })
12119 }
12120
12121 pub fn select_to_start_of_next_excerpt(
12122 &mut self,
12123 _: &SelectToStartOfNextExcerpt,
12124 window: &mut Window,
12125 cx: &mut Context<Self>,
12126 ) {
12127 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12128 cx.propagate();
12129 return;
12130 }
12131 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12132 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12133 s.move_heads_with(|map, head, _| {
12134 (
12135 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12136 SelectionGoal::None,
12137 )
12138 });
12139 })
12140 }
12141
12142 pub fn select_to_end_of_excerpt(
12143 &mut self,
12144 _: &SelectToEndOfExcerpt,
12145 window: &mut Window,
12146 cx: &mut Context<Self>,
12147 ) {
12148 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12149 cx.propagate();
12150 return;
12151 }
12152 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12153 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12154 s.move_heads_with(|map, head, _| {
12155 (
12156 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12157 SelectionGoal::None,
12158 )
12159 });
12160 })
12161 }
12162
12163 pub fn select_to_end_of_previous_excerpt(
12164 &mut self,
12165 _: &SelectToEndOfPreviousExcerpt,
12166 window: &mut Window,
12167 cx: &mut Context<Self>,
12168 ) {
12169 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12170 cx.propagate();
12171 return;
12172 }
12173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12174 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12175 s.move_heads_with(|map, head, _| {
12176 (
12177 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12178 SelectionGoal::None,
12179 )
12180 });
12181 })
12182 }
12183
12184 pub fn move_to_beginning(
12185 &mut self,
12186 _: &MoveToBeginning,
12187 window: &mut Window,
12188 cx: &mut Context<Self>,
12189 ) {
12190 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12191 cx.propagate();
12192 return;
12193 }
12194 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12195 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12196 s.select_ranges(vec![0..0]);
12197 });
12198 }
12199
12200 pub fn select_to_beginning(
12201 &mut self,
12202 _: &SelectToBeginning,
12203 window: &mut Window,
12204 cx: &mut Context<Self>,
12205 ) {
12206 let mut selection = self.selections.last::<Point>(cx);
12207 selection.set_head(Point::zero(), SelectionGoal::None);
12208 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12210 s.select(vec![selection]);
12211 });
12212 }
12213
12214 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12216 cx.propagate();
12217 return;
12218 }
12219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12220 let cursor = self.buffer.read(cx).read(cx).len();
12221 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12222 s.select_ranges(vec![cursor..cursor])
12223 });
12224 }
12225
12226 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12227 self.nav_history = nav_history;
12228 }
12229
12230 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12231 self.nav_history.as_ref()
12232 }
12233
12234 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12235 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12236 }
12237
12238 fn push_to_nav_history(
12239 &mut self,
12240 cursor_anchor: Anchor,
12241 new_position: Option<Point>,
12242 is_deactivate: bool,
12243 cx: &mut Context<Self>,
12244 ) {
12245 if let Some(nav_history) = self.nav_history.as_mut() {
12246 let buffer = self.buffer.read(cx).read(cx);
12247 let cursor_position = cursor_anchor.to_point(&buffer);
12248 let scroll_state = self.scroll_manager.anchor();
12249 let scroll_top_row = scroll_state.top_row(&buffer);
12250 drop(buffer);
12251
12252 if let Some(new_position) = new_position {
12253 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12254 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12255 return;
12256 }
12257 }
12258
12259 nav_history.push(
12260 Some(NavigationData {
12261 cursor_anchor,
12262 cursor_position,
12263 scroll_anchor: scroll_state,
12264 scroll_top_row,
12265 }),
12266 cx,
12267 );
12268 cx.emit(EditorEvent::PushedToNavHistory {
12269 anchor: cursor_anchor,
12270 is_deactivate,
12271 })
12272 }
12273 }
12274
12275 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12276 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12277 let buffer = self.buffer.read(cx).snapshot(cx);
12278 let mut selection = self.selections.first::<usize>(cx);
12279 selection.set_head(buffer.len(), SelectionGoal::None);
12280 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12281 s.select(vec![selection]);
12282 });
12283 }
12284
12285 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12286 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12287 let end = self.buffer.read(cx).read(cx).len();
12288 self.change_selections(None, window, cx, |s| {
12289 s.select_ranges(vec![0..end]);
12290 });
12291 }
12292
12293 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12294 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12296 let mut selections = self.selections.all::<Point>(cx);
12297 let max_point = display_map.buffer_snapshot.max_point();
12298 for selection in &mut selections {
12299 let rows = selection.spanned_rows(true, &display_map);
12300 selection.start = Point::new(rows.start.0, 0);
12301 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12302 selection.reversed = false;
12303 }
12304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.select(selections);
12306 });
12307 }
12308
12309 pub fn split_selection_into_lines(
12310 &mut self,
12311 _: &SplitSelectionIntoLines,
12312 window: &mut Window,
12313 cx: &mut Context<Self>,
12314 ) {
12315 let selections = self
12316 .selections
12317 .all::<Point>(cx)
12318 .into_iter()
12319 .map(|selection| selection.start..selection.end)
12320 .collect::<Vec<_>>();
12321 self.unfold_ranges(&selections, true, true, cx);
12322
12323 let mut new_selection_ranges = Vec::new();
12324 {
12325 let buffer = self.buffer.read(cx).read(cx);
12326 for selection in selections {
12327 for row in selection.start.row..selection.end.row {
12328 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12329 new_selection_ranges.push(cursor..cursor);
12330 }
12331
12332 let is_multiline_selection = selection.start.row != selection.end.row;
12333 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12334 // so this action feels more ergonomic when paired with other selection operations
12335 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12336 if !should_skip_last {
12337 new_selection_ranges.push(selection.end..selection.end);
12338 }
12339 }
12340 }
12341 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12342 s.select_ranges(new_selection_ranges);
12343 });
12344 }
12345
12346 pub fn add_selection_above(
12347 &mut self,
12348 _: &AddSelectionAbove,
12349 window: &mut Window,
12350 cx: &mut Context<Self>,
12351 ) {
12352 self.add_selection(true, window, cx);
12353 }
12354
12355 pub fn add_selection_below(
12356 &mut self,
12357 _: &AddSelectionBelow,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 self.add_selection(false, window, cx);
12362 }
12363
12364 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12366
12367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12368 let mut selections = self.selections.all::<Point>(cx);
12369 let text_layout_details = self.text_layout_details(window);
12370 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12371 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12372 let range = oldest_selection.display_range(&display_map).sorted();
12373
12374 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12375 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12376 let positions = start_x.min(end_x)..start_x.max(end_x);
12377
12378 selections.clear();
12379 let mut stack = Vec::new();
12380 for row in range.start.row().0..=range.end.row().0 {
12381 if let Some(selection) = self.selections.build_columnar_selection(
12382 &display_map,
12383 DisplayRow(row),
12384 &positions,
12385 oldest_selection.reversed,
12386 &text_layout_details,
12387 ) {
12388 stack.push(selection.id);
12389 selections.push(selection);
12390 }
12391 }
12392
12393 if above {
12394 stack.reverse();
12395 }
12396
12397 AddSelectionsState { above, stack }
12398 });
12399
12400 let last_added_selection = *state.stack.last().unwrap();
12401 let mut new_selections = Vec::new();
12402 if above == state.above {
12403 let end_row = if above {
12404 DisplayRow(0)
12405 } else {
12406 display_map.max_point().row()
12407 };
12408
12409 'outer: for selection in selections {
12410 if selection.id == last_added_selection {
12411 let range = selection.display_range(&display_map).sorted();
12412 debug_assert_eq!(range.start.row(), range.end.row());
12413 let mut row = range.start.row();
12414 let positions =
12415 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12416 px(start)..px(end)
12417 } else {
12418 let start_x =
12419 display_map.x_for_display_point(range.start, &text_layout_details);
12420 let end_x =
12421 display_map.x_for_display_point(range.end, &text_layout_details);
12422 start_x.min(end_x)..start_x.max(end_x)
12423 };
12424
12425 while row != end_row {
12426 if above {
12427 row.0 -= 1;
12428 } else {
12429 row.0 += 1;
12430 }
12431
12432 if let Some(new_selection) = self.selections.build_columnar_selection(
12433 &display_map,
12434 row,
12435 &positions,
12436 selection.reversed,
12437 &text_layout_details,
12438 ) {
12439 state.stack.push(new_selection.id);
12440 if above {
12441 new_selections.push(new_selection);
12442 new_selections.push(selection);
12443 } else {
12444 new_selections.push(selection);
12445 new_selections.push(new_selection);
12446 }
12447
12448 continue 'outer;
12449 }
12450 }
12451 }
12452
12453 new_selections.push(selection);
12454 }
12455 } else {
12456 new_selections = selections;
12457 new_selections.retain(|s| s.id != last_added_selection);
12458 state.stack.pop();
12459 }
12460
12461 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12462 s.select(new_selections);
12463 });
12464 if state.stack.len() > 1 {
12465 self.add_selections_state = Some(state);
12466 }
12467 }
12468
12469 fn select_match_ranges(
12470 &mut self,
12471 range: Range<usize>,
12472 reversed: bool,
12473 replace_newest: bool,
12474 auto_scroll: Option<Autoscroll>,
12475 window: &mut Window,
12476 cx: &mut Context<Editor>,
12477 ) {
12478 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12479 self.change_selections(auto_scroll, window, cx, |s| {
12480 if replace_newest {
12481 s.delete(s.newest_anchor().id);
12482 }
12483 if reversed {
12484 s.insert_range(range.end..range.start);
12485 } else {
12486 s.insert_range(range);
12487 }
12488 });
12489 }
12490
12491 pub fn select_next_match_internal(
12492 &mut self,
12493 display_map: &DisplaySnapshot,
12494 replace_newest: bool,
12495 autoscroll: Option<Autoscroll>,
12496 window: &mut Window,
12497 cx: &mut Context<Self>,
12498 ) -> Result<()> {
12499 let buffer = &display_map.buffer_snapshot;
12500 let mut selections = self.selections.all::<usize>(cx);
12501 if let Some(mut select_next_state) = self.select_next_state.take() {
12502 let query = &select_next_state.query;
12503 if !select_next_state.done {
12504 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12505 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12506 let mut next_selected_range = None;
12507
12508 let bytes_after_last_selection =
12509 buffer.bytes_in_range(last_selection.end..buffer.len());
12510 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12511 let query_matches = query
12512 .stream_find_iter(bytes_after_last_selection)
12513 .map(|result| (last_selection.end, result))
12514 .chain(
12515 query
12516 .stream_find_iter(bytes_before_first_selection)
12517 .map(|result| (0, result)),
12518 );
12519
12520 for (start_offset, query_match) in query_matches {
12521 let query_match = query_match.unwrap(); // can only fail due to I/O
12522 let offset_range =
12523 start_offset + query_match.start()..start_offset + query_match.end();
12524 let display_range = offset_range.start.to_display_point(display_map)
12525 ..offset_range.end.to_display_point(display_map);
12526
12527 if !select_next_state.wordwise
12528 || (!movement::is_inside_word(display_map, display_range.start)
12529 && !movement::is_inside_word(display_map, display_range.end))
12530 {
12531 // TODO: This is n^2, because we might check all the selections
12532 if !selections
12533 .iter()
12534 .any(|selection| selection.range().overlaps(&offset_range))
12535 {
12536 next_selected_range = Some(offset_range);
12537 break;
12538 }
12539 }
12540 }
12541
12542 if let Some(next_selected_range) = next_selected_range {
12543 self.select_match_ranges(
12544 next_selected_range,
12545 last_selection.reversed,
12546 replace_newest,
12547 autoscroll,
12548 window,
12549 cx,
12550 );
12551 } else {
12552 select_next_state.done = true;
12553 }
12554 }
12555
12556 self.select_next_state = Some(select_next_state);
12557 } else {
12558 let mut only_carets = true;
12559 let mut same_text_selected = true;
12560 let mut selected_text = None;
12561
12562 let mut selections_iter = selections.iter().peekable();
12563 while let Some(selection) = selections_iter.next() {
12564 if selection.start != selection.end {
12565 only_carets = false;
12566 }
12567
12568 if same_text_selected {
12569 if selected_text.is_none() {
12570 selected_text =
12571 Some(buffer.text_for_range(selection.range()).collect::<String>());
12572 }
12573
12574 if let Some(next_selection) = selections_iter.peek() {
12575 if next_selection.range().len() == selection.range().len() {
12576 let next_selected_text = buffer
12577 .text_for_range(next_selection.range())
12578 .collect::<String>();
12579 if Some(next_selected_text) != selected_text {
12580 same_text_selected = false;
12581 selected_text = None;
12582 }
12583 } else {
12584 same_text_selected = false;
12585 selected_text = None;
12586 }
12587 }
12588 }
12589 }
12590
12591 if only_carets {
12592 for selection in &mut selections {
12593 let word_range = movement::surrounding_word(
12594 display_map,
12595 selection.start.to_display_point(display_map),
12596 );
12597 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12598 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12599 selection.goal = SelectionGoal::None;
12600 selection.reversed = false;
12601 self.select_match_ranges(
12602 selection.start..selection.end,
12603 selection.reversed,
12604 replace_newest,
12605 autoscroll,
12606 window,
12607 cx,
12608 );
12609 }
12610
12611 if selections.len() == 1 {
12612 let selection = selections
12613 .last()
12614 .expect("ensured that there's only one selection");
12615 let query = buffer
12616 .text_for_range(selection.start..selection.end)
12617 .collect::<String>();
12618 let is_empty = query.is_empty();
12619 let select_state = SelectNextState {
12620 query: AhoCorasick::new(&[query])?,
12621 wordwise: true,
12622 done: is_empty,
12623 };
12624 self.select_next_state = Some(select_state);
12625 } else {
12626 self.select_next_state = None;
12627 }
12628 } else if let Some(selected_text) = selected_text {
12629 self.select_next_state = Some(SelectNextState {
12630 query: AhoCorasick::new(&[selected_text])?,
12631 wordwise: false,
12632 done: false,
12633 });
12634 self.select_next_match_internal(
12635 display_map,
12636 replace_newest,
12637 autoscroll,
12638 window,
12639 cx,
12640 )?;
12641 }
12642 }
12643 Ok(())
12644 }
12645
12646 pub fn select_all_matches(
12647 &mut self,
12648 _action: &SelectAllMatches,
12649 window: &mut Window,
12650 cx: &mut Context<Self>,
12651 ) -> Result<()> {
12652 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12653
12654 self.push_to_selection_history();
12655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12656
12657 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12658 let Some(select_next_state) = self.select_next_state.as_mut() else {
12659 return Ok(());
12660 };
12661 if select_next_state.done {
12662 return Ok(());
12663 }
12664
12665 let mut new_selections = Vec::new();
12666
12667 let reversed = self.selections.oldest::<usize>(cx).reversed;
12668 let buffer = &display_map.buffer_snapshot;
12669 let query_matches = select_next_state
12670 .query
12671 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12672
12673 for query_match in query_matches.into_iter() {
12674 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12675 let offset_range = if reversed {
12676 query_match.end()..query_match.start()
12677 } else {
12678 query_match.start()..query_match.end()
12679 };
12680 let display_range = offset_range.start.to_display_point(&display_map)
12681 ..offset_range.end.to_display_point(&display_map);
12682
12683 if !select_next_state.wordwise
12684 || (!movement::is_inside_word(&display_map, display_range.start)
12685 && !movement::is_inside_word(&display_map, display_range.end))
12686 {
12687 new_selections.push(offset_range.start..offset_range.end);
12688 }
12689 }
12690
12691 select_next_state.done = true;
12692 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12693 self.change_selections(None, window, cx, |selections| {
12694 selections.select_ranges(new_selections)
12695 });
12696
12697 Ok(())
12698 }
12699
12700 pub fn select_next(
12701 &mut self,
12702 action: &SelectNext,
12703 window: &mut Window,
12704 cx: &mut Context<Self>,
12705 ) -> Result<()> {
12706 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12707 self.push_to_selection_history();
12708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12709 self.select_next_match_internal(
12710 &display_map,
12711 action.replace_newest,
12712 Some(Autoscroll::newest()),
12713 window,
12714 cx,
12715 )?;
12716 Ok(())
12717 }
12718
12719 pub fn select_previous(
12720 &mut self,
12721 action: &SelectPrevious,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) -> Result<()> {
12725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12726 self.push_to_selection_history();
12727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12728 let buffer = &display_map.buffer_snapshot;
12729 let mut selections = self.selections.all::<usize>(cx);
12730 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12731 let query = &select_prev_state.query;
12732 if !select_prev_state.done {
12733 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12734 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12735 let mut next_selected_range = None;
12736 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12737 let bytes_before_last_selection =
12738 buffer.reversed_bytes_in_range(0..last_selection.start);
12739 let bytes_after_first_selection =
12740 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12741 let query_matches = query
12742 .stream_find_iter(bytes_before_last_selection)
12743 .map(|result| (last_selection.start, result))
12744 .chain(
12745 query
12746 .stream_find_iter(bytes_after_first_selection)
12747 .map(|result| (buffer.len(), result)),
12748 );
12749 for (end_offset, query_match) in query_matches {
12750 let query_match = query_match.unwrap(); // can only fail due to I/O
12751 let offset_range =
12752 end_offset - query_match.end()..end_offset - query_match.start();
12753 let display_range = offset_range.start.to_display_point(&display_map)
12754 ..offset_range.end.to_display_point(&display_map);
12755
12756 if !select_prev_state.wordwise
12757 || (!movement::is_inside_word(&display_map, display_range.start)
12758 && !movement::is_inside_word(&display_map, display_range.end))
12759 {
12760 next_selected_range = Some(offset_range);
12761 break;
12762 }
12763 }
12764
12765 if let Some(next_selected_range) = next_selected_range {
12766 self.select_match_ranges(
12767 next_selected_range,
12768 last_selection.reversed,
12769 action.replace_newest,
12770 Some(Autoscroll::newest()),
12771 window,
12772 cx,
12773 );
12774 } else {
12775 select_prev_state.done = true;
12776 }
12777 }
12778
12779 self.select_prev_state = Some(select_prev_state);
12780 } else {
12781 let mut only_carets = true;
12782 let mut same_text_selected = true;
12783 let mut selected_text = None;
12784
12785 let mut selections_iter = selections.iter().peekable();
12786 while let Some(selection) = selections_iter.next() {
12787 if selection.start != selection.end {
12788 only_carets = false;
12789 }
12790
12791 if same_text_selected {
12792 if selected_text.is_none() {
12793 selected_text =
12794 Some(buffer.text_for_range(selection.range()).collect::<String>());
12795 }
12796
12797 if let Some(next_selection) = selections_iter.peek() {
12798 if next_selection.range().len() == selection.range().len() {
12799 let next_selected_text = buffer
12800 .text_for_range(next_selection.range())
12801 .collect::<String>();
12802 if Some(next_selected_text) != selected_text {
12803 same_text_selected = false;
12804 selected_text = None;
12805 }
12806 } else {
12807 same_text_selected = false;
12808 selected_text = None;
12809 }
12810 }
12811 }
12812 }
12813
12814 if only_carets {
12815 for selection in &mut selections {
12816 let word_range = movement::surrounding_word(
12817 &display_map,
12818 selection.start.to_display_point(&display_map),
12819 );
12820 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12821 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12822 selection.goal = SelectionGoal::None;
12823 selection.reversed = false;
12824 self.select_match_ranges(
12825 selection.start..selection.end,
12826 selection.reversed,
12827 action.replace_newest,
12828 Some(Autoscroll::newest()),
12829 window,
12830 cx,
12831 );
12832 }
12833 if selections.len() == 1 {
12834 let selection = selections
12835 .last()
12836 .expect("ensured that there's only one selection");
12837 let query = buffer
12838 .text_for_range(selection.start..selection.end)
12839 .collect::<String>();
12840 let is_empty = query.is_empty();
12841 let select_state = SelectNextState {
12842 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12843 wordwise: true,
12844 done: is_empty,
12845 };
12846 self.select_prev_state = Some(select_state);
12847 } else {
12848 self.select_prev_state = None;
12849 }
12850 } else if let Some(selected_text) = selected_text {
12851 self.select_prev_state = Some(SelectNextState {
12852 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12853 wordwise: false,
12854 done: false,
12855 });
12856 self.select_previous(action, window, cx)?;
12857 }
12858 }
12859 Ok(())
12860 }
12861
12862 pub fn find_next_match(
12863 &mut self,
12864 _: &FindNextMatch,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) -> Result<()> {
12868 let selections = self.selections.disjoint_anchors();
12869 match selections.first() {
12870 Some(first) if selections.len() >= 2 => {
12871 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12872 s.select_ranges([first.range()]);
12873 });
12874 }
12875 _ => self.select_next(
12876 &SelectNext {
12877 replace_newest: true,
12878 },
12879 window,
12880 cx,
12881 )?,
12882 }
12883 Ok(())
12884 }
12885
12886 pub fn find_previous_match(
12887 &mut self,
12888 _: &FindPreviousMatch,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) -> Result<()> {
12892 let selections = self.selections.disjoint_anchors();
12893 match selections.last() {
12894 Some(last) if selections.len() >= 2 => {
12895 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12896 s.select_ranges([last.range()]);
12897 });
12898 }
12899 _ => self.select_previous(
12900 &SelectPrevious {
12901 replace_newest: true,
12902 },
12903 window,
12904 cx,
12905 )?,
12906 }
12907 Ok(())
12908 }
12909
12910 pub fn toggle_comments(
12911 &mut self,
12912 action: &ToggleComments,
12913 window: &mut Window,
12914 cx: &mut Context<Self>,
12915 ) {
12916 if self.read_only(cx) {
12917 return;
12918 }
12919 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12920 let text_layout_details = &self.text_layout_details(window);
12921 self.transact(window, cx, |this, window, cx| {
12922 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12923 let mut edits = Vec::new();
12924 let mut selection_edit_ranges = Vec::new();
12925 let mut last_toggled_row = None;
12926 let snapshot = this.buffer.read(cx).read(cx);
12927 let empty_str: Arc<str> = Arc::default();
12928 let mut suffixes_inserted = Vec::new();
12929 let ignore_indent = action.ignore_indent;
12930
12931 fn comment_prefix_range(
12932 snapshot: &MultiBufferSnapshot,
12933 row: MultiBufferRow,
12934 comment_prefix: &str,
12935 comment_prefix_whitespace: &str,
12936 ignore_indent: bool,
12937 ) -> Range<Point> {
12938 let indent_size = if ignore_indent {
12939 0
12940 } else {
12941 snapshot.indent_size_for_line(row).len
12942 };
12943
12944 let start = Point::new(row.0, indent_size);
12945
12946 let mut line_bytes = snapshot
12947 .bytes_in_range(start..snapshot.max_point())
12948 .flatten()
12949 .copied();
12950
12951 // If this line currently begins with the line comment prefix, then record
12952 // the range containing the prefix.
12953 if line_bytes
12954 .by_ref()
12955 .take(comment_prefix.len())
12956 .eq(comment_prefix.bytes())
12957 {
12958 // Include any whitespace that matches the comment prefix.
12959 let matching_whitespace_len = line_bytes
12960 .zip(comment_prefix_whitespace.bytes())
12961 .take_while(|(a, b)| a == b)
12962 .count() as u32;
12963 let end = Point::new(
12964 start.row,
12965 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12966 );
12967 start..end
12968 } else {
12969 start..start
12970 }
12971 }
12972
12973 fn comment_suffix_range(
12974 snapshot: &MultiBufferSnapshot,
12975 row: MultiBufferRow,
12976 comment_suffix: &str,
12977 comment_suffix_has_leading_space: bool,
12978 ) -> Range<Point> {
12979 let end = Point::new(row.0, snapshot.line_len(row));
12980 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12981
12982 let mut line_end_bytes = snapshot
12983 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12984 .flatten()
12985 .copied();
12986
12987 let leading_space_len = if suffix_start_column > 0
12988 && line_end_bytes.next() == Some(b' ')
12989 && comment_suffix_has_leading_space
12990 {
12991 1
12992 } else {
12993 0
12994 };
12995
12996 // If this line currently begins with the line comment prefix, then record
12997 // the range containing the prefix.
12998 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12999 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13000 start..end
13001 } else {
13002 end..end
13003 }
13004 }
13005
13006 // TODO: Handle selections that cross excerpts
13007 for selection in &mut selections {
13008 let start_column = snapshot
13009 .indent_size_for_line(MultiBufferRow(selection.start.row))
13010 .len;
13011 let language = if let Some(language) =
13012 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13013 {
13014 language
13015 } else {
13016 continue;
13017 };
13018
13019 selection_edit_ranges.clear();
13020
13021 // If multiple selections contain a given row, avoid processing that
13022 // row more than once.
13023 let mut start_row = MultiBufferRow(selection.start.row);
13024 if last_toggled_row == Some(start_row) {
13025 start_row = start_row.next_row();
13026 }
13027 let end_row =
13028 if selection.end.row > selection.start.row && selection.end.column == 0 {
13029 MultiBufferRow(selection.end.row - 1)
13030 } else {
13031 MultiBufferRow(selection.end.row)
13032 };
13033 last_toggled_row = Some(end_row);
13034
13035 if start_row > end_row {
13036 continue;
13037 }
13038
13039 // If the language has line comments, toggle those.
13040 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13041
13042 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13043 if ignore_indent {
13044 full_comment_prefixes = full_comment_prefixes
13045 .into_iter()
13046 .map(|s| Arc::from(s.trim_end()))
13047 .collect();
13048 }
13049
13050 if !full_comment_prefixes.is_empty() {
13051 let first_prefix = full_comment_prefixes
13052 .first()
13053 .expect("prefixes is non-empty");
13054 let prefix_trimmed_lengths = full_comment_prefixes
13055 .iter()
13056 .map(|p| p.trim_end_matches(' ').len())
13057 .collect::<SmallVec<[usize; 4]>>();
13058
13059 let mut all_selection_lines_are_comments = true;
13060
13061 for row in start_row.0..=end_row.0 {
13062 let row = MultiBufferRow(row);
13063 if start_row < end_row && snapshot.is_line_blank(row) {
13064 continue;
13065 }
13066
13067 let prefix_range = full_comment_prefixes
13068 .iter()
13069 .zip(prefix_trimmed_lengths.iter().copied())
13070 .map(|(prefix, trimmed_prefix_len)| {
13071 comment_prefix_range(
13072 snapshot.deref(),
13073 row,
13074 &prefix[..trimmed_prefix_len],
13075 &prefix[trimmed_prefix_len..],
13076 ignore_indent,
13077 )
13078 })
13079 .max_by_key(|range| range.end.column - range.start.column)
13080 .expect("prefixes is non-empty");
13081
13082 if prefix_range.is_empty() {
13083 all_selection_lines_are_comments = false;
13084 }
13085
13086 selection_edit_ranges.push(prefix_range);
13087 }
13088
13089 if all_selection_lines_are_comments {
13090 edits.extend(
13091 selection_edit_ranges
13092 .iter()
13093 .cloned()
13094 .map(|range| (range, empty_str.clone())),
13095 );
13096 } else {
13097 let min_column = selection_edit_ranges
13098 .iter()
13099 .map(|range| range.start.column)
13100 .min()
13101 .unwrap_or(0);
13102 edits.extend(selection_edit_ranges.iter().map(|range| {
13103 let position = Point::new(range.start.row, min_column);
13104 (position..position, first_prefix.clone())
13105 }));
13106 }
13107 } else if let Some((full_comment_prefix, comment_suffix)) =
13108 language.block_comment_delimiters()
13109 {
13110 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13111 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13112 let prefix_range = comment_prefix_range(
13113 snapshot.deref(),
13114 start_row,
13115 comment_prefix,
13116 comment_prefix_whitespace,
13117 ignore_indent,
13118 );
13119 let suffix_range = comment_suffix_range(
13120 snapshot.deref(),
13121 end_row,
13122 comment_suffix.trim_start_matches(' '),
13123 comment_suffix.starts_with(' '),
13124 );
13125
13126 if prefix_range.is_empty() || suffix_range.is_empty() {
13127 edits.push((
13128 prefix_range.start..prefix_range.start,
13129 full_comment_prefix.clone(),
13130 ));
13131 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13132 suffixes_inserted.push((end_row, comment_suffix.len()));
13133 } else {
13134 edits.push((prefix_range, empty_str.clone()));
13135 edits.push((suffix_range, empty_str.clone()));
13136 }
13137 } else {
13138 continue;
13139 }
13140 }
13141
13142 drop(snapshot);
13143 this.buffer.update(cx, |buffer, cx| {
13144 buffer.edit(edits, None, cx);
13145 });
13146
13147 // Adjust selections so that they end before any comment suffixes that
13148 // were inserted.
13149 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13150 let mut selections = this.selections.all::<Point>(cx);
13151 let snapshot = this.buffer.read(cx).read(cx);
13152 for selection in &mut selections {
13153 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13154 match row.cmp(&MultiBufferRow(selection.end.row)) {
13155 Ordering::Less => {
13156 suffixes_inserted.next();
13157 continue;
13158 }
13159 Ordering::Greater => break,
13160 Ordering::Equal => {
13161 if selection.end.column == snapshot.line_len(row) {
13162 if selection.is_empty() {
13163 selection.start.column -= suffix_len as u32;
13164 }
13165 selection.end.column -= suffix_len as u32;
13166 }
13167 break;
13168 }
13169 }
13170 }
13171 }
13172
13173 drop(snapshot);
13174 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13175 s.select(selections)
13176 });
13177
13178 let selections = this.selections.all::<Point>(cx);
13179 let selections_on_single_row = selections.windows(2).all(|selections| {
13180 selections[0].start.row == selections[1].start.row
13181 && selections[0].end.row == selections[1].end.row
13182 && selections[0].start.row == selections[0].end.row
13183 });
13184 let selections_selecting = selections
13185 .iter()
13186 .any(|selection| selection.start != selection.end);
13187 let advance_downwards = action.advance_downwards
13188 && selections_on_single_row
13189 && !selections_selecting
13190 && !matches!(this.mode, EditorMode::SingleLine { .. });
13191
13192 if advance_downwards {
13193 let snapshot = this.buffer.read(cx).snapshot(cx);
13194
13195 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13196 s.move_cursors_with(|display_snapshot, display_point, _| {
13197 let mut point = display_point.to_point(display_snapshot);
13198 point.row += 1;
13199 point = snapshot.clip_point(point, Bias::Left);
13200 let display_point = point.to_display_point(display_snapshot);
13201 let goal = SelectionGoal::HorizontalPosition(
13202 display_snapshot
13203 .x_for_display_point(display_point, text_layout_details)
13204 .into(),
13205 );
13206 (display_point, goal)
13207 })
13208 });
13209 }
13210 });
13211 }
13212
13213 pub fn select_enclosing_symbol(
13214 &mut self,
13215 _: &SelectEnclosingSymbol,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13220
13221 let buffer = self.buffer.read(cx).snapshot(cx);
13222 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13223
13224 fn update_selection(
13225 selection: &Selection<usize>,
13226 buffer_snap: &MultiBufferSnapshot,
13227 ) -> Option<Selection<usize>> {
13228 let cursor = selection.head();
13229 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13230 for symbol in symbols.iter().rev() {
13231 let start = symbol.range.start.to_offset(buffer_snap);
13232 let end = symbol.range.end.to_offset(buffer_snap);
13233 let new_range = start..end;
13234 if start < selection.start || end > selection.end {
13235 return Some(Selection {
13236 id: selection.id,
13237 start: new_range.start,
13238 end: new_range.end,
13239 goal: SelectionGoal::None,
13240 reversed: selection.reversed,
13241 });
13242 }
13243 }
13244 None
13245 }
13246
13247 let mut selected_larger_symbol = false;
13248 let new_selections = old_selections
13249 .iter()
13250 .map(|selection| match update_selection(selection, &buffer) {
13251 Some(new_selection) => {
13252 if new_selection.range() != selection.range() {
13253 selected_larger_symbol = true;
13254 }
13255 new_selection
13256 }
13257 None => selection.clone(),
13258 })
13259 .collect::<Vec<_>>();
13260
13261 if selected_larger_symbol {
13262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13263 s.select(new_selections);
13264 });
13265 }
13266 }
13267
13268 pub fn select_larger_syntax_node(
13269 &mut self,
13270 _: &SelectLargerSyntaxNode,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) {
13274 let Some(visible_row_count) = self.visible_row_count() else {
13275 return;
13276 };
13277 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13278 if old_selections.is_empty() {
13279 return;
13280 }
13281
13282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13283
13284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13285 let buffer = self.buffer.read(cx).snapshot(cx);
13286
13287 let mut selected_larger_node = false;
13288 let mut new_selections = old_selections
13289 .iter()
13290 .map(|selection| {
13291 let old_range = selection.start..selection.end;
13292
13293 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13294 // manually select word at selection
13295 if ["string_content", "inline"].contains(&node.kind()) {
13296 let word_range = {
13297 let display_point = buffer
13298 .offset_to_point(old_range.start)
13299 .to_display_point(&display_map);
13300 let Range { start, end } =
13301 movement::surrounding_word(&display_map, display_point);
13302 start.to_point(&display_map).to_offset(&buffer)
13303 ..end.to_point(&display_map).to_offset(&buffer)
13304 };
13305 // ignore if word is already selected
13306 if !word_range.is_empty() && old_range != word_range {
13307 let last_word_range = {
13308 let display_point = buffer
13309 .offset_to_point(old_range.end)
13310 .to_display_point(&display_map);
13311 let Range { start, end } =
13312 movement::surrounding_word(&display_map, display_point);
13313 start.to_point(&display_map).to_offset(&buffer)
13314 ..end.to_point(&display_map).to_offset(&buffer)
13315 };
13316 // only select word if start and end point belongs to same word
13317 if word_range == last_word_range {
13318 selected_larger_node = true;
13319 return Selection {
13320 id: selection.id,
13321 start: word_range.start,
13322 end: word_range.end,
13323 goal: SelectionGoal::None,
13324 reversed: selection.reversed,
13325 };
13326 }
13327 }
13328 }
13329 }
13330
13331 let mut new_range = old_range.clone();
13332 while let Some((_node, containing_range)) =
13333 buffer.syntax_ancestor(new_range.clone())
13334 {
13335 new_range = match containing_range {
13336 MultiOrSingleBufferOffsetRange::Single(_) => break,
13337 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13338 };
13339 if !display_map.intersects_fold(new_range.start)
13340 && !display_map.intersects_fold(new_range.end)
13341 {
13342 break;
13343 }
13344 }
13345
13346 selected_larger_node |= new_range != old_range;
13347 Selection {
13348 id: selection.id,
13349 start: new_range.start,
13350 end: new_range.end,
13351 goal: SelectionGoal::None,
13352 reversed: selection.reversed,
13353 }
13354 })
13355 .collect::<Vec<_>>();
13356
13357 if !selected_larger_node {
13358 return; // don't put this call in the history
13359 }
13360
13361 // scroll based on transformation done to the last selection created by the user
13362 let (last_old, last_new) = old_selections
13363 .last()
13364 .zip(new_selections.last().cloned())
13365 .expect("old_selections isn't empty");
13366
13367 // revert selection
13368 let is_selection_reversed = {
13369 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13370 new_selections.last_mut().expect("checked above").reversed =
13371 should_newest_selection_be_reversed;
13372 should_newest_selection_be_reversed
13373 };
13374
13375 if selected_larger_node {
13376 self.select_syntax_node_history.disable_clearing = true;
13377 self.change_selections(None, window, cx, |s| {
13378 s.select(new_selections.clone());
13379 });
13380 self.select_syntax_node_history.disable_clearing = false;
13381 }
13382
13383 let start_row = last_new.start.to_display_point(&display_map).row().0;
13384 let end_row = last_new.end.to_display_point(&display_map).row().0;
13385 let selection_height = end_row - start_row + 1;
13386 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13387
13388 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13389 let scroll_behavior = if fits_on_the_screen {
13390 self.request_autoscroll(Autoscroll::fit(), cx);
13391 SelectSyntaxNodeScrollBehavior::FitSelection
13392 } else if is_selection_reversed {
13393 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13394 SelectSyntaxNodeScrollBehavior::CursorTop
13395 } else {
13396 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13397 SelectSyntaxNodeScrollBehavior::CursorBottom
13398 };
13399
13400 self.select_syntax_node_history.push((
13401 old_selections,
13402 scroll_behavior,
13403 is_selection_reversed,
13404 ));
13405 }
13406
13407 pub fn select_smaller_syntax_node(
13408 &mut self,
13409 _: &SelectSmallerSyntaxNode,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13414
13415 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13416 self.select_syntax_node_history.pop()
13417 {
13418 if let Some(selection) = selections.last_mut() {
13419 selection.reversed = is_selection_reversed;
13420 }
13421
13422 self.select_syntax_node_history.disable_clearing = true;
13423 self.change_selections(None, window, cx, |s| {
13424 s.select(selections.to_vec());
13425 });
13426 self.select_syntax_node_history.disable_clearing = false;
13427
13428 match scroll_behavior {
13429 SelectSyntaxNodeScrollBehavior::CursorTop => {
13430 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13431 }
13432 SelectSyntaxNodeScrollBehavior::FitSelection => {
13433 self.request_autoscroll(Autoscroll::fit(), cx);
13434 }
13435 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13436 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13437 }
13438 }
13439 }
13440 }
13441
13442 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13443 if !EditorSettings::get_global(cx).gutter.runnables {
13444 self.clear_tasks();
13445 return Task::ready(());
13446 }
13447 let project = self.project.as_ref().map(Entity::downgrade);
13448 let task_sources = self.lsp_task_sources(cx);
13449 cx.spawn_in(window, async move |editor, cx| {
13450 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13451 let Some(project) = project.and_then(|p| p.upgrade()) else {
13452 return;
13453 };
13454 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13455 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13456 }) else {
13457 return;
13458 };
13459
13460 let hide_runnables = project
13461 .update(cx, |project, cx| {
13462 // Do not display any test indicators in non-dev server remote projects.
13463 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13464 })
13465 .unwrap_or(true);
13466 if hide_runnables {
13467 return;
13468 }
13469 let new_rows =
13470 cx.background_spawn({
13471 let snapshot = display_snapshot.clone();
13472 async move {
13473 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13474 }
13475 })
13476 .await;
13477 let Ok(lsp_tasks) =
13478 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13479 else {
13480 return;
13481 };
13482 let lsp_tasks = lsp_tasks.await;
13483
13484 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13485 lsp_tasks
13486 .into_iter()
13487 .flat_map(|(kind, tasks)| {
13488 tasks.into_iter().filter_map(move |(location, task)| {
13489 Some((kind.clone(), location?, task))
13490 })
13491 })
13492 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13493 let buffer = location.target.buffer;
13494 let buffer_snapshot = buffer.read(cx).snapshot();
13495 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13496 |(excerpt_id, snapshot, _)| {
13497 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13498 display_snapshot
13499 .buffer_snapshot
13500 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13501 } else {
13502 None
13503 }
13504 },
13505 );
13506 if let Some(offset) = offset {
13507 let task_buffer_range =
13508 location.target.range.to_point(&buffer_snapshot);
13509 let context_buffer_range =
13510 task_buffer_range.to_offset(&buffer_snapshot);
13511 let context_range = BufferOffset(context_buffer_range.start)
13512 ..BufferOffset(context_buffer_range.end);
13513
13514 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13515 .or_insert_with(|| RunnableTasks {
13516 templates: Vec::new(),
13517 offset,
13518 column: task_buffer_range.start.column,
13519 extra_variables: HashMap::default(),
13520 context_range,
13521 })
13522 .templates
13523 .push((kind, task.original_task().clone()));
13524 }
13525
13526 acc
13527 })
13528 }) else {
13529 return;
13530 };
13531
13532 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13533 editor
13534 .update(cx, |editor, _| {
13535 editor.clear_tasks();
13536 for (key, mut value) in rows {
13537 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13538 value.templates.extend(lsp_tasks.templates);
13539 }
13540
13541 editor.insert_tasks(key, value);
13542 }
13543 for (key, value) in lsp_tasks_by_rows {
13544 editor.insert_tasks(key, value);
13545 }
13546 })
13547 .ok();
13548 })
13549 }
13550 fn fetch_runnable_ranges(
13551 snapshot: &DisplaySnapshot,
13552 range: Range<Anchor>,
13553 ) -> Vec<language::RunnableRange> {
13554 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13555 }
13556
13557 fn runnable_rows(
13558 project: Entity<Project>,
13559 snapshot: DisplaySnapshot,
13560 runnable_ranges: Vec<RunnableRange>,
13561 mut cx: AsyncWindowContext,
13562 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13563 runnable_ranges
13564 .into_iter()
13565 .filter_map(|mut runnable| {
13566 let tasks = cx
13567 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13568 .ok()?;
13569 if tasks.is_empty() {
13570 return None;
13571 }
13572
13573 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13574
13575 let row = snapshot
13576 .buffer_snapshot
13577 .buffer_line_for_row(MultiBufferRow(point.row))?
13578 .1
13579 .start
13580 .row;
13581
13582 let context_range =
13583 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13584 Some((
13585 (runnable.buffer_id, row),
13586 RunnableTasks {
13587 templates: tasks,
13588 offset: snapshot
13589 .buffer_snapshot
13590 .anchor_before(runnable.run_range.start),
13591 context_range,
13592 column: point.column,
13593 extra_variables: runnable.extra_captures,
13594 },
13595 ))
13596 })
13597 .collect()
13598 }
13599
13600 fn templates_with_tags(
13601 project: &Entity<Project>,
13602 runnable: &mut Runnable,
13603 cx: &mut App,
13604 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13605 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13606 let (worktree_id, file) = project
13607 .buffer_for_id(runnable.buffer, cx)
13608 .and_then(|buffer| buffer.read(cx).file())
13609 .map(|file| (file.worktree_id(cx), file.clone()))
13610 .unzip();
13611
13612 (
13613 project.task_store().read(cx).task_inventory().cloned(),
13614 worktree_id,
13615 file,
13616 )
13617 });
13618
13619 let mut templates_with_tags = mem::take(&mut runnable.tags)
13620 .into_iter()
13621 .flat_map(|RunnableTag(tag)| {
13622 inventory
13623 .as_ref()
13624 .into_iter()
13625 .flat_map(|inventory| {
13626 inventory.read(cx).list_tasks(
13627 file.clone(),
13628 Some(runnable.language.clone()),
13629 worktree_id,
13630 cx,
13631 )
13632 })
13633 .filter(move |(_, template)| {
13634 template.tags.iter().any(|source_tag| source_tag == &tag)
13635 })
13636 })
13637 .sorted_by_key(|(kind, _)| kind.to_owned())
13638 .collect::<Vec<_>>();
13639 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13640 // Strongest source wins; if we have worktree tag binding, prefer that to
13641 // global and language bindings;
13642 // if we have a global binding, prefer that to language binding.
13643 let first_mismatch = templates_with_tags
13644 .iter()
13645 .position(|(tag_source, _)| tag_source != leading_tag_source);
13646 if let Some(index) = first_mismatch {
13647 templates_with_tags.truncate(index);
13648 }
13649 }
13650
13651 templates_with_tags
13652 }
13653
13654 pub fn move_to_enclosing_bracket(
13655 &mut self,
13656 _: &MoveToEnclosingBracket,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13661 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13662 s.move_offsets_with(|snapshot, selection| {
13663 let Some(enclosing_bracket_ranges) =
13664 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13665 else {
13666 return;
13667 };
13668
13669 let mut best_length = usize::MAX;
13670 let mut best_inside = false;
13671 let mut best_in_bracket_range = false;
13672 let mut best_destination = None;
13673 for (open, close) in enclosing_bracket_ranges {
13674 let close = close.to_inclusive();
13675 let length = close.end() - open.start;
13676 let inside = selection.start >= open.end && selection.end <= *close.start();
13677 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13678 || close.contains(&selection.head());
13679
13680 // If best is next to a bracket and current isn't, skip
13681 if !in_bracket_range && best_in_bracket_range {
13682 continue;
13683 }
13684
13685 // Prefer smaller lengths unless best is inside and current isn't
13686 if length > best_length && (best_inside || !inside) {
13687 continue;
13688 }
13689
13690 best_length = length;
13691 best_inside = inside;
13692 best_in_bracket_range = in_bracket_range;
13693 best_destination = Some(
13694 if close.contains(&selection.start) && close.contains(&selection.end) {
13695 if inside { open.end } else { open.start }
13696 } else if inside {
13697 *close.start()
13698 } else {
13699 *close.end()
13700 },
13701 );
13702 }
13703
13704 if let Some(destination) = best_destination {
13705 selection.collapse_to(destination, SelectionGoal::None);
13706 }
13707 })
13708 });
13709 }
13710
13711 pub fn undo_selection(
13712 &mut self,
13713 _: &UndoSelection,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13718 self.end_selection(window, cx);
13719 self.selection_history.mode = SelectionHistoryMode::Undoing;
13720 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13721 self.change_selections(None, window, cx, |s| {
13722 s.select_anchors(entry.selections.to_vec())
13723 });
13724 self.select_next_state = entry.select_next_state;
13725 self.select_prev_state = entry.select_prev_state;
13726 self.add_selections_state = entry.add_selections_state;
13727 self.request_autoscroll(Autoscroll::newest(), cx);
13728 }
13729 self.selection_history.mode = SelectionHistoryMode::Normal;
13730 }
13731
13732 pub fn redo_selection(
13733 &mut self,
13734 _: &RedoSelection,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13739 self.end_selection(window, cx);
13740 self.selection_history.mode = SelectionHistoryMode::Redoing;
13741 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13742 self.change_selections(None, window, cx, |s| {
13743 s.select_anchors(entry.selections.to_vec())
13744 });
13745 self.select_next_state = entry.select_next_state;
13746 self.select_prev_state = entry.select_prev_state;
13747 self.add_selections_state = entry.add_selections_state;
13748 self.request_autoscroll(Autoscroll::newest(), cx);
13749 }
13750 self.selection_history.mode = SelectionHistoryMode::Normal;
13751 }
13752
13753 pub fn expand_excerpts(
13754 &mut self,
13755 action: &ExpandExcerpts,
13756 _: &mut Window,
13757 cx: &mut Context<Self>,
13758 ) {
13759 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13760 }
13761
13762 pub fn expand_excerpts_down(
13763 &mut self,
13764 action: &ExpandExcerptsDown,
13765 _: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) {
13768 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13769 }
13770
13771 pub fn expand_excerpts_up(
13772 &mut self,
13773 action: &ExpandExcerptsUp,
13774 _: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13778 }
13779
13780 pub fn expand_excerpts_for_direction(
13781 &mut self,
13782 lines: u32,
13783 direction: ExpandExcerptDirection,
13784
13785 cx: &mut Context<Self>,
13786 ) {
13787 let selections = self.selections.disjoint_anchors();
13788
13789 let lines = if lines == 0 {
13790 EditorSettings::get_global(cx).expand_excerpt_lines
13791 } else {
13792 lines
13793 };
13794
13795 self.buffer.update(cx, |buffer, cx| {
13796 let snapshot = buffer.snapshot(cx);
13797 let mut excerpt_ids = selections
13798 .iter()
13799 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13800 .collect::<Vec<_>>();
13801 excerpt_ids.sort();
13802 excerpt_ids.dedup();
13803 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13804 })
13805 }
13806
13807 pub fn expand_excerpt(
13808 &mut self,
13809 excerpt: ExcerptId,
13810 direction: ExpandExcerptDirection,
13811 window: &mut Window,
13812 cx: &mut Context<Self>,
13813 ) {
13814 let current_scroll_position = self.scroll_position(cx);
13815 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13816 let mut should_scroll_up = false;
13817
13818 if direction == ExpandExcerptDirection::Down {
13819 let multi_buffer = self.buffer.read(cx);
13820 let snapshot = multi_buffer.snapshot(cx);
13821 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13822 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13823 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13824 let buffer_snapshot = buffer.read(cx).snapshot();
13825 let excerpt_end_row =
13826 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13827 let last_row = buffer_snapshot.max_point().row;
13828 let lines_below = last_row.saturating_sub(excerpt_end_row);
13829 should_scroll_up = lines_below >= lines_to_expand;
13830 }
13831 }
13832 }
13833 }
13834
13835 self.buffer.update(cx, |buffer, cx| {
13836 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13837 });
13838
13839 if should_scroll_up {
13840 let new_scroll_position =
13841 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13842 self.set_scroll_position(new_scroll_position, window, cx);
13843 }
13844 }
13845
13846 pub fn go_to_singleton_buffer_point(
13847 &mut self,
13848 point: Point,
13849 window: &mut Window,
13850 cx: &mut Context<Self>,
13851 ) {
13852 self.go_to_singleton_buffer_range(point..point, window, cx);
13853 }
13854
13855 pub fn go_to_singleton_buffer_range(
13856 &mut self,
13857 range: Range<Point>,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 let multibuffer = self.buffer().read(cx);
13862 let Some(buffer) = multibuffer.as_singleton() else {
13863 return;
13864 };
13865 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13866 return;
13867 };
13868 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13869 return;
13870 };
13871 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13872 s.select_anchor_ranges([start..end])
13873 });
13874 }
13875
13876 pub fn go_to_diagnostic(
13877 &mut self,
13878 _: &GoToDiagnostic,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13883 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13884 }
13885
13886 pub fn go_to_prev_diagnostic(
13887 &mut self,
13888 _: &GoToPreviousDiagnostic,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13893 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13894 }
13895
13896 pub fn go_to_diagnostic_impl(
13897 &mut self,
13898 direction: Direction,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 let buffer = self.buffer.read(cx).snapshot(cx);
13903 let selection = self.selections.newest::<usize>(cx);
13904
13905 let mut active_group_id = None;
13906 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13907 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13908 active_group_id = Some(active_group.group_id);
13909 }
13910 }
13911
13912 fn filtered(
13913 snapshot: EditorSnapshot,
13914 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13915 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13916 diagnostics
13917 .filter(|entry| entry.range.start != entry.range.end)
13918 .filter(|entry| !entry.diagnostic.is_unnecessary)
13919 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13920 }
13921
13922 let snapshot = self.snapshot(window, cx);
13923 let before = filtered(
13924 snapshot.clone(),
13925 buffer
13926 .diagnostics_in_range(0..selection.start)
13927 .filter(|entry| entry.range.start <= selection.start),
13928 );
13929 let after = filtered(
13930 snapshot,
13931 buffer
13932 .diagnostics_in_range(selection.start..buffer.len())
13933 .filter(|entry| entry.range.start >= selection.start),
13934 );
13935
13936 let mut found: Option<DiagnosticEntry<usize>> = None;
13937 if direction == Direction::Prev {
13938 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13939 {
13940 for diagnostic in prev_diagnostics.into_iter().rev() {
13941 if diagnostic.range.start != selection.start
13942 || active_group_id
13943 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13944 {
13945 found = Some(diagnostic);
13946 break 'outer;
13947 }
13948 }
13949 }
13950 } else {
13951 for diagnostic in after.chain(before) {
13952 if diagnostic.range.start != selection.start
13953 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13954 {
13955 found = Some(diagnostic);
13956 break;
13957 }
13958 }
13959 }
13960 let Some(next_diagnostic) = found else {
13961 return;
13962 };
13963
13964 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13965 return;
13966 };
13967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13968 s.select_ranges(vec![
13969 next_diagnostic.range.start..next_diagnostic.range.start,
13970 ])
13971 });
13972 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13973 self.refresh_inline_completion(false, true, window, cx);
13974 }
13975
13976 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13977 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13978 let snapshot = self.snapshot(window, cx);
13979 let selection = self.selections.newest::<Point>(cx);
13980 self.go_to_hunk_before_or_after_position(
13981 &snapshot,
13982 selection.head(),
13983 Direction::Next,
13984 window,
13985 cx,
13986 );
13987 }
13988
13989 pub fn go_to_hunk_before_or_after_position(
13990 &mut self,
13991 snapshot: &EditorSnapshot,
13992 position: Point,
13993 direction: Direction,
13994 window: &mut Window,
13995 cx: &mut Context<Editor>,
13996 ) {
13997 let row = if direction == Direction::Next {
13998 self.hunk_after_position(snapshot, position)
13999 .map(|hunk| hunk.row_range.start)
14000 } else {
14001 self.hunk_before_position(snapshot, position)
14002 };
14003
14004 if let Some(row) = row {
14005 let destination = Point::new(row.0, 0);
14006 let autoscroll = Autoscroll::center();
14007
14008 self.unfold_ranges(&[destination..destination], false, false, cx);
14009 self.change_selections(Some(autoscroll), window, cx, |s| {
14010 s.select_ranges([destination..destination]);
14011 });
14012 }
14013 }
14014
14015 fn hunk_after_position(
14016 &mut self,
14017 snapshot: &EditorSnapshot,
14018 position: Point,
14019 ) -> Option<MultiBufferDiffHunk> {
14020 snapshot
14021 .buffer_snapshot
14022 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14023 .find(|hunk| hunk.row_range.start.0 > position.row)
14024 .or_else(|| {
14025 snapshot
14026 .buffer_snapshot
14027 .diff_hunks_in_range(Point::zero()..position)
14028 .find(|hunk| hunk.row_range.end.0 < position.row)
14029 })
14030 }
14031
14032 fn go_to_prev_hunk(
14033 &mut self,
14034 _: &GoToPreviousHunk,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14039 let snapshot = self.snapshot(window, cx);
14040 let selection = self.selections.newest::<Point>(cx);
14041 self.go_to_hunk_before_or_after_position(
14042 &snapshot,
14043 selection.head(),
14044 Direction::Prev,
14045 window,
14046 cx,
14047 );
14048 }
14049
14050 fn hunk_before_position(
14051 &mut self,
14052 snapshot: &EditorSnapshot,
14053 position: Point,
14054 ) -> Option<MultiBufferRow> {
14055 snapshot
14056 .buffer_snapshot
14057 .diff_hunk_before(position)
14058 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14059 }
14060
14061 fn go_to_next_change(
14062 &mut self,
14063 _: &GoToNextChange,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 if let Some(selections) = self
14068 .change_list
14069 .next_change(1, Direction::Next)
14070 .map(|s| s.to_vec())
14071 {
14072 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14073 let map = s.display_map();
14074 s.select_display_ranges(selections.iter().map(|a| {
14075 let point = a.to_display_point(&map);
14076 point..point
14077 }))
14078 })
14079 }
14080 }
14081
14082 fn go_to_previous_change(
14083 &mut self,
14084 _: &GoToPreviousChange,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 if let Some(selections) = self
14089 .change_list
14090 .next_change(1, Direction::Prev)
14091 .map(|s| s.to_vec())
14092 {
14093 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14094 let map = s.display_map();
14095 s.select_display_ranges(selections.iter().map(|a| {
14096 let point = a.to_display_point(&map);
14097 point..point
14098 }))
14099 })
14100 }
14101 }
14102
14103 fn go_to_line<T: 'static>(
14104 &mut self,
14105 position: Anchor,
14106 highlight_color: Option<Hsla>,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) {
14110 let snapshot = self.snapshot(window, cx).display_snapshot;
14111 let position = position.to_point(&snapshot.buffer_snapshot);
14112 let start = snapshot
14113 .buffer_snapshot
14114 .clip_point(Point::new(position.row, 0), Bias::Left);
14115 let end = start + Point::new(1, 0);
14116 let start = snapshot.buffer_snapshot.anchor_before(start);
14117 let end = snapshot.buffer_snapshot.anchor_before(end);
14118
14119 self.highlight_rows::<T>(
14120 start..end,
14121 highlight_color
14122 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14123 Default::default(),
14124 cx,
14125 );
14126
14127 if self.buffer.read(cx).is_singleton() {
14128 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14129 }
14130 }
14131
14132 pub fn go_to_definition(
14133 &mut self,
14134 _: &GoToDefinition,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) -> Task<Result<Navigated>> {
14138 let definition =
14139 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14140 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14141 cx.spawn_in(window, async move |editor, cx| {
14142 if definition.await? == Navigated::Yes {
14143 return Ok(Navigated::Yes);
14144 }
14145 match fallback_strategy {
14146 GoToDefinitionFallback::None => Ok(Navigated::No),
14147 GoToDefinitionFallback::FindAllReferences => {
14148 match editor.update_in(cx, |editor, window, cx| {
14149 editor.find_all_references(&FindAllReferences, window, cx)
14150 })? {
14151 Some(references) => references.await,
14152 None => Ok(Navigated::No),
14153 }
14154 }
14155 }
14156 })
14157 }
14158
14159 pub fn go_to_declaration(
14160 &mut self,
14161 _: &GoToDeclaration,
14162 window: &mut Window,
14163 cx: &mut Context<Self>,
14164 ) -> Task<Result<Navigated>> {
14165 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14166 }
14167
14168 pub fn go_to_declaration_split(
14169 &mut self,
14170 _: &GoToDeclaration,
14171 window: &mut Window,
14172 cx: &mut Context<Self>,
14173 ) -> Task<Result<Navigated>> {
14174 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14175 }
14176
14177 pub fn go_to_implementation(
14178 &mut self,
14179 _: &GoToImplementation,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) -> Task<Result<Navigated>> {
14183 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14184 }
14185
14186 pub fn go_to_implementation_split(
14187 &mut self,
14188 _: &GoToImplementationSplit,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) -> Task<Result<Navigated>> {
14192 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14193 }
14194
14195 pub fn go_to_type_definition(
14196 &mut self,
14197 _: &GoToTypeDefinition,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) -> Task<Result<Navigated>> {
14201 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14202 }
14203
14204 pub fn go_to_definition_split(
14205 &mut self,
14206 _: &GoToDefinitionSplit,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) -> Task<Result<Navigated>> {
14210 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14211 }
14212
14213 pub fn go_to_type_definition_split(
14214 &mut self,
14215 _: &GoToTypeDefinitionSplit,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) -> Task<Result<Navigated>> {
14219 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14220 }
14221
14222 fn go_to_definition_of_kind(
14223 &mut self,
14224 kind: GotoDefinitionKind,
14225 split: bool,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) -> Task<Result<Navigated>> {
14229 let Some(provider) = self.semantics_provider.clone() else {
14230 return Task::ready(Ok(Navigated::No));
14231 };
14232 let head = self.selections.newest::<usize>(cx).head();
14233 let buffer = self.buffer.read(cx);
14234 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14235 text_anchor
14236 } else {
14237 return Task::ready(Ok(Navigated::No));
14238 };
14239
14240 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14241 return Task::ready(Ok(Navigated::No));
14242 };
14243
14244 cx.spawn_in(window, async move |editor, cx| {
14245 let definitions = definitions.await?;
14246 let navigated = editor
14247 .update_in(cx, |editor, window, cx| {
14248 editor.navigate_to_hover_links(
14249 Some(kind),
14250 definitions
14251 .into_iter()
14252 .filter(|location| {
14253 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14254 })
14255 .map(HoverLink::Text)
14256 .collect::<Vec<_>>(),
14257 split,
14258 window,
14259 cx,
14260 )
14261 })?
14262 .await?;
14263 anyhow::Ok(navigated)
14264 })
14265 }
14266
14267 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14268 let selection = self.selections.newest_anchor();
14269 let head = selection.head();
14270 let tail = selection.tail();
14271
14272 let Some((buffer, start_position)) =
14273 self.buffer.read(cx).text_anchor_for_position(head, cx)
14274 else {
14275 return;
14276 };
14277
14278 let end_position = if head != tail {
14279 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14280 return;
14281 };
14282 Some(pos)
14283 } else {
14284 None
14285 };
14286
14287 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14288 let url = if let Some(end_pos) = end_position {
14289 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14290 } else {
14291 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14292 };
14293
14294 if let Some(url) = url {
14295 editor.update(cx, |_, cx| {
14296 cx.open_url(&url);
14297 })
14298 } else {
14299 Ok(())
14300 }
14301 });
14302
14303 url_finder.detach();
14304 }
14305
14306 pub fn open_selected_filename(
14307 &mut self,
14308 _: &OpenSelectedFilename,
14309 window: &mut Window,
14310 cx: &mut Context<Self>,
14311 ) {
14312 let Some(workspace) = self.workspace() else {
14313 return;
14314 };
14315
14316 let position = self.selections.newest_anchor().head();
14317
14318 let Some((buffer, buffer_position)) =
14319 self.buffer.read(cx).text_anchor_for_position(position, cx)
14320 else {
14321 return;
14322 };
14323
14324 let project = self.project.clone();
14325
14326 cx.spawn_in(window, async move |_, cx| {
14327 let result = find_file(&buffer, project, buffer_position, cx).await;
14328
14329 if let Some((_, path)) = result {
14330 workspace
14331 .update_in(cx, |workspace, window, cx| {
14332 workspace.open_resolved_path(path, window, cx)
14333 })?
14334 .await?;
14335 }
14336 anyhow::Ok(())
14337 })
14338 .detach();
14339 }
14340
14341 pub(crate) fn navigate_to_hover_links(
14342 &mut self,
14343 kind: Option<GotoDefinitionKind>,
14344 mut definitions: Vec<HoverLink>,
14345 split: bool,
14346 window: &mut Window,
14347 cx: &mut Context<Editor>,
14348 ) -> Task<Result<Navigated>> {
14349 // If there is one definition, just open it directly
14350 if definitions.len() == 1 {
14351 let definition = definitions.pop().unwrap();
14352
14353 enum TargetTaskResult {
14354 Location(Option<Location>),
14355 AlreadyNavigated,
14356 }
14357
14358 let target_task = match definition {
14359 HoverLink::Text(link) => {
14360 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14361 }
14362 HoverLink::InlayHint(lsp_location, server_id) => {
14363 let computation =
14364 self.compute_target_location(lsp_location, server_id, window, cx);
14365 cx.background_spawn(async move {
14366 let location = computation.await?;
14367 Ok(TargetTaskResult::Location(location))
14368 })
14369 }
14370 HoverLink::Url(url) => {
14371 cx.open_url(&url);
14372 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14373 }
14374 HoverLink::File(path) => {
14375 if let Some(workspace) = self.workspace() {
14376 cx.spawn_in(window, async move |_, cx| {
14377 workspace
14378 .update_in(cx, |workspace, window, cx| {
14379 workspace.open_resolved_path(path, window, cx)
14380 })?
14381 .await
14382 .map(|_| TargetTaskResult::AlreadyNavigated)
14383 })
14384 } else {
14385 Task::ready(Ok(TargetTaskResult::Location(None)))
14386 }
14387 }
14388 };
14389 cx.spawn_in(window, async move |editor, cx| {
14390 let target = match target_task.await.context("target resolution task")? {
14391 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14392 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14393 TargetTaskResult::Location(Some(target)) => target,
14394 };
14395
14396 editor.update_in(cx, |editor, window, cx| {
14397 let Some(workspace) = editor.workspace() else {
14398 return Navigated::No;
14399 };
14400 let pane = workspace.read(cx).active_pane().clone();
14401
14402 let range = target.range.to_point(target.buffer.read(cx));
14403 let range = editor.range_for_match(&range);
14404 let range = collapse_multiline_range(range);
14405
14406 if !split
14407 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14408 {
14409 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14410 } else {
14411 window.defer(cx, move |window, cx| {
14412 let target_editor: Entity<Self> =
14413 workspace.update(cx, |workspace, cx| {
14414 let pane = if split {
14415 workspace.adjacent_pane(window, cx)
14416 } else {
14417 workspace.active_pane().clone()
14418 };
14419
14420 workspace.open_project_item(
14421 pane,
14422 target.buffer.clone(),
14423 true,
14424 true,
14425 window,
14426 cx,
14427 )
14428 });
14429 target_editor.update(cx, |target_editor, cx| {
14430 // When selecting a definition in a different buffer, disable the nav history
14431 // to avoid creating a history entry at the previous cursor location.
14432 pane.update(cx, |pane, _| pane.disable_history());
14433 target_editor.go_to_singleton_buffer_range(range, window, cx);
14434 pane.update(cx, |pane, _| pane.enable_history());
14435 });
14436 });
14437 }
14438 Navigated::Yes
14439 })
14440 })
14441 } else if !definitions.is_empty() {
14442 cx.spawn_in(window, async move |editor, cx| {
14443 let (title, location_tasks, workspace) = editor
14444 .update_in(cx, |editor, window, cx| {
14445 let tab_kind = match kind {
14446 Some(GotoDefinitionKind::Implementation) => "Implementations",
14447 _ => "Definitions",
14448 };
14449 let title = definitions
14450 .iter()
14451 .find_map(|definition| match definition {
14452 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14453 let buffer = origin.buffer.read(cx);
14454 format!(
14455 "{} for {}",
14456 tab_kind,
14457 buffer
14458 .text_for_range(origin.range.clone())
14459 .collect::<String>()
14460 )
14461 }),
14462 HoverLink::InlayHint(_, _) => None,
14463 HoverLink::Url(_) => None,
14464 HoverLink::File(_) => None,
14465 })
14466 .unwrap_or(tab_kind.to_string());
14467 let location_tasks = definitions
14468 .into_iter()
14469 .map(|definition| match definition {
14470 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14471 HoverLink::InlayHint(lsp_location, server_id) => editor
14472 .compute_target_location(lsp_location, server_id, window, cx),
14473 HoverLink::Url(_) => Task::ready(Ok(None)),
14474 HoverLink::File(_) => Task::ready(Ok(None)),
14475 })
14476 .collect::<Vec<_>>();
14477 (title, location_tasks, editor.workspace().clone())
14478 })
14479 .context("location tasks preparation")?;
14480
14481 let locations = future::join_all(location_tasks)
14482 .await
14483 .into_iter()
14484 .filter_map(|location| location.transpose())
14485 .collect::<Result<_>>()
14486 .context("location tasks")?;
14487
14488 let Some(workspace) = workspace else {
14489 return Ok(Navigated::No);
14490 };
14491 let opened = workspace
14492 .update_in(cx, |workspace, window, cx| {
14493 Self::open_locations_in_multibuffer(
14494 workspace,
14495 locations,
14496 title,
14497 split,
14498 MultibufferSelectionMode::First,
14499 window,
14500 cx,
14501 )
14502 })
14503 .ok();
14504
14505 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14506 })
14507 } else {
14508 Task::ready(Ok(Navigated::No))
14509 }
14510 }
14511
14512 fn compute_target_location(
14513 &self,
14514 lsp_location: lsp::Location,
14515 server_id: LanguageServerId,
14516 window: &mut Window,
14517 cx: &mut Context<Self>,
14518 ) -> Task<anyhow::Result<Option<Location>>> {
14519 let Some(project) = self.project.clone() else {
14520 return Task::ready(Ok(None));
14521 };
14522
14523 cx.spawn_in(window, async move |editor, cx| {
14524 let location_task = editor.update(cx, |_, cx| {
14525 project.update(cx, |project, cx| {
14526 let language_server_name = project
14527 .language_server_statuses(cx)
14528 .find(|(id, _)| server_id == *id)
14529 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14530 language_server_name.map(|language_server_name| {
14531 project.open_local_buffer_via_lsp(
14532 lsp_location.uri.clone(),
14533 server_id,
14534 language_server_name,
14535 cx,
14536 )
14537 })
14538 })
14539 })?;
14540 let location = match location_task {
14541 Some(task) => Some({
14542 let target_buffer_handle = task.await.context("open local buffer")?;
14543 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14544 let target_start = target_buffer
14545 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14546 let target_end = target_buffer
14547 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14548 target_buffer.anchor_after(target_start)
14549 ..target_buffer.anchor_before(target_end)
14550 })?;
14551 Location {
14552 buffer: target_buffer_handle,
14553 range,
14554 }
14555 }),
14556 None => None,
14557 };
14558 Ok(location)
14559 })
14560 }
14561
14562 pub fn find_all_references(
14563 &mut self,
14564 _: &FindAllReferences,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) -> Option<Task<Result<Navigated>>> {
14568 let selection = self.selections.newest::<usize>(cx);
14569 let multi_buffer = self.buffer.read(cx);
14570 let head = selection.head();
14571
14572 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14573 let head_anchor = multi_buffer_snapshot.anchor_at(
14574 head,
14575 if head < selection.tail() {
14576 Bias::Right
14577 } else {
14578 Bias::Left
14579 },
14580 );
14581
14582 match self
14583 .find_all_references_task_sources
14584 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14585 {
14586 Ok(_) => {
14587 log::info!(
14588 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14589 );
14590 return None;
14591 }
14592 Err(i) => {
14593 self.find_all_references_task_sources.insert(i, head_anchor);
14594 }
14595 }
14596
14597 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14598 let workspace = self.workspace()?;
14599 let project = workspace.read(cx).project().clone();
14600 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14601 Some(cx.spawn_in(window, async move |editor, cx| {
14602 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14603 if let Ok(i) = editor
14604 .find_all_references_task_sources
14605 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14606 {
14607 editor.find_all_references_task_sources.remove(i);
14608 }
14609 });
14610
14611 let locations = references.await?;
14612 if locations.is_empty() {
14613 return anyhow::Ok(Navigated::No);
14614 }
14615
14616 workspace.update_in(cx, |workspace, window, cx| {
14617 let title = locations
14618 .first()
14619 .as_ref()
14620 .map(|location| {
14621 let buffer = location.buffer.read(cx);
14622 format!(
14623 "References to `{}`",
14624 buffer
14625 .text_for_range(location.range.clone())
14626 .collect::<String>()
14627 )
14628 })
14629 .unwrap();
14630 Self::open_locations_in_multibuffer(
14631 workspace,
14632 locations,
14633 title,
14634 false,
14635 MultibufferSelectionMode::First,
14636 window,
14637 cx,
14638 );
14639 Navigated::Yes
14640 })
14641 }))
14642 }
14643
14644 /// Opens a multibuffer with the given project locations in it
14645 pub fn open_locations_in_multibuffer(
14646 workspace: &mut Workspace,
14647 mut locations: Vec<Location>,
14648 title: String,
14649 split: bool,
14650 multibuffer_selection_mode: MultibufferSelectionMode,
14651 window: &mut Window,
14652 cx: &mut Context<Workspace>,
14653 ) {
14654 // If there are multiple definitions, open them in a multibuffer
14655 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14656 let mut locations = locations.into_iter().peekable();
14657 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14658 let capability = workspace.project().read(cx).capability();
14659
14660 let excerpt_buffer = cx.new(|cx| {
14661 let mut multibuffer = MultiBuffer::new(capability);
14662 while let Some(location) = locations.next() {
14663 let buffer = location.buffer.read(cx);
14664 let mut ranges_for_buffer = Vec::new();
14665 let range = location.range.to_point(buffer);
14666 ranges_for_buffer.push(range.clone());
14667
14668 while let Some(next_location) = locations.peek() {
14669 if next_location.buffer == location.buffer {
14670 ranges_for_buffer.push(next_location.range.to_point(buffer));
14671 locations.next();
14672 } else {
14673 break;
14674 }
14675 }
14676
14677 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14678 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14679 PathKey::for_buffer(&location.buffer, cx),
14680 location.buffer.clone(),
14681 ranges_for_buffer,
14682 DEFAULT_MULTIBUFFER_CONTEXT,
14683 cx,
14684 );
14685 ranges.extend(new_ranges)
14686 }
14687
14688 multibuffer.with_title(title)
14689 });
14690
14691 let editor = cx.new(|cx| {
14692 Editor::for_multibuffer(
14693 excerpt_buffer,
14694 Some(workspace.project().clone()),
14695 window,
14696 cx,
14697 )
14698 });
14699 editor.update(cx, |editor, cx| {
14700 match multibuffer_selection_mode {
14701 MultibufferSelectionMode::First => {
14702 if let Some(first_range) = ranges.first() {
14703 editor.change_selections(None, window, cx, |selections| {
14704 selections.clear_disjoint();
14705 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14706 });
14707 }
14708 editor.highlight_background::<Self>(
14709 &ranges,
14710 |theme| theme.editor_highlighted_line_background,
14711 cx,
14712 );
14713 }
14714 MultibufferSelectionMode::All => {
14715 editor.change_selections(None, window, cx, |selections| {
14716 selections.clear_disjoint();
14717 selections.select_anchor_ranges(ranges);
14718 });
14719 }
14720 }
14721 editor.register_buffers_with_language_servers(cx);
14722 });
14723
14724 let item = Box::new(editor);
14725 let item_id = item.item_id();
14726
14727 if split {
14728 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14729 } else {
14730 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14731 let (preview_item_id, preview_item_idx) =
14732 workspace.active_pane().update(cx, |pane, _| {
14733 (pane.preview_item_id(), pane.preview_item_idx())
14734 });
14735
14736 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14737
14738 if let Some(preview_item_id) = preview_item_id {
14739 workspace.active_pane().update(cx, |pane, cx| {
14740 pane.remove_item(preview_item_id, false, false, window, cx);
14741 });
14742 }
14743 } else {
14744 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14745 }
14746 }
14747 workspace.active_pane().update(cx, |pane, cx| {
14748 pane.set_preview_item_id(Some(item_id), cx);
14749 });
14750 }
14751
14752 pub fn rename(
14753 &mut self,
14754 _: &Rename,
14755 window: &mut Window,
14756 cx: &mut Context<Self>,
14757 ) -> Option<Task<Result<()>>> {
14758 use language::ToOffset as _;
14759
14760 let provider = self.semantics_provider.clone()?;
14761 let selection = self.selections.newest_anchor().clone();
14762 let (cursor_buffer, cursor_buffer_position) = self
14763 .buffer
14764 .read(cx)
14765 .text_anchor_for_position(selection.head(), cx)?;
14766 let (tail_buffer, cursor_buffer_position_end) = self
14767 .buffer
14768 .read(cx)
14769 .text_anchor_for_position(selection.tail(), cx)?;
14770 if tail_buffer != cursor_buffer {
14771 return None;
14772 }
14773
14774 let snapshot = cursor_buffer.read(cx).snapshot();
14775 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14776 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14777 let prepare_rename = provider
14778 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14779 .unwrap_or_else(|| Task::ready(Ok(None)));
14780 drop(snapshot);
14781
14782 Some(cx.spawn_in(window, async move |this, cx| {
14783 let rename_range = if let Some(range) = prepare_rename.await? {
14784 Some(range)
14785 } else {
14786 this.update(cx, |this, cx| {
14787 let buffer = this.buffer.read(cx).snapshot(cx);
14788 let mut buffer_highlights = this
14789 .document_highlights_for_position(selection.head(), &buffer)
14790 .filter(|highlight| {
14791 highlight.start.excerpt_id == selection.head().excerpt_id
14792 && highlight.end.excerpt_id == selection.head().excerpt_id
14793 });
14794 buffer_highlights
14795 .next()
14796 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14797 })?
14798 };
14799 if let Some(rename_range) = rename_range {
14800 this.update_in(cx, |this, window, cx| {
14801 let snapshot = cursor_buffer.read(cx).snapshot();
14802 let rename_buffer_range = rename_range.to_offset(&snapshot);
14803 let cursor_offset_in_rename_range =
14804 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14805 let cursor_offset_in_rename_range_end =
14806 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14807
14808 this.take_rename(false, window, cx);
14809 let buffer = this.buffer.read(cx).read(cx);
14810 let cursor_offset = selection.head().to_offset(&buffer);
14811 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14812 let rename_end = rename_start + rename_buffer_range.len();
14813 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14814 let mut old_highlight_id = None;
14815 let old_name: Arc<str> = buffer
14816 .chunks(rename_start..rename_end, true)
14817 .map(|chunk| {
14818 if old_highlight_id.is_none() {
14819 old_highlight_id = chunk.syntax_highlight_id;
14820 }
14821 chunk.text
14822 })
14823 .collect::<String>()
14824 .into();
14825
14826 drop(buffer);
14827
14828 // Position the selection in the rename editor so that it matches the current selection.
14829 this.show_local_selections = false;
14830 let rename_editor = cx.new(|cx| {
14831 let mut editor = Editor::single_line(window, cx);
14832 editor.buffer.update(cx, |buffer, cx| {
14833 buffer.edit([(0..0, old_name.clone())], None, cx)
14834 });
14835 let rename_selection_range = match cursor_offset_in_rename_range
14836 .cmp(&cursor_offset_in_rename_range_end)
14837 {
14838 Ordering::Equal => {
14839 editor.select_all(&SelectAll, window, cx);
14840 return editor;
14841 }
14842 Ordering::Less => {
14843 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14844 }
14845 Ordering::Greater => {
14846 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14847 }
14848 };
14849 if rename_selection_range.end > old_name.len() {
14850 editor.select_all(&SelectAll, window, cx);
14851 } else {
14852 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14853 s.select_ranges([rename_selection_range]);
14854 });
14855 }
14856 editor
14857 });
14858 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14859 if e == &EditorEvent::Focused {
14860 cx.emit(EditorEvent::FocusedIn)
14861 }
14862 })
14863 .detach();
14864
14865 let write_highlights =
14866 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14867 let read_highlights =
14868 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14869 let ranges = write_highlights
14870 .iter()
14871 .flat_map(|(_, ranges)| ranges.iter())
14872 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14873 .cloned()
14874 .collect();
14875
14876 this.highlight_text::<Rename>(
14877 ranges,
14878 HighlightStyle {
14879 fade_out: Some(0.6),
14880 ..Default::default()
14881 },
14882 cx,
14883 );
14884 let rename_focus_handle = rename_editor.focus_handle(cx);
14885 window.focus(&rename_focus_handle);
14886 let block_id = this.insert_blocks(
14887 [BlockProperties {
14888 style: BlockStyle::Flex,
14889 placement: BlockPlacement::Below(range.start),
14890 height: Some(1),
14891 render: Arc::new({
14892 let rename_editor = rename_editor.clone();
14893 move |cx: &mut BlockContext| {
14894 let mut text_style = cx.editor_style.text.clone();
14895 if let Some(highlight_style) = old_highlight_id
14896 .and_then(|h| h.style(&cx.editor_style.syntax))
14897 {
14898 text_style = text_style.highlight(highlight_style);
14899 }
14900 div()
14901 .block_mouse_down()
14902 .pl(cx.anchor_x)
14903 .child(EditorElement::new(
14904 &rename_editor,
14905 EditorStyle {
14906 background: cx.theme().system().transparent,
14907 local_player: cx.editor_style.local_player,
14908 text: text_style,
14909 scrollbar_width: cx.editor_style.scrollbar_width,
14910 syntax: cx.editor_style.syntax.clone(),
14911 status: cx.editor_style.status.clone(),
14912 inlay_hints_style: HighlightStyle {
14913 font_weight: Some(FontWeight::BOLD),
14914 ..make_inlay_hints_style(cx.app)
14915 },
14916 inline_completion_styles: make_suggestion_styles(
14917 cx.app,
14918 ),
14919 ..EditorStyle::default()
14920 },
14921 ))
14922 .into_any_element()
14923 }
14924 }),
14925 priority: 0,
14926 render_in_minimap: true,
14927 }],
14928 Some(Autoscroll::fit()),
14929 cx,
14930 )[0];
14931 this.pending_rename = Some(RenameState {
14932 range,
14933 old_name,
14934 editor: rename_editor,
14935 block_id,
14936 });
14937 })?;
14938 }
14939
14940 Ok(())
14941 }))
14942 }
14943
14944 pub fn confirm_rename(
14945 &mut self,
14946 _: &ConfirmRename,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) -> Option<Task<Result<()>>> {
14950 let rename = self.take_rename(false, window, cx)?;
14951 let workspace = self.workspace()?.downgrade();
14952 let (buffer, start) = self
14953 .buffer
14954 .read(cx)
14955 .text_anchor_for_position(rename.range.start, cx)?;
14956 let (end_buffer, _) = self
14957 .buffer
14958 .read(cx)
14959 .text_anchor_for_position(rename.range.end, cx)?;
14960 if buffer != end_buffer {
14961 return None;
14962 }
14963
14964 let old_name = rename.old_name;
14965 let new_name = rename.editor.read(cx).text(cx);
14966
14967 let rename = self.semantics_provider.as_ref()?.perform_rename(
14968 &buffer,
14969 start,
14970 new_name.clone(),
14971 cx,
14972 )?;
14973
14974 Some(cx.spawn_in(window, async move |editor, cx| {
14975 let project_transaction = rename.await?;
14976 Self::open_project_transaction(
14977 &editor,
14978 workspace,
14979 project_transaction,
14980 format!("Rename: {} → {}", old_name, new_name),
14981 cx,
14982 )
14983 .await?;
14984
14985 editor.update(cx, |editor, cx| {
14986 editor.refresh_document_highlights(cx);
14987 })?;
14988 Ok(())
14989 }))
14990 }
14991
14992 fn take_rename(
14993 &mut self,
14994 moving_cursor: bool,
14995 window: &mut Window,
14996 cx: &mut Context<Self>,
14997 ) -> Option<RenameState> {
14998 let rename = self.pending_rename.take()?;
14999 if rename.editor.focus_handle(cx).is_focused(window) {
15000 window.focus(&self.focus_handle);
15001 }
15002
15003 self.remove_blocks(
15004 [rename.block_id].into_iter().collect(),
15005 Some(Autoscroll::fit()),
15006 cx,
15007 );
15008 self.clear_highlights::<Rename>(cx);
15009 self.show_local_selections = true;
15010
15011 if moving_cursor {
15012 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15013 editor.selections.newest::<usize>(cx).head()
15014 });
15015
15016 // Update the selection to match the position of the selection inside
15017 // the rename editor.
15018 let snapshot = self.buffer.read(cx).read(cx);
15019 let rename_range = rename.range.to_offset(&snapshot);
15020 let cursor_in_editor = snapshot
15021 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15022 .min(rename_range.end);
15023 drop(snapshot);
15024
15025 self.change_selections(None, window, cx, |s| {
15026 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15027 });
15028 } else {
15029 self.refresh_document_highlights(cx);
15030 }
15031
15032 Some(rename)
15033 }
15034
15035 pub fn pending_rename(&self) -> Option<&RenameState> {
15036 self.pending_rename.as_ref()
15037 }
15038
15039 fn format(
15040 &mut self,
15041 _: &Format,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) -> Option<Task<Result<()>>> {
15045 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15046
15047 let project = match &self.project {
15048 Some(project) => project.clone(),
15049 None => return None,
15050 };
15051
15052 Some(self.perform_format(
15053 project,
15054 FormatTrigger::Manual,
15055 FormatTarget::Buffers,
15056 window,
15057 cx,
15058 ))
15059 }
15060
15061 fn format_selections(
15062 &mut self,
15063 _: &FormatSelections,
15064 window: &mut Window,
15065 cx: &mut Context<Self>,
15066 ) -> Option<Task<Result<()>>> {
15067 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15068
15069 let project = match &self.project {
15070 Some(project) => project.clone(),
15071 None => return None,
15072 };
15073
15074 let ranges = self
15075 .selections
15076 .all_adjusted(cx)
15077 .into_iter()
15078 .map(|selection| selection.range())
15079 .collect_vec();
15080
15081 Some(self.perform_format(
15082 project,
15083 FormatTrigger::Manual,
15084 FormatTarget::Ranges(ranges),
15085 window,
15086 cx,
15087 ))
15088 }
15089
15090 fn perform_format(
15091 &mut self,
15092 project: Entity<Project>,
15093 trigger: FormatTrigger,
15094 target: FormatTarget,
15095 window: &mut Window,
15096 cx: &mut Context<Self>,
15097 ) -> Task<Result<()>> {
15098 let buffer = self.buffer.clone();
15099 let (buffers, target) = match target {
15100 FormatTarget::Buffers => {
15101 let mut buffers = buffer.read(cx).all_buffers();
15102 if trigger == FormatTrigger::Save {
15103 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15104 }
15105 (buffers, LspFormatTarget::Buffers)
15106 }
15107 FormatTarget::Ranges(selection_ranges) => {
15108 let multi_buffer = buffer.read(cx);
15109 let snapshot = multi_buffer.read(cx);
15110 let mut buffers = HashSet::default();
15111 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15112 BTreeMap::new();
15113 for selection_range in selection_ranges {
15114 for (buffer, buffer_range, _) in
15115 snapshot.range_to_buffer_ranges(selection_range)
15116 {
15117 let buffer_id = buffer.remote_id();
15118 let start = buffer.anchor_before(buffer_range.start);
15119 let end = buffer.anchor_after(buffer_range.end);
15120 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15121 buffer_id_to_ranges
15122 .entry(buffer_id)
15123 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15124 .or_insert_with(|| vec![start..end]);
15125 }
15126 }
15127 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15128 }
15129 };
15130
15131 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15132 let selections_prev = transaction_id_prev
15133 .and_then(|transaction_id_prev| {
15134 // default to selections as they were after the last edit, if we have them,
15135 // instead of how they are now.
15136 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15137 // will take you back to where you made the last edit, instead of staying where you scrolled
15138 self.selection_history
15139 .transaction(transaction_id_prev)
15140 .map(|t| t.0.clone())
15141 })
15142 .unwrap_or_else(|| {
15143 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15144 self.selections.disjoint_anchors()
15145 });
15146
15147 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15148 let format = project.update(cx, |project, cx| {
15149 project.format(buffers, target, true, trigger, cx)
15150 });
15151
15152 cx.spawn_in(window, async move |editor, cx| {
15153 let transaction = futures::select_biased! {
15154 transaction = format.log_err().fuse() => transaction,
15155 () = timeout => {
15156 log::warn!("timed out waiting for formatting");
15157 None
15158 }
15159 };
15160
15161 buffer
15162 .update(cx, |buffer, cx| {
15163 if let Some(transaction) = transaction {
15164 if !buffer.is_singleton() {
15165 buffer.push_transaction(&transaction.0, cx);
15166 }
15167 }
15168 cx.notify();
15169 })
15170 .ok();
15171
15172 if let Some(transaction_id_now) =
15173 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15174 {
15175 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15176 if has_new_transaction {
15177 _ = editor.update(cx, |editor, _| {
15178 editor
15179 .selection_history
15180 .insert_transaction(transaction_id_now, selections_prev);
15181 });
15182 }
15183 }
15184
15185 Ok(())
15186 })
15187 }
15188
15189 fn organize_imports(
15190 &mut self,
15191 _: &OrganizeImports,
15192 window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) -> Option<Task<Result<()>>> {
15195 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15196 let project = match &self.project {
15197 Some(project) => project.clone(),
15198 None => return None,
15199 };
15200 Some(self.perform_code_action_kind(
15201 project,
15202 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15203 window,
15204 cx,
15205 ))
15206 }
15207
15208 fn perform_code_action_kind(
15209 &mut self,
15210 project: Entity<Project>,
15211 kind: CodeActionKind,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) -> Task<Result<()>> {
15215 let buffer = self.buffer.clone();
15216 let buffers = buffer.read(cx).all_buffers();
15217 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15218 let apply_action = project.update(cx, |project, cx| {
15219 project.apply_code_action_kind(buffers, kind, true, cx)
15220 });
15221 cx.spawn_in(window, async move |_, cx| {
15222 let transaction = futures::select_biased! {
15223 () = timeout => {
15224 log::warn!("timed out waiting for executing code action");
15225 None
15226 }
15227 transaction = apply_action.log_err().fuse() => transaction,
15228 };
15229 buffer
15230 .update(cx, |buffer, cx| {
15231 // check if we need this
15232 if let Some(transaction) = transaction {
15233 if !buffer.is_singleton() {
15234 buffer.push_transaction(&transaction.0, cx);
15235 }
15236 }
15237 cx.notify();
15238 })
15239 .ok();
15240 Ok(())
15241 })
15242 }
15243
15244 fn restart_language_server(
15245 &mut self,
15246 _: &RestartLanguageServer,
15247 _: &mut Window,
15248 cx: &mut Context<Self>,
15249 ) {
15250 if let Some(project) = self.project.clone() {
15251 self.buffer.update(cx, |multi_buffer, cx| {
15252 project.update(cx, |project, cx| {
15253 project.restart_language_servers_for_buffers(
15254 multi_buffer.all_buffers().into_iter().collect(),
15255 cx,
15256 );
15257 });
15258 })
15259 }
15260 }
15261
15262 fn stop_language_server(
15263 &mut self,
15264 _: &StopLanguageServer,
15265 _: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 if let Some(project) = self.project.clone() {
15269 self.buffer.update(cx, |multi_buffer, cx| {
15270 project.update(cx, |project, cx| {
15271 project.stop_language_servers_for_buffers(
15272 multi_buffer.all_buffers().into_iter().collect(),
15273 cx,
15274 );
15275 cx.emit(project::Event::RefreshInlayHints);
15276 });
15277 });
15278 }
15279 }
15280
15281 fn cancel_language_server_work(
15282 workspace: &mut Workspace,
15283 _: &actions::CancelLanguageServerWork,
15284 _: &mut Window,
15285 cx: &mut Context<Workspace>,
15286 ) {
15287 let project = workspace.project();
15288 let buffers = workspace
15289 .active_item(cx)
15290 .and_then(|item| item.act_as::<Editor>(cx))
15291 .map_or(HashSet::default(), |editor| {
15292 editor.read(cx).buffer.read(cx).all_buffers()
15293 });
15294 project.update(cx, |project, cx| {
15295 project.cancel_language_server_work_for_buffers(buffers, cx);
15296 });
15297 }
15298
15299 fn show_character_palette(
15300 &mut self,
15301 _: &ShowCharacterPalette,
15302 window: &mut Window,
15303 _: &mut Context<Self>,
15304 ) {
15305 window.show_character_palette();
15306 }
15307
15308 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15309 if self.mode.is_minimap() {
15310 return;
15311 }
15312
15313 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15314 let buffer = self.buffer.read(cx).snapshot(cx);
15315 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15316 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15317 let is_valid = buffer
15318 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15319 .any(|entry| {
15320 entry.diagnostic.is_primary
15321 && !entry.range.is_empty()
15322 && entry.range.start == primary_range_start
15323 && entry.diagnostic.message == active_diagnostics.active_message
15324 });
15325
15326 if !is_valid {
15327 self.dismiss_diagnostics(cx);
15328 }
15329 }
15330 }
15331
15332 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15333 match &self.active_diagnostics {
15334 ActiveDiagnostic::Group(group) => Some(group),
15335 _ => None,
15336 }
15337 }
15338
15339 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15340 self.dismiss_diagnostics(cx);
15341 self.active_diagnostics = ActiveDiagnostic::All;
15342 }
15343
15344 fn activate_diagnostics(
15345 &mut self,
15346 buffer_id: BufferId,
15347 diagnostic: DiagnosticEntry<usize>,
15348 window: &mut Window,
15349 cx: &mut Context<Self>,
15350 ) {
15351 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15352 return;
15353 }
15354 self.dismiss_diagnostics(cx);
15355 let snapshot = self.snapshot(window, cx);
15356 let buffer = self.buffer.read(cx).snapshot(cx);
15357 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15358 return;
15359 };
15360
15361 let diagnostic_group = buffer
15362 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15363 .collect::<Vec<_>>();
15364
15365 let blocks =
15366 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15367
15368 let blocks = self.display_map.update(cx, |display_map, cx| {
15369 display_map.insert_blocks(blocks, cx).into_iter().collect()
15370 });
15371 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15372 active_range: buffer.anchor_before(diagnostic.range.start)
15373 ..buffer.anchor_after(diagnostic.range.end),
15374 active_message: diagnostic.diagnostic.message.clone(),
15375 group_id: diagnostic.diagnostic.group_id,
15376 blocks,
15377 });
15378 cx.notify();
15379 }
15380
15381 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15382 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15383 return;
15384 };
15385
15386 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15387 if let ActiveDiagnostic::Group(group) = prev {
15388 self.display_map.update(cx, |display_map, cx| {
15389 display_map.remove_blocks(group.blocks, cx);
15390 });
15391 cx.notify();
15392 }
15393 }
15394
15395 /// Disable inline diagnostics rendering for this editor.
15396 pub fn disable_inline_diagnostics(&mut self) {
15397 self.inline_diagnostics_enabled = false;
15398 self.inline_diagnostics_update = Task::ready(());
15399 self.inline_diagnostics.clear();
15400 }
15401
15402 pub fn diagnostics_enabled(&self) -> bool {
15403 self.mode.is_full()
15404 }
15405
15406 pub fn inline_diagnostics_enabled(&self) -> bool {
15407 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15408 }
15409
15410 pub fn show_inline_diagnostics(&self) -> bool {
15411 self.show_inline_diagnostics
15412 }
15413
15414 pub fn toggle_inline_diagnostics(
15415 &mut self,
15416 _: &ToggleInlineDiagnostics,
15417 window: &mut Window,
15418 cx: &mut Context<Editor>,
15419 ) {
15420 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15421 self.refresh_inline_diagnostics(false, window, cx);
15422 }
15423
15424 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15425 self.diagnostics_max_severity = severity;
15426 self.display_map.update(cx, |display_map, _| {
15427 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15428 });
15429 }
15430
15431 pub fn toggle_diagnostics(
15432 &mut self,
15433 _: &ToggleDiagnostics,
15434 window: &mut Window,
15435 cx: &mut Context<Editor>,
15436 ) {
15437 if !self.diagnostics_enabled() {
15438 return;
15439 }
15440
15441 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15442 EditorSettings::get_global(cx)
15443 .diagnostics_max_severity
15444 .filter(|severity| severity != &DiagnosticSeverity::Off)
15445 .unwrap_or(DiagnosticSeverity::Hint)
15446 } else {
15447 DiagnosticSeverity::Off
15448 };
15449 self.set_max_diagnostics_severity(new_severity, cx);
15450 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15451 self.active_diagnostics = ActiveDiagnostic::None;
15452 self.inline_diagnostics_update = Task::ready(());
15453 self.inline_diagnostics.clear();
15454 } else {
15455 self.refresh_inline_diagnostics(false, window, cx);
15456 }
15457
15458 cx.notify();
15459 }
15460
15461 pub fn toggle_minimap(
15462 &mut self,
15463 _: &ToggleMinimap,
15464 window: &mut Window,
15465 cx: &mut Context<Editor>,
15466 ) {
15467 if self.supports_minimap(cx) {
15468 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15469 }
15470 }
15471
15472 fn refresh_inline_diagnostics(
15473 &mut self,
15474 debounce: bool,
15475 window: &mut Window,
15476 cx: &mut Context<Self>,
15477 ) {
15478 let max_severity = ProjectSettings::get_global(cx)
15479 .diagnostics
15480 .inline
15481 .max_severity
15482 .unwrap_or(self.diagnostics_max_severity);
15483
15484 if self.mode.is_minimap()
15485 || !self.inline_diagnostics_enabled()
15486 || !self.show_inline_diagnostics
15487 || max_severity == DiagnosticSeverity::Off
15488 {
15489 self.inline_diagnostics_update = Task::ready(());
15490 self.inline_diagnostics.clear();
15491 return;
15492 }
15493
15494 let debounce_ms = ProjectSettings::get_global(cx)
15495 .diagnostics
15496 .inline
15497 .update_debounce_ms;
15498 let debounce = if debounce && debounce_ms > 0 {
15499 Some(Duration::from_millis(debounce_ms))
15500 } else {
15501 None
15502 };
15503 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15504 let editor = editor.upgrade().unwrap();
15505
15506 if let Some(debounce) = debounce {
15507 cx.background_executor().timer(debounce).await;
15508 }
15509 let Some(snapshot) = editor
15510 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15511 .ok()
15512 else {
15513 return;
15514 };
15515
15516 let new_inline_diagnostics = cx
15517 .background_spawn(async move {
15518 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15519 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15520 let message = diagnostic_entry
15521 .diagnostic
15522 .message
15523 .split_once('\n')
15524 .map(|(line, _)| line)
15525 .map(SharedString::new)
15526 .unwrap_or_else(|| {
15527 SharedString::from(diagnostic_entry.diagnostic.message)
15528 });
15529 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15530 let (Ok(i) | Err(i)) = inline_diagnostics
15531 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15532 inline_diagnostics.insert(
15533 i,
15534 (
15535 start_anchor,
15536 InlineDiagnostic {
15537 message,
15538 group_id: diagnostic_entry.diagnostic.group_id,
15539 start: diagnostic_entry.range.start.to_point(&snapshot),
15540 is_primary: diagnostic_entry.diagnostic.is_primary,
15541 severity: diagnostic_entry.diagnostic.severity,
15542 },
15543 ),
15544 );
15545 }
15546 inline_diagnostics
15547 })
15548 .await;
15549
15550 editor
15551 .update(cx, |editor, cx| {
15552 editor.inline_diagnostics = new_inline_diagnostics;
15553 cx.notify();
15554 })
15555 .ok();
15556 });
15557 }
15558
15559 pub fn set_selections_from_remote(
15560 &mut self,
15561 selections: Vec<Selection<Anchor>>,
15562 pending_selection: Option<Selection<Anchor>>,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 let old_cursor_position = self.selections.newest_anchor().head();
15567 self.selections.change_with(cx, |s| {
15568 s.select_anchors(selections);
15569 if let Some(pending_selection) = pending_selection {
15570 s.set_pending(pending_selection, SelectMode::Character);
15571 } else {
15572 s.clear_pending();
15573 }
15574 });
15575 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15576 }
15577
15578 fn push_to_selection_history(&mut self) {
15579 self.selection_history.push(SelectionHistoryEntry {
15580 selections: self.selections.disjoint_anchors(),
15581 select_next_state: self.select_next_state.clone(),
15582 select_prev_state: self.select_prev_state.clone(),
15583 add_selections_state: self.add_selections_state.clone(),
15584 });
15585 }
15586
15587 pub fn transact(
15588 &mut self,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15592 ) -> Option<TransactionId> {
15593 self.start_transaction_at(Instant::now(), window, cx);
15594 update(self, window, cx);
15595 self.end_transaction_at(Instant::now(), cx)
15596 }
15597
15598 pub fn start_transaction_at(
15599 &mut self,
15600 now: Instant,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) {
15604 self.end_selection(window, cx);
15605 if let Some(tx_id) = self
15606 .buffer
15607 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15608 {
15609 self.selection_history
15610 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15611 cx.emit(EditorEvent::TransactionBegun {
15612 transaction_id: tx_id,
15613 })
15614 }
15615 }
15616
15617 pub fn end_transaction_at(
15618 &mut self,
15619 now: Instant,
15620 cx: &mut Context<Self>,
15621 ) -> Option<TransactionId> {
15622 if let Some(transaction_id) = self
15623 .buffer
15624 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15625 {
15626 if let Some((_, end_selections)) =
15627 self.selection_history.transaction_mut(transaction_id)
15628 {
15629 *end_selections = Some(self.selections.disjoint_anchors());
15630 } else {
15631 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15632 }
15633
15634 cx.emit(EditorEvent::Edited { transaction_id });
15635 Some(transaction_id)
15636 } else {
15637 None
15638 }
15639 }
15640
15641 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15642 if self.selection_mark_mode {
15643 self.change_selections(None, window, cx, |s| {
15644 s.move_with(|_, sel| {
15645 sel.collapse_to(sel.head(), SelectionGoal::None);
15646 });
15647 })
15648 }
15649 self.selection_mark_mode = true;
15650 cx.notify();
15651 }
15652
15653 pub fn swap_selection_ends(
15654 &mut self,
15655 _: &actions::SwapSelectionEnds,
15656 window: &mut Window,
15657 cx: &mut Context<Self>,
15658 ) {
15659 self.change_selections(None, window, cx, |s| {
15660 s.move_with(|_, sel| {
15661 if sel.start != sel.end {
15662 sel.reversed = !sel.reversed
15663 }
15664 });
15665 });
15666 self.request_autoscroll(Autoscroll::newest(), cx);
15667 cx.notify();
15668 }
15669
15670 pub fn toggle_fold(
15671 &mut self,
15672 _: &actions::ToggleFold,
15673 window: &mut Window,
15674 cx: &mut Context<Self>,
15675 ) {
15676 if self.is_singleton(cx) {
15677 let selection = self.selections.newest::<Point>(cx);
15678
15679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15680 let range = if selection.is_empty() {
15681 let point = selection.head().to_display_point(&display_map);
15682 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15683 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15684 .to_point(&display_map);
15685 start..end
15686 } else {
15687 selection.range()
15688 };
15689 if display_map.folds_in_range(range).next().is_some() {
15690 self.unfold_lines(&Default::default(), window, cx)
15691 } else {
15692 self.fold(&Default::default(), window, cx)
15693 }
15694 } else {
15695 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15696 let buffer_ids: HashSet<_> = self
15697 .selections
15698 .disjoint_anchor_ranges()
15699 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15700 .collect();
15701
15702 let should_unfold = buffer_ids
15703 .iter()
15704 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15705
15706 for buffer_id in buffer_ids {
15707 if should_unfold {
15708 self.unfold_buffer(buffer_id, cx);
15709 } else {
15710 self.fold_buffer(buffer_id, cx);
15711 }
15712 }
15713 }
15714 }
15715
15716 pub fn toggle_fold_recursive(
15717 &mut self,
15718 _: &actions::ToggleFoldRecursive,
15719 window: &mut Window,
15720 cx: &mut Context<Self>,
15721 ) {
15722 let selection = self.selections.newest::<Point>(cx);
15723
15724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15725 let range = if selection.is_empty() {
15726 let point = selection.head().to_display_point(&display_map);
15727 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15728 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15729 .to_point(&display_map);
15730 start..end
15731 } else {
15732 selection.range()
15733 };
15734 if display_map.folds_in_range(range).next().is_some() {
15735 self.unfold_recursive(&Default::default(), window, cx)
15736 } else {
15737 self.fold_recursive(&Default::default(), window, cx)
15738 }
15739 }
15740
15741 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15742 if self.is_singleton(cx) {
15743 let mut to_fold = Vec::new();
15744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15745 let selections = self.selections.all_adjusted(cx);
15746
15747 for selection in selections {
15748 let range = selection.range().sorted();
15749 let buffer_start_row = range.start.row;
15750
15751 if range.start.row != range.end.row {
15752 let mut found = false;
15753 let mut row = range.start.row;
15754 while row <= range.end.row {
15755 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15756 {
15757 found = true;
15758 row = crease.range().end.row + 1;
15759 to_fold.push(crease);
15760 } else {
15761 row += 1
15762 }
15763 }
15764 if found {
15765 continue;
15766 }
15767 }
15768
15769 for row in (0..=range.start.row).rev() {
15770 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15771 if crease.range().end.row >= buffer_start_row {
15772 to_fold.push(crease);
15773 if row <= range.start.row {
15774 break;
15775 }
15776 }
15777 }
15778 }
15779 }
15780
15781 self.fold_creases(to_fold, true, window, cx);
15782 } else {
15783 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15784 let buffer_ids = self
15785 .selections
15786 .disjoint_anchor_ranges()
15787 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15788 .collect::<HashSet<_>>();
15789 for buffer_id in buffer_ids {
15790 self.fold_buffer(buffer_id, cx);
15791 }
15792 }
15793 }
15794
15795 fn fold_at_level(
15796 &mut self,
15797 fold_at: &FoldAtLevel,
15798 window: &mut Window,
15799 cx: &mut Context<Self>,
15800 ) {
15801 if !self.buffer.read(cx).is_singleton() {
15802 return;
15803 }
15804
15805 let fold_at_level = fold_at.0;
15806 let snapshot = self.buffer.read(cx).snapshot(cx);
15807 let mut to_fold = Vec::new();
15808 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15809
15810 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15811 while start_row < end_row {
15812 match self
15813 .snapshot(window, cx)
15814 .crease_for_buffer_row(MultiBufferRow(start_row))
15815 {
15816 Some(crease) => {
15817 let nested_start_row = crease.range().start.row + 1;
15818 let nested_end_row = crease.range().end.row;
15819
15820 if current_level < fold_at_level {
15821 stack.push((nested_start_row, nested_end_row, current_level + 1));
15822 } else if current_level == fold_at_level {
15823 to_fold.push(crease);
15824 }
15825
15826 start_row = nested_end_row + 1;
15827 }
15828 None => start_row += 1,
15829 }
15830 }
15831 }
15832
15833 self.fold_creases(to_fold, true, window, cx);
15834 }
15835
15836 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15837 if self.buffer.read(cx).is_singleton() {
15838 let mut fold_ranges = Vec::new();
15839 let snapshot = self.buffer.read(cx).snapshot(cx);
15840
15841 for row in 0..snapshot.max_row().0 {
15842 if let Some(foldable_range) = self
15843 .snapshot(window, cx)
15844 .crease_for_buffer_row(MultiBufferRow(row))
15845 {
15846 fold_ranges.push(foldable_range);
15847 }
15848 }
15849
15850 self.fold_creases(fold_ranges, true, window, cx);
15851 } else {
15852 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15853 editor
15854 .update_in(cx, |editor, _, cx| {
15855 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15856 editor.fold_buffer(buffer_id, cx);
15857 }
15858 })
15859 .ok();
15860 });
15861 }
15862 }
15863
15864 pub fn fold_function_bodies(
15865 &mut self,
15866 _: &actions::FoldFunctionBodies,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 let snapshot = self.buffer.read(cx).snapshot(cx);
15871
15872 let ranges = snapshot
15873 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15874 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15875 .collect::<Vec<_>>();
15876
15877 let creases = ranges
15878 .into_iter()
15879 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15880 .collect();
15881
15882 self.fold_creases(creases, true, window, cx);
15883 }
15884
15885 pub fn fold_recursive(
15886 &mut self,
15887 _: &actions::FoldRecursive,
15888 window: &mut Window,
15889 cx: &mut Context<Self>,
15890 ) {
15891 let mut to_fold = Vec::new();
15892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15893 let selections = self.selections.all_adjusted(cx);
15894
15895 for selection in selections {
15896 let range = selection.range().sorted();
15897 let buffer_start_row = range.start.row;
15898
15899 if range.start.row != range.end.row {
15900 let mut found = false;
15901 for row in range.start.row..=range.end.row {
15902 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15903 found = true;
15904 to_fold.push(crease);
15905 }
15906 }
15907 if found {
15908 continue;
15909 }
15910 }
15911
15912 for row in (0..=range.start.row).rev() {
15913 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15914 if crease.range().end.row >= buffer_start_row {
15915 to_fold.push(crease);
15916 } else {
15917 break;
15918 }
15919 }
15920 }
15921 }
15922
15923 self.fold_creases(to_fold, true, window, cx);
15924 }
15925
15926 pub fn fold_at(
15927 &mut self,
15928 buffer_row: MultiBufferRow,
15929 window: &mut Window,
15930 cx: &mut Context<Self>,
15931 ) {
15932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15933
15934 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15935 let autoscroll = self
15936 .selections
15937 .all::<Point>(cx)
15938 .iter()
15939 .any(|selection| crease.range().overlaps(&selection.range()));
15940
15941 self.fold_creases(vec![crease], autoscroll, window, cx);
15942 }
15943 }
15944
15945 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15946 if self.is_singleton(cx) {
15947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15948 let buffer = &display_map.buffer_snapshot;
15949 let selections = self.selections.all::<Point>(cx);
15950 let ranges = selections
15951 .iter()
15952 .map(|s| {
15953 let range = s.display_range(&display_map).sorted();
15954 let mut start = range.start.to_point(&display_map);
15955 let mut end = range.end.to_point(&display_map);
15956 start.column = 0;
15957 end.column = buffer.line_len(MultiBufferRow(end.row));
15958 start..end
15959 })
15960 .collect::<Vec<_>>();
15961
15962 self.unfold_ranges(&ranges, true, true, cx);
15963 } else {
15964 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15965 let buffer_ids = self
15966 .selections
15967 .disjoint_anchor_ranges()
15968 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15969 .collect::<HashSet<_>>();
15970 for buffer_id in buffer_ids {
15971 self.unfold_buffer(buffer_id, cx);
15972 }
15973 }
15974 }
15975
15976 pub fn unfold_recursive(
15977 &mut self,
15978 _: &UnfoldRecursive,
15979 _window: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) {
15982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15983 let selections = self.selections.all::<Point>(cx);
15984 let ranges = selections
15985 .iter()
15986 .map(|s| {
15987 let mut range = s.display_range(&display_map).sorted();
15988 *range.start.column_mut() = 0;
15989 *range.end.column_mut() = display_map.line_len(range.end.row());
15990 let start = range.start.to_point(&display_map);
15991 let end = range.end.to_point(&display_map);
15992 start..end
15993 })
15994 .collect::<Vec<_>>();
15995
15996 self.unfold_ranges(&ranges, true, true, cx);
15997 }
15998
15999 pub fn unfold_at(
16000 &mut self,
16001 buffer_row: MultiBufferRow,
16002 _window: &mut Window,
16003 cx: &mut Context<Self>,
16004 ) {
16005 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16006
16007 let intersection_range = Point::new(buffer_row.0, 0)
16008 ..Point::new(
16009 buffer_row.0,
16010 display_map.buffer_snapshot.line_len(buffer_row),
16011 );
16012
16013 let autoscroll = self
16014 .selections
16015 .all::<Point>(cx)
16016 .iter()
16017 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16018
16019 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16020 }
16021
16022 pub fn unfold_all(
16023 &mut self,
16024 _: &actions::UnfoldAll,
16025 _window: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) {
16028 if self.buffer.read(cx).is_singleton() {
16029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16030 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16031 } else {
16032 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16033 editor
16034 .update(cx, |editor, cx| {
16035 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16036 editor.unfold_buffer(buffer_id, cx);
16037 }
16038 })
16039 .ok();
16040 });
16041 }
16042 }
16043
16044 pub fn fold_selected_ranges(
16045 &mut self,
16046 _: &FoldSelectedRanges,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) {
16050 let selections = self.selections.all_adjusted(cx);
16051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16052 let ranges = selections
16053 .into_iter()
16054 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16055 .collect::<Vec<_>>();
16056 self.fold_creases(ranges, true, window, cx);
16057 }
16058
16059 pub fn fold_ranges<T: ToOffset + Clone>(
16060 &mut self,
16061 ranges: Vec<Range<T>>,
16062 auto_scroll: bool,
16063 window: &mut Window,
16064 cx: &mut Context<Self>,
16065 ) {
16066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16067 let ranges = ranges
16068 .into_iter()
16069 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16070 .collect::<Vec<_>>();
16071 self.fold_creases(ranges, auto_scroll, window, cx);
16072 }
16073
16074 pub fn fold_creases<T: ToOffset + Clone>(
16075 &mut self,
16076 creases: Vec<Crease<T>>,
16077 auto_scroll: bool,
16078 _window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 if creases.is_empty() {
16082 return;
16083 }
16084
16085 let mut buffers_affected = HashSet::default();
16086 let multi_buffer = self.buffer().read(cx);
16087 for crease in &creases {
16088 if let Some((_, buffer, _)) =
16089 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16090 {
16091 buffers_affected.insert(buffer.read(cx).remote_id());
16092 };
16093 }
16094
16095 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16096
16097 if auto_scroll {
16098 self.request_autoscroll(Autoscroll::fit(), cx);
16099 }
16100
16101 cx.notify();
16102
16103 self.scrollbar_marker_state.dirty = true;
16104 self.folds_did_change(cx);
16105 }
16106
16107 /// Removes any folds whose ranges intersect any of the given ranges.
16108 pub fn unfold_ranges<T: ToOffset + Clone>(
16109 &mut self,
16110 ranges: &[Range<T>],
16111 inclusive: bool,
16112 auto_scroll: bool,
16113 cx: &mut Context<Self>,
16114 ) {
16115 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16116 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16117 });
16118 self.folds_did_change(cx);
16119 }
16120
16121 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16122 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16123 return;
16124 }
16125 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16126 self.display_map.update(cx, |display_map, cx| {
16127 display_map.fold_buffers([buffer_id], cx)
16128 });
16129 cx.emit(EditorEvent::BufferFoldToggled {
16130 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16131 folded: true,
16132 });
16133 cx.notify();
16134 }
16135
16136 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16137 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16138 return;
16139 }
16140 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16141 self.display_map.update(cx, |display_map, cx| {
16142 display_map.unfold_buffers([buffer_id], cx);
16143 });
16144 cx.emit(EditorEvent::BufferFoldToggled {
16145 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16146 folded: false,
16147 });
16148 cx.notify();
16149 }
16150
16151 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16152 self.display_map.read(cx).is_buffer_folded(buffer)
16153 }
16154
16155 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16156 self.display_map.read(cx).folded_buffers()
16157 }
16158
16159 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16160 self.display_map.update(cx, |display_map, cx| {
16161 display_map.disable_header_for_buffer(buffer_id, cx);
16162 });
16163 cx.notify();
16164 }
16165
16166 /// Removes any folds with the given ranges.
16167 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16168 &mut self,
16169 ranges: &[Range<T>],
16170 type_id: TypeId,
16171 auto_scroll: bool,
16172 cx: &mut Context<Self>,
16173 ) {
16174 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16175 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16176 });
16177 self.folds_did_change(cx);
16178 }
16179
16180 fn remove_folds_with<T: ToOffset + Clone>(
16181 &mut self,
16182 ranges: &[Range<T>],
16183 auto_scroll: bool,
16184 cx: &mut Context<Self>,
16185 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16186 ) {
16187 if ranges.is_empty() {
16188 return;
16189 }
16190
16191 let mut buffers_affected = HashSet::default();
16192 let multi_buffer = self.buffer().read(cx);
16193 for range in ranges {
16194 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16195 buffers_affected.insert(buffer.read(cx).remote_id());
16196 };
16197 }
16198
16199 self.display_map.update(cx, update);
16200
16201 if auto_scroll {
16202 self.request_autoscroll(Autoscroll::fit(), cx);
16203 }
16204
16205 cx.notify();
16206 self.scrollbar_marker_state.dirty = true;
16207 self.active_indent_guides_state.dirty = true;
16208 }
16209
16210 pub fn update_fold_widths(
16211 &mut self,
16212 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16213 cx: &mut Context<Self>,
16214 ) -> bool {
16215 self.display_map
16216 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16217 }
16218
16219 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16220 self.display_map.read(cx).fold_placeholder.clone()
16221 }
16222
16223 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16224 self.buffer.update(cx, |buffer, cx| {
16225 buffer.set_all_diff_hunks_expanded(cx);
16226 });
16227 }
16228
16229 pub fn expand_all_diff_hunks(
16230 &mut self,
16231 _: &ExpandAllDiffHunks,
16232 _window: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) {
16235 self.buffer.update(cx, |buffer, cx| {
16236 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16237 });
16238 }
16239
16240 pub fn toggle_selected_diff_hunks(
16241 &mut self,
16242 _: &ToggleSelectedDiffHunks,
16243 _window: &mut Window,
16244 cx: &mut Context<Self>,
16245 ) {
16246 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16247 self.toggle_diff_hunks_in_ranges(ranges, cx);
16248 }
16249
16250 pub fn diff_hunks_in_ranges<'a>(
16251 &'a self,
16252 ranges: &'a [Range<Anchor>],
16253 buffer: &'a MultiBufferSnapshot,
16254 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16255 ranges.iter().flat_map(move |range| {
16256 let end_excerpt_id = range.end.excerpt_id;
16257 let range = range.to_point(buffer);
16258 let mut peek_end = range.end;
16259 if range.end.row < buffer.max_row().0 {
16260 peek_end = Point::new(range.end.row + 1, 0);
16261 }
16262 buffer
16263 .diff_hunks_in_range(range.start..peek_end)
16264 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16265 })
16266 }
16267
16268 pub fn has_stageable_diff_hunks_in_ranges(
16269 &self,
16270 ranges: &[Range<Anchor>],
16271 snapshot: &MultiBufferSnapshot,
16272 ) -> bool {
16273 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16274 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16275 }
16276
16277 pub fn toggle_staged_selected_diff_hunks(
16278 &mut self,
16279 _: &::git::ToggleStaged,
16280 _: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 let snapshot = self.buffer.read(cx).snapshot(cx);
16284 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16285 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16286 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16287 }
16288
16289 pub fn set_render_diff_hunk_controls(
16290 &mut self,
16291 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16292 cx: &mut Context<Self>,
16293 ) {
16294 self.render_diff_hunk_controls = render_diff_hunk_controls;
16295 cx.notify();
16296 }
16297
16298 pub fn stage_and_next(
16299 &mut self,
16300 _: &::git::StageAndNext,
16301 window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 self.do_stage_or_unstage_and_next(true, window, cx);
16305 }
16306
16307 pub fn unstage_and_next(
16308 &mut self,
16309 _: &::git::UnstageAndNext,
16310 window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) {
16313 self.do_stage_or_unstage_and_next(false, window, cx);
16314 }
16315
16316 pub fn stage_or_unstage_diff_hunks(
16317 &mut self,
16318 stage: bool,
16319 ranges: Vec<Range<Anchor>>,
16320 cx: &mut Context<Self>,
16321 ) {
16322 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16323 cx.spawn(async move |this, cx| {
16324 task.await?;
16325 this.update(cx, |this, cx| {
16326 let snapshot = this.buffer.read(cx).snapshot(cx);
16327 let chunk_by = this
16328 .diff_hunks_in_ranges(&ranges, &snapshot)
16329 .chunk_by(|hunk| hunk.buffer_id);
16330 for (buffer_id, hunks) in &chunk_by {
16331 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16332 }
16333 })
16334 })
16335 .detach_and_log_err(cx);
16336 }
16337
16338 fn save_buffers_for_ranges_if_needed(
16339 &mut self,
16340 ranges: &[Range<Anchor>],
16341 cx: &mut Context<Editor>,
16342 ) -> Task<Result<()>> {
16343 let multibuffer = self.buffer.read(cx);
16344 let snapshot = multibuffer.read(cx);
16345 let buffer_ids: HashSet<_> = ranges
16346 .iter()
16347 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16348 .collect();
16349 drop(snapshot);
16350
16351 let mut buffers = HashSet::default();
16352 for buffer_id in buffer_ids {
16353 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16354 let buffer = buffer_entity.read(cx);
16355 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16356 {
16357 buffers.insert(buffer_entity);
16358 }
16359 }
16360 }
16361
16362 if let Some(project) = &self.project {
16363 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16364 } else {
16365 Task::ready(Ok(()))
16366 }
16367 }
16368
16369 fn do_stage_or_unstage_and_next(
16370 &mut self,
16371 stage: bool,
16372 window: &mut Window,
16373 cx: &mut Context<Self>,
16374 ) {
16375 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16376
16377 if ranges.iter().any(|range| range.start != range.end) {
16378 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16379 return;
16380 }
16381
16382 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16383 let snapshot = self.snapshot(window, cx);
16384 let position = self.selections.newest::<Point>(cx).head();
16385 let mut row = snapshot
16386 .buffer_snapshot
16387 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16388 .find(|hunk| hunk.row_range.start.0 > position.row)
16389 .map(|hunk| hunk.row_range.start);
16390
16391 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16392 // Outside of the project diff editor, wrap around to the beginning.
16393 if !all_diff_hunks_expanded {
16394 row = row.or_else(|| {
16395 snapshot
16396 .buffer_snapshot
16397 .diff_hunks_in_range(Point::zero()..position)
16398 .find(|hunk| hunk.row_range.end.0 < position.row)
16399 .map(|hunk| hunk.row_range.start)
16400 });
16401 }
16402
16403 if let Some(row) = row {
16404 let destination = Point::new(row.0, 0);
16405 let autoscroll = Autoscroll::center();
16406
16407 self.unfold_ranges(&[destination..destination], false, false, cx);
16408 self.change_selections(Some(autoscroll), window, cx, |s| {
16409 s.select_ranges([destination..destination]);
16410 });
16411 }
16412 }
16413
16414 fn do_stage_or_unstage(
16415 &self,
16416 stage: bool,
16417 buffer_id: BufferId,
16418 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16419 cx: &mut App,
16420 ) -> Option<()> {
16421 let project = self.project.as_ref()?;
16422 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16423 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16424 let buffer_snapshot = buffer.read(cx).snapshot();
16425 let file_exists = buffer_snapshot
16426 .file()
16427 .is_some_and(|file| file.disk_state().exists());
16428 diff.update(cx, |diff, cx| {
16429 diff.stage_or_unstage_hunks(
16430 stage,
16431 &hunks
16432 .map(|hunk| buffer_diff::DiffHunk {
16433 buffer_range: hunk.buffer_range,
16434 diff_base_byte_range: hunk.diff_base_byte_range,
16435 secondary_status: hunk.secondary_status,
16436 range: Point::zero()..Point::zero(), // unused
16437 })
16438 .collect::<Vec<_>>(),
16439 &buffer_snapshot,
16440 file_exists,
16441 cx,
16442 )
16443 });
16444 None
16445 }
16446
16447 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16448 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16449 self.buffer
16450 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16451 }
16452
16453 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16454 self.buffer.update(cx, |buffer, cx| {
16455 let ranges = vec![Anchor::min()..Anchor::max()];
16456 if !buffer.all_diff_hunks_expanded()
16457 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16458 {
16459 buffer.collapse_diff_hunks(ranges, cx);
16460 true
16461 } else {
16462 false
16463 }
16464 })
16465 }
16466
16467 fn toggle_diff_hunks_in_ranges(
16468 &mut self,
16469 ranges: Vec<Range<Anchor>>,
16470 cx: &mut Context<Editor>,
16471 ) {
16472 self.buffer.update(cx, |buffer, cx| {
16473 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16474 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16475 })
16476 }
16477
16478 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16479 self.buffer.update(cx, |buffer, cx| {
16480 let snapshot = buffer.snapshot(cx);
16481 let excerpt_id = range.end.excerpt_id;
16482 let point_range = range.to_point(&snapshot);
16483 let expand = !buffer.single_hunk_is_expanded(range, cx);
16484 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16485 })
16486 }
16487
16488 pub(crate) fn apply_all_diff_hunks(
16489 &mut self,
16490 _: &ApplyAllDiffHunks,
16491 window: &mut Window,
16492 cx: &mut Context<Self>,
16493 ) {
16494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16495
16496 let buffers = self.buffer.read(cx).all_buffers();
16497 for branch_buffer in buffers {
16498 branch_buffer.update(cx, |branch_buffer, cx| {
16499 branch_buffer.merge_into_base(Vec::new(), cx);
16500 });
16501 }
16502
16503 if let Some(project) = self.project.clone() {
16504 self.save(true, project, window, cx).detach_and_log_err(cx);
16505 }
16506 }
16507
16508 pub(crate) fn apply_selected_diff_hunks(
16509 &mut self,
16510 _: &ApplyDiffHunk,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) {
16514 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16515 let snapshot = self.snapshot(window, cx);
16516 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16517 let mut ranges_by_buffer = HashMap::default();
16518 self.transact(window, cx, |editor, _window, cx| {
16519 for hunk in hunks {
16520 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16521 ranges_by_buffer
16522 .entry(buffer.clone())
16523 .or_insert_with(Vec::new)
16524 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16525 }
16526 }
16527
16528 for (buffer, ranges) in ranges_by_buffer {
16529 buffer.update(cx, |buffer, cx| {
16530 buffer.merge_into_base(ranges, cx);
16531 });
16532 }
16533 });
16534
16535 if let Some(project) = self.project.clone() {
16536 self.save(true, project, window, cx).detach_and_log_err(cx);
16537 }
16538 }
16539
16540 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16541 if hovered != self.gutter_hovered {
16542 self.gutter_hovered = hovered;
16543 cx.notify();
16544 }
16545 }
16546
16547 pub fn insert_blocks(
16548 &mut self,
16549 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16550 autoscroll: Option<Autoscroll>,
16551 cx: &mut Context<Self>,
16552 ) -> Vec<CustomBlockId> {
16553 let blocks = self
16554 .display_map
16555 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16556 if let Some(autoscroll) = autoscroll {
16557 self.request_autoscroll(autoscroll, cx);
16558 }
16559 cx.notify();
16560 blocks
16561 }
16562
16563 pub fn resize_blocks(
16564 &mut self,
16565 heights: HashMap<CustomBlockId, u32>,
16566 autoscroll: Option<Autoscroll>,
16567 cx: &mut Context<Self>,
16568 ) {
16569 self.display_map
16570 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16571 if let Some(autoscroll) = autoscroll {
16572 self.request_autoscroll(autoscroll, cx);
16573 }
16574 cx.notify();
16575 }
16576
16577 pub fn replace_blocks(
16578 &mut self,
16579 renderers: HashMap<CustomBlockId, RenderBlock>,
16580 autoscroll: Option<Autoscroll>,
16581 cx: &mut Context<Self>,
16582 ) {
16583 self.display_map
16584 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16585 if let Some(autoscroll) = autoscroll {
16586 self.request_autoscroll(autoscroll, cx);
16587 }
16588 cx.notify();
16589 }
16590
16591 pub fn remove_blocks(
16592 &mut self,
16593 block_ids: HashSet<CustomBlockId>,
16594 autoscroll: Option<Autoscroll>,
16595 cx: &mut Context<Self>,
16596 ) {
16597 self.display_map.update(cx, |display_map, cx| {
16598 display_map.remove_blocks(block_ids, cx)
16599 });
16600 if let Some(autoscroll) = autoscroll {
16601 self.request_autoscroll(autoscroll, cx);
16602 }
16603 cx.notify();
16604 }
16605
16606 pub fn row_for_block(
16607 &self,
16608 block_id: CustomBlockId,
16609 cx: &mut Context<Self>,
16610 ) -> Option<DisplayRow> {
16611 self.display_map
16612 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16613 }
16614
16615 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16616 self.focused_block = Some(focused_block);
16617 }
16618
16619 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16620 self.focused_block.take()
16621 }
16622
16623 pub fn insert_creases(
16624 &mut self,
16625 creases: impl IntoIterator<Item = Crease<Anchor>>,
16626 cx: &mut Context<Self>,
16627 ) -> Vec<CreaseId> {
16628 self.display_map
16629 .update(cx, |map, cx| map.insert_creases(creases, cx))
16630 }
16631
16632 pub fn remove_creases(
16633 &mut self,
16634 ids: impl IntoIterator<Item = CreaseId>,
16635 cx: &mut Context<Self>,
16636 ) -> Vec<(CreaseId, Range<Anchor>)> {
16637 self.display_map
16638 .update(cx, |map, cx| map.remove_creases(ids, cx))
16639 }
16640
16641 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16642 self.display_map
16643 .update(cx, |map, cx| map.snapshot(cx))
16644 .longest_row()
16645 }
16646
16647 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16648 self.display_map
16649 .update(cx, |map, cx| map.snapshot(cx))
16650 .max_point()
16651 }
16652
16653 pub fn text(&self, cx: &App) -> String {
16654 self.buffer.read(cx).read(cx).text()
16655 }
16656
16657 pub fn is_empty(&self, cx: &App) -> bool {
16658 self.buffer.read(cx).read(cx).is_empty()
16659 }
16660
16661 pub fn text_option(&self, cx: &App) -> Option<String> {
16662 let text = self.text(cx);
16663 let text = text.trim();
16664
16665 if text.is_empty() {
16666 return None;
16667 }
16668
16669 Some(text.to_string())
16670 }
16671
16672 pub fn set_text(
16673 &mut self,
16674 text: impl Into<Arc<str>>,
16675 window: &mut Window,
16676 cx: &mut Context<Self>,
16677 ) {
16678 self.transact(window, cx, |this, _, cx| {
16679 this.buffer
16680 .read(cx)
16681 .as_singleton()
16682 .expect("you can only call set_text on editors for singleton buffers")
16683 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16684 });
16685 }
16686
16687 pub fn display_text(&self, cx: &mut App) -> String {
16688 self.display_map
16689 .update(cx, |map, cx| map.snapshot(cx))
16690 .text()
16691 }
16692
16693 fn create_minimap(
16694 &self,
16695 minimap_settings: MinimapSettings,
16696 window: &mut Window,
16697 cx: &mut Context<Self>,
16698 ) -> Option<Entity<Self>> {
16699 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16700 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16701 }
16702
16703 fn initialize_new_minimap(
16704 &self,
16705 minimap_settings: MinimapSettings,
16706 window: &mut Window,
16707 cx: &mut Context<Self>,
16708 ) -> Entity<Self> {
16709 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16710
16711 let mut minimap = Editor::new_internal(
16712 EditorMode::Minimap {
16713 parent: cx.weak_entity(),
16714 },
16715 self.buffer.clone(),
16716 self.project.clone(),
16717 Some(self.display_map.clone()),
16718 window,
16719 cx,
16720 );
16721 minimap.scroll_manager.clone_state(&self.scroll_manager);
16722 minimap.set_text_style_refinement(TextStyleRefinement {
16723 font_size: Some(MINIMAP_FONT_SIZE),
16724 font_weight: Some(MINIMAP_FONT_WEIGHT),
16725 ..Default::default()
16726 });
16727 minimap.update_minimap_configuration(minimap_settings, cx);
16728 cx.new(|_| minimap)
16729 }
16730
16731 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16732 let current_line_highlight = minimap_settings
16733 .current_line_highlight
16734 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16735 self.set_current_line_highlight(Some(current_line_highlight));
16736 }
16737
16738 pub fn minimap(&self) -> Option<&Entity<Self>> {
16739 self.minimap
16740 .as_ref()
16741 .filter(|_| self.minimap_visibility.visible())
16742 }
16743
16744 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16745 let mut wrap_guides = smallvec::smallvec![];
16746
16747 if self.show_wrap_guides == Some(false) {
16748 return wrap_guides;
16749 }
16750
16751 let settings = self.buffer.read(cx).language_settings(cx);
16752 if settings.show_wrap_guides {
16753 match self.soft_wrap_mode(cx) {
16754 SoftWrap::Column(soft_wrap) => {
16755 wrap_guides.push((soft_wrap as usize, true));
16756 }
16757 SoftWrap::Bounded(soft_wrap) => {
16758 wrap_guides.push((soft_wrap as usize, true));
16759 }
16760 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16761 }
16762 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16763 }
16764
16765 wrap_guides
16766 }
16767
16768 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16769 let settings = self.buffer.read(cx).language_settings(cx);
16770 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16771 match mode {
16772 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16773 SoftWrap::None
16774 }
16775 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16776 language_settings::SoftWrap::PreferredLineLength => {
16777 SoftWrap::Column(settings.preferred_line_length)
16778 }
16779 language_settings::SoftWrap::Bounded => {
16780 SoftWrap::Bounded(settings.preferred_line_length)
16781 }
16782 }
16783 }
16784
16785 pub fn set_soft_wrap_mode(
16786 &mut self,
16787 mode: language_settings::SoftWrap,
16788
16789 cx: &mut Context<Self>,
16790 ) {
16791 self.soft_wrap_mode_override = Some(mode);
16792 cx.notify();
16793 }
16794
16795 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16796 self.hard_wrap = hard_wrap;
16797 cx.notify();
16798 }
16799
16800 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16801 self.text_style_refinement = Some(style);
16802 }
16803
16804 /// called by the Element so we know what style we were most recently rendered with.
16805 pub(crate) fn set_style(
16806 &mut self,
16807 style: EditorStyle,
16808 window: &mut Window,
16809 cx: &mut Context<Self>,
16810 ) {
16811 // We intentionally do not inform the display map about the minimap style
16812 // so that wrapping is not recalculated and stays consistent for the editor
16813 // and its linked minimap.
16814 if !self.mode.is_minimap() {
16815 let rem_size = window.rem_size();
16816 self.display_map.update(cx, |map, cx| {
16817 map.set_font(
16818 style.text.font(),
16819 style.text.font_size.to_pixels(rem_size),
16820 cx,
16821 )
16822 });
16823 }
16824 self.style = Some(style);
16825 }
16826
16827 pub fn style(&self) -> Option<&EditorStyle> {
16828 self.style.as_ref()
16829 }
16830
16831 // Called by the element. This method is not designed to be called outside of the editor
16832 // element's layout code because it does not notify when rewrapping is computed synchronously.
16833 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16834 self.display_map
16835 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16836 }
16837
16838 pub fn set_soft_wrap(&mut self) {
16839 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16840 }
16841
16842 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16843 if self.soft_wrap_mode_override.is_some() {
16844 self.soft_wrap_mode_override.take();
16845 } else {
16846 let soft_wrap = match self.soft_wrap_mode(cx) {
16847 SoftWrap::GitDiff => return,
16848 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16849 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16850 language_settings::SoftWrap::None
16851 }
16852 };
16853 self.soft_wrap_mode_override = Some(soft_wrap);
16854 }
16855 cx.notify();
16856 }
16857
16858 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16859 let Some(workspace) = self.workspace() else {
16860 return;
16861 };
16862 let fs = workspace.read(cx).app_state().fs.clone();
16863 let current_show = TabBarSettings::get_global(cx).show;
16864 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16865 setting.show = Some(!current_show);
16866 });
16867 }
16868
16869 pub fn toggle_indent_guides(
16870 &mut self,
16871 _: &ToggleIndentGuides,
16872 _: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) {
16875 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16876 self.buffer
16877 .read(cx)
16878 .language_settings(cx)
16879 .indent_guides
16880 .enabled
16881 });
16882 self.show_indent_guides = Some(!currently_enabled);
16883 cx.notify();
16884 }
16885
16886 fn should_show_indent_guides(&self) -> Option<bool> {
16887 self.show_indent_guides
16888 }
16889
16890 pub fn toggle_line_numbers(
16891 &mut self,
16892 _: &ToggleLineNumbers,
16893 _: &mut Window,
16894 cx: &mut Context<Self>,
16895 ) {
16896 let mut editor_settings = EditorSettings::get_global(cx).clone();
16897 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16898 EditorSettings::override_global(editor_settings, cx);
16899 }
16900
16901 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16902 if let Some(show_line_numbers) = self.show_line_numbers {
16903 return show_line_numbers;
16904 }
16905 EditorSettings::get_global(cx).gutter.line_numbers
16906 }
16907
16908 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16909 self.use_relative_line_numbers
16910 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16911 }
16912
16913 pub fn toggle_relative_line_numbers(
16914 &mut self,
16915 _: &ToggleRelativeLineNumbers,
16916 _: &mut Window,
16917 cx: &mut Context<Self>,
16918 ) {
16919 let is_relative = self.should_use_relative_line_numbers(cx);
16920 self.set_relative_line_number(Some(!is_relative), cx)
16921 }
16922
16923 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16924 self.use_relative_line_numbers = is_relative;
16925 cx.notify();
16926 }
16927
16928 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16929 self.show_gutter = show_gutter;
16930 cx.notify();
16931 }
16932
16933 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16934 self.show_scrollbars = show_scrollbars;
16935 cx.notify();
16936 }
16937
16938 pub fn set_minimap_visibility(
16939 &mut self,
16940 minimap_visibility: MinimapVisibility,
16941 window: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) {
16944 if self.minimap_visibility != minimap_visibility {
16945 if minimap_visibility.visible() && self.minimap.is_none() {
16946 let minimap_settings = EditorSettings::get_global(cx).minimap;
16947 self.minimap =
16948 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16949 }
16950 self.minimap_visibility = minimap_visibility;
16951 cx.notify();
16952 }
16953 }
16954
16955 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16956 self.set_show_scrollbars(false, cx);
16957 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16958 }
16959
16960 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16961 self.show_line_numbers = Some(show_line_numbers);
16962 cx.notify();
16963 }
16964
16965 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16966 self.disable_expand_excerpt_buttons = true;
16967 cx.notify();
16968 }
16969
16970 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16971 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16972 cx.notify();
16973 }
16974
16975 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16976 self.show_code_actions = Some(show_code_actions);
16977 cx.notify();
16978 }
16979
16980 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16981 self.show_runnables = Some(show_runnables);
16982 cx.notify();
16983 }
16984
16985 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16986 self.show_breakpoints = Some(show_breakpoints);
16987 cx.notify();
16988 }
16989
16990 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16991 if self.display_map.read(cx).masked != masked {
16992 self.display_map.update(cx, |map, _| map.masked = masked);
16993 }
16994 cx.notify()
16995 }
16996
16997 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16998 self.show_wrap_guides = Some(show_wrap_guides);
16999 cx.notify();
17000 }
17001
17002 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17003 self.show_indent_guides = Some(show_indent_guides);
17004 cx.notify();
17005 }
17006
17007 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17008 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17009 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17010 if let Some(dir) = file.abs_path(cx).parent() {
17011 return Some(dir.to_owned());
17012 }
17013 }
17014
17015 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17016 return Some(project_path.path.to_path_buf());
17017 }
17018 }
17019
17020 None
17021 }
17022
17023 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17024 self.active_excerpt(cx)?
17025 .1
17026 .read(cx)
17027 .file()
17028 .and_then(|f| f.as_local())
17029 }
17030
17031 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17032 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17033 let buffer = buffer.read(cx);
17034 if let Some(project_path) = buffer.project_path(cx) {
17035 let project = self.project.as_ref()?.read(cx);
17036 project.absolute_path(&project_path, cx)
17037 } else {
17038 buffer
17039 .file()
17040 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17041 }
17042 })
17043 }
17044
17045 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17046 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17047 let project_path = buffer.read(cx).project_path(cx)?;
17048 let project = self.project.as_ref()?.read(cx);
17049 let entry = project.entry_for_path(&project_path, cx)?;
17050 let path = entry.path.to_path_buf();
17051 Some(path)
17052 })
17053 }
17054
17055 pub fn reveal_in_finder(
17056 &mut self,
17057 _: &RevealInFileManager,
17058 _window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 if let Some(target) = self.target_file(cx) {
17062 cx.reveal_path(&target.abs_path(cx));
17063 }
17064 }
17065
17066 pub fn copy_path(
17067 &mut self,
17068 _: &zed_actions::workspace::CopyPath,
17069 _window: &mut Window,
17070 cx: &mut Context<Self>,
17071 ) {
17072 if let Some(path) = self.target_file_abs_path(cx) {
17073 if let Some(path) = path.to_str() {
17074 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17075 }
17076 }
17077 }
17078
17079 pub fn copy_relative_path(
17080 &mut self,
17081 _: &zed_actions::workspace::CopyRelativePath,
17082 _window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 if let Some(path) = self.target_file_path(cx) {
17086 if let Some(path) = path.to_str() {
17087 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17088 }
17089 }
17090 }
17091
17092 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17093 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17094 buffer.read(cx).project_path(cx)
17095 } else {
17096 None
17097 }
17098 }
17099
17100 // Returns true if the editor handled a go-to-line request
17101 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17102 maybe!({
17103 let breakpoint_store = self.breakpoint_store.as_ref()?;
17104
17105 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17106 else {
17107 self.clear_row_highlights::<ActiveDebugLine>();
17108 return None;
17109 };
17110
17111 let position = active_stack_frame.position;
17112 let buffer_id = position.buffer_id?;
17113 let snapshot = self
17114 .project
17115 .as_ref()?
17116 .read(cx)
17117 .buffer_for_id(buffer_id, cx)?
17118 .read(cx)
17119 .snapshot();
17120
17121 let mut handled = false;
17122 for (id, ExcerptRange { context, .. }) in
17123 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17124 {
17125 if context.start.cmp(&position, &snapshot).is_ge()
17126 || context.end.cmp(&position, &snapshot).is_lt()
17127 {
17128 continue;
17129 }
17130 let snapshot = self.buffer.read(cx).snapshot(cx);
17131 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17132
17133 handled = true;
17134 self.clear_row_highlights::<ActiveDebugLine>();
17135
17136 self.go_to_line::<ActiveDebugLine>(
17137 multibuffer_anchor,
17138 Some(cx.theme().colors().editor_debugger_active_line_background),
17139 window,
17140 cx,
17141 );
17142
17143 cx.notify();
17144 }
17145
17146 handled.then_some(())
17147 })
17148 .is_some()
17149 }
17150
17151 pub fn copy_file_name_without_extension(
17152 &mut self,
17153 _: &CopyFileNameWithoutExtension,
17154 _: &mut Window,
17155 cx: &mut Context<Self>,
17156 ) {
17157 if let Some(file) = self.target_file(cx) {
17158 if let Some(file_stem) = file.path().file_stem() {
17159 if let Some(name) = file_stem.to_str() {
17160 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17161 }
17162 }
17163 }
17164 }
17165
17166 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17167 if let Some(file) = self.target_file(cx) {
17168 if let Some(file_name) = file.path().file_name() {
17169 if let Some(name) = file_name.to_str() {
17170 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17171 }
17172 }
17173 }
17174 }
17175
17176 pub fn toggle_git_blame(
17177 &mut self,
17178 _: &::git::Blame,
17179 window: &mut Window,
17180 cx: &mut Context<Self>,
17181 ) {
17182 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17183
17184 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17185 self.start_git_blame(true, window, cx);
17186 }
17187
17188 cx.notify();
17189 }
17190
17191 pub fn toggle_git_blame_inline(
17192 &mut self,
17193 _: &ToggleGitBlameInline,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) {
17197 self.toggle_git_blame_inline_internal(true, window, cx);
17198 cx.notify();
17199 }
17200
17201 pub fn open_git_blame_commit(
17202 &mut self,
17203 _: &OpenGitBlameCommit,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 ) {
17207 self.open_git_blame_commit_internal(window, cx);
17208 }
17209
17210 fn open_git_blame_commit_internal(
17211 &mut self,
17212 window: &mut Window,
17213 cx: &mut Context<Self>,
17214 ) -> Option<()> {
17215 let blame = self.blame.as_ref()?;
17216 let snapshot = self.snapshot(window, cx);
17217 let cursor = self.selections.newest::<Point>(cx).head();
17218 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17219 let blame_entry = blame
17220 .update(cx, |blame, cx| {
17221 blame
17222 .blame_for_rows(
17223 &[RowInfo {
17224 buffer_id: Some(buffer.remote_id()),
17225 buffer_row: Some(point.row),
17226 ..Default::default()
17227 }],
17228 cx,
17229 )
17230 .next()
17231 })
17232 .flatten()?;
17233 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17234 let repo = blame.read(cx).repository(cx)?;
17235 let workspace = self.workspace()?.downgrade();
17236 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17237 None
17238 }
17239
17240 pub fn git_blame_inline_enabled(&self) -> bool {
17241 self.git_blame_inline_enabled
17242 }
17243
17244 pub fn toggle_selection_menu(
17245 &mut self,
17246 _: &ToggleSelectionMenu,
17247 _: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) {
17250 self.show_selection_menu = self
17251 .show_selection_menu
17252 .map(|show_selections_menu| !show_selections_menu)
17253 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17254
17255 cx.notify();
17256 }
17257
17258 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17259 self.show_selection_menu
17260 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17261 }
17262
17263 fn start_git_blame(
17264 &mut self,
17265 user_triggered: bool,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) {
17269 if let Some(project) = self.project.as_ref() {
17270 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17271 return;
17272 };
17273
17274 if buffer.read(cx).file().is_none() {
17275 return;
17276 }
17277
17278 let focused = self.focus_handle(cx).contains_focused(window, cx);
17279
17280 let project = project.clone();
17281 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17282 self.blame_subscription =
17283 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17284 self.blame = Some(blame);
17285 }
17286 }
17287
17288 fn toggle_git_blame_inline_internal(
17289 &mut self,
17290 user_triggered: bool,
17291 window: &mut Window,
17292 cx: &mut Context<Self>,
17293 ) {
17294 if self.git_blame_inline_enabled {
17295 self.git_blame_inline_enabled = false;
17296 self.show_git_blame_inline = false;
17297 self.show_git_blame_inline_delay_task.take();
17298 } else {
17299 self.git_blame_inline_enabled = true;
17300 self.start_git_blame_inline(user_triggered, window, cx);
17301 }
17302
17303 cx.notify();
17304 }
17305
17306 fn start_git_blame_inline(
17307 &mut self,
17308 user_triggered: bool,
17309 window: &mut Window,
17310 cx: &mut Context<Self>,
17311 ) {
17312 self.start_git_blame(user_triggered, window, cx);
17313
17314 if ProjectSettings::get_global(cx)
17315 .git
17316 .inline_blame_delay()
17317 .is_some()
17318 {
17319 self.start_inline_blame_timer(window, cx);
17320 } else {
17321 self.show_git_blame_inline = true
17322 }
17323 }
17324
17325 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17326 self.blame.as_ref()
17327 }
17328
17329 pub fn show_git_blame_gutter(&self) -> bool {
17330 self.show_git_blame_gutter
17331 }
17332
17333 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17334 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17335 }
17336
17337 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17338 self.show_git_blame_inline
17339 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17340 && !self.newest_selection_head_on_empty_line(cx)
17341 && self.has_blame_entries(cx)
17342 }
17343
17344 fn has_blame_entries(&self, cx: &App) -> bool {
17345 self.blame()
17346 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17347 }
17348
17349 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17350 let cursor_anchor = self.selections.newest_anchor().head();
17351
17352 let snapshot = self.buffer.read(cx).snapshot(cx);
17353 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17354
17355 snapshot.line_len(buffer_row) == 0
17356 }
17357
17358 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17359 let buffer_and_selection = maybe!({
17360 let selection = self.selections.newest::<Point>(cx);
17361 let selection_range = selection.range();
17362
17363 let multi_buffer = self.buffer().read(cx);
17364 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17365 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17366
17367 let (buffer, range, _) = if selection.reversed {
17368 buffer_ranges.first()
17369 } else {
17370 buffer_ranges.last()
17371 }?;
17372
17373 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17374 ..text::ToPoint::to_point(&range.end, &buffer).row;
17375 Some((
17376 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17377 selection,
17378 ))
17379 });
17380
17381 let Some((buffer, selection)) = buffer_and_selection else {
17382 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17383 };
17384
17385 let Some(project) = self.project.as_ref() else {
17386 return Task::ready(Err(anyhow!("editor does not have project")));
17387 };
17388
17389 project.update(cx, |project, cx| {
17390 project.get_permalink_to_line(&buffer, selection, cx)
17391 })
17392 }
17393
17394 pub fn copy_permalink_to_line(
17395 &mut self,
17396 _: &CopyPermalinkToLine,
17397 window: &mut Window,
17398 cx: &mut Context<Self>,
17399 ) {
17400 let permalink_task = self.get_permalink_to_line(cx);
17401 let workspace = self.workspace();
17402
17403 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17404 Ok(permalink) => {
17405 cx.update(|_, cx| {
17406 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17407 })
17408 .ok();
17409 }
17410 Err(err) => {
17411 let message = format!("Failed to copy permalink: {err}");
17412
17413 anyhow::Result::<()>::Err(err).log_err();
17414
17415 if let Some(workspace) = workspace {
17416 workspace
17417 .update_in(cx, |workspace, _, cx| {
17418 struct CopyPermalinkToLine;
17419
17420 workspace.show_toast(
17421 Toast::new(
17422 NotificationId::unique::<CopyPermalinkToLine>(),
17423 message,
17424 ),
17425 cx,
17426 )
17427 })
17428 .ok();
17429 }
17430 }
17431 })
17432 .detach();
17433 }
17434
17435 pub fn copy_file_location(
17436 &mut self,
17437 _: &CopyFileLocation,
17438 _: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17442 if let Some(file) = self.target_file(cx) {
17443 if let Some(path) = file.path().to_str() {
17444 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17445 }
17446 }
17447 }
17448
17449 pub fn open_permalink_to_line(
17450 &mut self,
17451 _: &OpenPermalinkToLine,
17452 window: &mut Window,
17453 cx: &mut Context<Self>,
17454 ) {
17455 let permalink_task = self.get_permalink_to_line(cx);
17456 let workspace = self.workspace();
17457
17458 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17459 Ok(permalink) => {
17460 cx.update(|_, cx| {
17461 cx.open_url(permalink.as_ref());
17462 })
17463 .ok();
17464 }
17465 Err(err) => {
17466 let message = format!("Failed to open permalink: {err}");
17467
17468 anyhow::Result::<()>::Err(err).log_err();
17469
17470 if let Some(workspace) = workspace {
17471 workspace
17472 .update(cx, |workspace, cx| {
17473 struct OpenPermalinkToLine;
17474
17475 workspace.show_toast(
17476 Toast::new(
17477 NotificationId::unique::<OpenPermalinkToLine>(),
17478 message,
17479 ),
17480 cx,
17481 )
17482 })
17483 .ok();
17484 }
17485 }
17486 })
17487 .detach();
17488 }
17489
17490 pub fn insert_uuid_v4(
17491 &mut self,
17492 _: &InsertUuidV4,
17493 window: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) {
17496 self.insert_uuid(UuidVersion::V4, window, cx);
17497 }
17498
17499 pub fn insert_uuid_v7(
17500 &mut self,
17501 _: &InsertUuidV7,
17502 window: &mut Window,
17503 cx: &mut Context<Self>,
17504 ) {
17505 self.insert_uuid(UuidVersion::V7, window, cx);
17506 }
17507
17508 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17509 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17510 self.transact(window, cx, |this, window, cx| {
17511 let edits = this
17512 .selections
17513 .all::<Point>(cx)
17514 .into_iter()
17515 .map(|selection| {
17516 let uuid = match version {
17517 UuidVersion::V4 => uuid::Uuid::new_v4(),
17518 UuidVersion::V7 => uuid::Uuid::now_v7(),
17519 };
17520
17521 (selection.range(), uuid.to_string())
17522 });
17523 this.edit(edits, cx);
17524 this.refresh_inline_completion(true, false, window, cx);
17525 });
17526 }
17527
17528 pub fn open_selections_in_multibuffer(
17529 &mut self,
17530 _: &OpenSelectionsInMultibuffer,
17531 window: &mut Window,
17532 cx: &mut Context<Self>,
17533 ) {
17534 let multibuffer = self.buffer.read(cx);
17535
17536 let Some(buffer) = multibuffer.as_singleton() else {
17537 return;
17538 };
17539
17540 let Some(workspace) = self.workspace() else {
17541 return;
17542 };
17543
17544 let locations = self
17545 .selections
17546 .disjoint_anchors()
17547 .iter()
17548 .map(|range| Location {
17549 buffer: buffer.clone(),
17550 range: range.start.text_anchor..range.end.text_anchor,
17551 })
17552 .collect::<Vec<_>>();
17553
17554 let title = multibuffer.title(cx).to_string();
17555
17556 cx.spawn_in(window, async move |_, cx| {
17557 workspace.update_in(cx, |workspace, window, cx| {
17558 Self::open_locations_in_multibuffer(
17559 workspace,
17560 locations,
17561 format!("Selections for '{title}'"),
17562 false,
17563 MultibufferSelectionMode::All,
17564 window,
17565 cx,
17566 );
17567 })
17568 })
17569 .detach();
17570 }
17571
17572 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17573 /// last highlight added will be used.
17574 ///
17575 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17576 pub fn highlight_rows<T: 'static>(
17577 &mut self,
17578 range: Range<Anchor>,
17579 color: Hsla,
17580 options: RowHighlightOptions,
17581 cx: &mut Context<Self>,
17582 ) {
17583 let snapshot = self.buffer().read(cx).snapshot(cx);
17584 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17585 let ix = row_highlights.binary_search_by(|highlight| {
17586 Ordering::Equal
17587 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17588 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17589 });
17590
17591 if let Err(mut ix) = ix {
17592 let index = post_inc(&mut self.highlight_order);
17593
17594 // If this range intersects with the preceding highlight, then merge it with
17595 // the preceding highlight. Otherwise insert a new highlight.
17596 let mut merged = false;
17597 if ix > 0 {
17598 let prev_highlight = &mut row_highlights[ix - 1];
17599 if prev_highlight
17600 .range
17601 .end
17602 .cmp(&range.start, &snapshot)
17603 .is_ge()
17604 {
17605 ix -= 1;
17606 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17607 prev_highlight.range.end = range.end;
17608 }
17609 merged = true;
17610 prev_highlight.index = index;
17611 prev_highlight.color = color;
17612 prev_highlight.options = options;
17613 }
17614 }
17615
17616 if !merged {
17617 row_highlights.insert(
17618 ix,
17619 RowHighlight {
17620 range: range.clone(),
17621 index,
17622 color,
17623 options,
17624 type_id: TypeId::of::<T>(),
17625 },
17626 );
17627 }
17628
17629 // If any of the following highlights intersect with this one, merge them.
17630 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17631 let highlight = &row_highlights[ix];
17632 if next_highlight
17633 .range
17634 .start
17635 .cmp(&highlight.range.end, &snapshot)
17636 .is_le()
17637 {
17638 if next_highlight
17639 .range
17640 .end
17641 .cmp(&highlight.range.end, &snapshot)
17642 .is_gt()
17643 {
17644 row_highlights[ix].range.end = next_highlight.range.end;
17645 }
17646 row_highlights.remove(ix + 1);
17647 } else {
17648 break;
17649 }
17650 }
17651 }
17652 }
17653
17654 /// Remove any highlighted row ranges of the given type that intersect the
17655 /// given ranges.
17656 pub fn remove_highlighted_rows<T: 'static>(
17657 &mut self,
17658 ranges_to_remove: Vec<Range<Anchor>>,
17659 cx: &mut Context<Self>,
17660 ) {
17661 let snapshot = self.buffer().read(cx).snapshot(cx);
17662 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17663 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17664 row_highlights.retain(|highlight| {
17665 while let Some(range_to_remove) = ranges_to_remove.peek() {
17666 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17667 Ordering::Less | Ordering::Equal => {
17668 ranges_to_remove.next();
17669 }
17670 Ordering::Greater => {
17671 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17672 Ordering::Less | Ordering::Equal => {
17673 return false;
17674 }
17675 Ordering::Greater => break,
17676 }
17677 }
17678 }
17679 }
17680
17681 true
17682 })
17683 }
17684
17685 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17686 pub fn clear_row_highlights<T: 'static>(&mut self) {
17687 self.highlighted_rows.remove(&TypeId::of::<T>());
17688 }
17689
17690 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17691 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17692 self.highlighted_rows
17693 .get(&TypeId::of::<T>())
17694 .map_or(&[] as &[_], |vec| vec.as_slice())
17695 .iter()
17696 .map(|highlight| (highlight.range.clone(), highlight.color))
17697 }
17698
17699 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17700 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17701 /// Allows to ignore certain kinds of highlights.
17702 pub fn highlighted_display_rows(
17703 &self,
17704 window: &mut Window,
17705 cx: &mut App,
17706 ) -> BTreeMap<DisplayRow, LineHighlight> {
17707 let snapshot = self.snapshot(window, cx);
17708 let mut used_highlight_orders = HashMap::default();
17709 self.highlighted_rows
17710 .iter()
17711 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17712 .fold(
17713 BTreeMap::<DisplayRow, LineHighlight>::new(),
17714 |mut unique_rows, highlight| {
17715 let start = highlight.range.start.to_display_point(&snapshot);
17716 let end = highlight.range.end.to_display_point(&snapshot);
17717 let start_row = start.row().0;
17718 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17719 && end.column() == 0
17720 {
17721 end.row().0.saturating_sub(1)
17722 } else {
17723 end.row().0
17724 };
17725 for row in start_row..=end_row {
17726 let used_index =
17727 used_highlight_orders.entry(row).or_insert(highlight.index);
17728 if highlight.index >= *used_index {
17729 *used_index = highlight.index;
17730 unique_rows.insert(
17731 DisplayRow(row),
17732 LineHighlight {
17733 include_gutter: highlight.options.include_gutter,
17734 border: None,
17735 background: highlight.color.into(),
17736 type_id: Some(highlight.type_id),
17737 },
17738 );
17739 }
17740 }
17741 unique_rows
17742 },
17743 )
17744 }
17745
17746 pub fn highlighted_display_row_for_autoscroll(
17747 &self,
17748 snapshot: &DisplaySnapshot,
17749 ) -> Option<DisplayRow> {
17750 self.highlighted_rows
17751 .values()
17752 .flat_map(|highlighted_rows| highlighted_rows.iter())
17753 .filter_map(|highlight| {
17754 if highlight.options.autoscroll {
17755 Some(highlight.range.start.to_display_point(snapshot).row())
17756 } else {
17757 None
17758 }
17759 })
17760 .min()
17761 }
17762
17763 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17764 self.highlight_background::<SearchWithinRange>(
17765 ranges,
17766 |colors| colors.editor_document_highlight_read_background,
17767 cx,
17768 )
17769 }
17770
17771 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17772 self.breadcrumb_header = Some(new_header);
17773 }
17774
17775 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17776 self.clear_background_highlights::<SearchWithinRange>(cx);
17777 }
17778
17779 pub fn highlight_background<T: 'static>(
17780 &mut self,
17781 ranges: &[Range<Anchor>],
17782 color_fetcher: fn(&ThemeColors) -> Hsla,
17783 cx: &mut Context<Self>,
17784 ) {
17785 self.background_highlights
17786 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17787 self.scrollbar_marker_state.dirty = true;
17788 cx.notify();
17789 }
17790
17791 pub fn clear_background_highlights<T: 'static>(
17792 &mut self,
17793 cx: &mut Context<Self>,
17794 ) -> Option<BackgroundHighlight> {
17795 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17796 if !text_highlights.1.is_empty() {
17797 self.scrollbar_marker_state.dirty = true;
17798 cx.notify();
17799 }
17800 Some(text_highlights)
17801 }
17802
17803 pub fn highlight_gutter<T: 'static>(
17804 &mut self,
17805 ranges: &[Range<Anchor>],
17806 color_fetcher: fn(&App) -> Hsla,
17807 cx: &mut Context<Self>,
17808 ) {
17809 self.gutter_highlights
17810 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17811 cx.notify();
17812 }
17813
17814 pub fn clear_gutter_highlights<T: 'static>(
17815 &mut self,
17816 cx: &mut Context<Self>,
17817 ) -> Option<GutterHighlight> {
17818 cx.notify();
17819 self.gutter_highlights.remove(&TypeId::of::<T>())
17820 }
17821
17822 #[cfg(feature = "test-support")]
17823 pub fn all_text_background_highlights(
17824 &self,
17825 window: &mut Window,
17826 cx: &mut Context<Self>,
17827 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17828 let snapshot = self.snapshot(window, cx);
17829 let buffer = &snapshot.buffer_snapshot;
17830 let start = buffer.anchor_before(0);
17831 let end = buffer.anchor_after(buffer.len());
17832 let theme = cx.theme().colors();
17833 self.background_highlights_in_range(start..end, &snapshot, theme)
17834 }
17835
17836 #[cfg(feature = "test-support")]
17837 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17838 let snapshot = self.buffer().read(cx).snapshot(cx);
17839
17840 let highlights = self
17841 .background_highlights
17842 .get(&TypeId::of::<items::BufferSearchHighlights>());
17843
17844 if let Some((_color, ranges)) = highlights {
17845 ranges
17846 .iter()
17847 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17848 .collect_vec()
17849 } else {
17850 vec![]
17851 }
17852 }
17853
17854 fn document_highlights_for_position<'a>(
17855 &'a self,
17856 position: Anchor,
17857 buffer: &'a MultiBufferSnapshot,
17858 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17859 let read_highlights = self
17860 .background_highlights
17861 .get(&TypeId::of::<DocumentHighlightRead>())
17862 .map(|h| &h.1);
17863 let write_highlights = self
17864 .background_highlights
17865 .get(&TypeId::of::<DocumentHighlightWrite>())
17866 .map(|h| &h.1);
17867 let left_position = position.bias_left(buffer);
17868 let right_position = position.bias_right(buffer);
17869 read_highlights
17870 .into_iter()
17871 .chain(write_highlights)
17872 .flat_map(move |ranges| {
17873 let start_ix = match ranges.binary_search_by(|probe| {
17874 let cmp = probe.end.cmp(&left_position, buffer);
17875 if cmp.is_ge() {
17876 Ordering::Greater
17877 } else {
17878 Ordering::Less
17879 }
17880 }) {
17881 Ok(i) | Err(i) => i,
17882 };
17883
17884 ranges[start_ix..]
17885 .iter()
17886 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17887 })
17888 }
17889
17890 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17891 self.background_highlights
17892 .get(&TypeId::of::<T>())
17893 .map_or(false, |(_, highlights)| !highlights.is_empty())
17894 }
17895
17896 pub fn background_highlights_in_range(
17897 &self,
17898 search_range: Range<Anchor>,
17899 display_snapshot: &DisplaySnapshot,
17900 theme: &ThemeColors,
17901 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17902 let mut results = Vec::new();
17903 for (color_fetcher, ranges) in self.background_highlights.values() {
17904 let color = color_fetcher(theme);
17905 let start_ix = match ranges.binary_search_by(|probe| {
17906 let cmp = probe
17907 .end
17908 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17909 if cmp.is_gt() {
17910 Ordering::Greater
17911 } else {
17912 Ordering::Less
17913 }
17914 }) {
17915 Ok(i) | Err(i) => i,
17916 };
17917 for range in &ranges[start_ix..] {
17918 if range
17919 .start
17920 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17921 .is_ge()
17922 {
17923 break;
17924 }
17925
17926 let start = range.start.to_display_point(display_snapshot);
17927 let end = range.end.to_display_point(display_snapshot);
17928 results.push((start..end, color))
17929 }
17930 }
17931 results
17932 }
17933
17934 pub fn background_highlight_row_ranges<T: 'static>(
17935 &self,
17936 search_range: Range<Anchor>,
17937 display_snapshot: &DisplaySnapshot,
17938 count: usize,
17939 ) -> Vec<RangeInclusive<DisplayPoint>> {
17940 let mut results = Vec::new();
17941 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17942 return vec![];
17943 };
17944
17945 let start_ix = match ranges.binary_search_by(|probe| {
17946 let cmp = probe
17947 .end
17948 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17949 if cmp.is_gt() {
17950 Ordering::Greater
17951 } else {
17952 Ordering::Less
17953 }
17954 }) {
17955 Ok(i) | Err(i) => i,
17956 };
17957 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17958 if let (Some(start_display), Some(end_display)) = (start, end) {
17959 results.push(
17960 start_display.to_display_point(display_snapshot)
17961 ..=end_display.to_display_point(display_snapshot),
17962 );
17963 }
17964 };
17965 let mut start_row: Option<Point> = None;
17966 let mut end_row: Option<Point> = None;
17967 if ranges.len() > count {
17968 return Vec::new();
17969 }
17970 for range in &ranges[start_ix..] {
17971 if range
17972 .start
17973 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17974 .is_ge()
17975 {
17976 break;
17977 }
17978 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17979 if let Some(current_row) = &end_row {
17980 if end.row == current_row.row {
17981 continue;
17982 }
17983 }
17984 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17985 if start_row.is_none() {
17986 assert_eq!(end_row, None);
17987 start_row = Some(start);
17988 end_row = Some(end);
17989 continue;
17990 }
17991 if let Some(current_end) = end_row.as_mut() {
17992 if start.row > current_end.row + 1 {
17993 push_region(start_row, end_row);
17994 start_row = Some(start);
17995 end_row = Some(end);
17996 } else {
17997 // Merge two hunks.
17998 *current_end = end;
17999 }
18000 } else {
18001 unreachable!();
18002 }
18003 }
18004 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18005 push_region(start_row, end_row);
18006 results
18007 }
18008
18009 pub fn gutter_highlights_in_range(
18010 &self,
18011 search_range: Range<Anchor>,
18012 display_snapshot: &DisplaySnapshot,
18013 cx: &App,
18014 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18015 let mut results = Vec::new();
18016 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18017 let color = color_fetcher(cx);
18018 let start_ix = match ranges.binary_search_by(|probe| {
18019 let cmp = probe
18020 .end
18021 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18022 if cmp.is_gt() {
18023 Ordering::Greater
18024 } else {
18025 Ordering::Less
18026 }
18027 }) {
18028 Ok(i) | Err(i) => i,
18029 };
18030 for range in &ranges[start_ix..] {
18031 if range
18032 .start
18033 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18034 .is_ge()
18035 {
18036 break;
18037 }
18038
18039 let start = range.start.to_display_point(display_snapshot);
18040 let end = range.end.to_display_point(display_snapshot);
18041 results.push((start..end, color))
18042 }
18043 }
18044 results
18045 }
18046
18047 /// Get the text ranges corresponding to the redaction query
18048 pub fn redacted_ranges(
18049 &self,
18050 search_range: Range<Anchor>,
18051 display_snapshot: &DisplaySnapshot,
18052 cx: &App,
18053 ) -> Vec<Range<DisplayPoint>> {
18054 display_snapshot
18055 .buffer_snapshot
18056 .redacted_ranges(search_range, |file| {
18057 if let Some(file) = file {
18058 file.is_private()
18059 && EditorSettings::get(
18060 Some(SettingsLocation {
18061 worktree_id: file.worktree_id(cx),
18062 path: file.path().as_ref(),
18063 }),
18064 cx,
18065 )
18066 .redact_private_values
18067 } else {
18068 false
18069 }
18070 })
18071 .map(|range| {
18072 range.start.to_display_point(display_snapshot)
18073 ..range.end.to_display_point(display_snapshot)
18074 })
18075 .collect()
18076 }
18077
18078 pub fn highlight_text<T: 'static>(
18079 &mut self,
18080 ranges: Vec<Range<Anchor>>,
18081 style: HighlightStyle,
18082 cx: &mut Context<Self>,
18083 ) {
18084 self.display_map.update(cx, |map, _| {
18085 map.highlight_text(TypeId::of::<T>(), ranges, style)
18086 });
18087 cx.notify();
18088 }
18089
18090 pub(crate) fn highlight_inlays<T: 'static>(
18091 &mut self,
18092 highlights: Vec<InlayHighlight>,
18093 style: HighlightStyle,
18094 cx: &mut Context<Self>,
18095 ) {
18096 self.display_map.update(cx, |map, _| {
18097 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18098 });
18099 cx.notify();
18100 }
18101
18102 pub fn text_highlights<'a, T: 'static>(
18103 &'a self,
18104 cx: &'a App,
18105 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18106 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18107 }
18108
18109 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18110 let cleared = self
18111 .display_map
18112 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18113 if cleared {
18114 cx.notify();
18115 }
18116 }
18117
18118 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18119 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18120 && self.focus_handle.is_focused(window)
18121 }
18122
18123 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18124 self.show_cursor_when_unfocused = is_enabled;
18125 cx.notify();
18126 }
18127
18128 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18129 cx.notify();
18130 }
18131
18132 fn on_debug_session_event(
18133 &mut self,
18134 _session: Entity<Session>,
18135 event: &SessionEvent,
18136 cx: &mut Context<Self>,
18137 ) {
18138 match event {
18139 SessionEvent::InvalidateInlineValue => {
18140 self.refresh_inline_values(cx);
18141 }
18142 _ => {}
18143 }
18144 }
18145
18146 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18147 let Some(project) = self.project.clone() else {
18148 return;
18149 };
18150
18151 if !self.inline_value_cache.enabled {
18152 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18153 self.splice_inlays(&inlays, Vec::new(), cx);
18154 return;
18155 }
18156
18157 let current_execution_position = self
18158 .highlighted_rows
18159 .get(&TypeId::of::<ActiveDebugLine>())
18160 .and_then(|lines| lines.last().map(|line| line.range.start));
18161
18162 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18163 let inline_values = editor
18164 .update(cx, |editor, cx| {
18165 let Some(current_execution_position) = current_execution_position else {
18166 return Some(Task::ready(Ok(Vec::new())));
18167 };
18168
18169 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18170 let snapshot = buffer.snapshot(cx);
18171
18172 let excerpt = snapshot.excerpt_containing(
18173 current_execution_position..current_execution_position,
18174 )?;
18175
18176 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18177 })?;
18178
18179 let range =
18180 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18181
18182 project.inline_values(buffer, range, cx)
18183 })
18184 .ok()
18185 .flatten()?
18186 .await
18187 .context("refreshing debugger inlays")
18188 .log_err()?;
18189
18190 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18191
18192 for (buffer_id, inline_value) in inline_values
18193 .into_iter()
18194 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18195 {
18196 buffer_inline_values
18197 .entry(buffer_id)
18198 .or_default()
18199 .push(inline_value);
18200 }
18201
18202 editor
18203 .update(cx, |editor, cx| {
18204 let snapshot = editor.buffer.read(cx).snapshot(cx);
18205 let mut new_inlays = Vec::default();
18206
18207 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18208 let buffer_id = buffer_snapshot.remote_id();
18209 buffer_inline_values
18210 .get(&buffer_id)
18211 .into_iter()
18212 .flatten()
18213 .for_each(|hint| {
18214 let inlay = Inlay::debugger_hint(
18215 post_inc(&mut editor.next_inlay_id),
18216 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18217 hint.text(),
18218 );
18219
18220 new_inlays.push(inlay);
18221 });
18222 }
18223
18224 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18225 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18226
18227 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18228 })
18229 .ok()?;
18230 Some(())
18231 });
18232 }
18233
18234 fn on_buffer_event(
18235 &mut self,
18236 multibuffer: &Entity<MultiBuffer>,
18237 event: &multi_buffer::Event,
18238 window: &mut Window,
18239 cx: &mut Context<Self>,
18240 ) {
18241 match event {
18242 multi_buffer::Event::Edited {
18243 singleton_buffer_edited,
18244 edited_buffer: buffer_edited,
18245 } => {
18246 self.scrollbar_marker_state.dirty = true;
18247 self.active_indent_guides_state.dirty = true;
18248 self.refresh_active_diagnostics(cx);
18249 self.refresh_code_actions(window, cx);
18250 self.refresh_selected_text_highlights(true, window, cx);
18251 refresh_matching_bracket_highlights(self, window, cx);
18252 if self.has_active_inline_completion() {
18253 self.update_visible_inline_completion(window, cx);
18254 }
18255 if let Some(buffer) = buffer_edited {
18256 let buffer_id = buffer.read(cx).remote_id();
18257 if !self.registered_buffers.contains_key(&buffer_id) {
18258 if let Some(project) = self.project.as_ref() {
18259 project.update(cx, |project, cx| {
18260 self.registered_buffers.insert(
18261 buffer_id,
18262 project.register_buffer_with_language_servers(&buffer, cx),
18263 );
18264 })
18265 }
18266 }
18267 }
18268 cx.emit(EditorEvent::BufferEdited);
18269 cx.emit(SearchEvent::MatchesInvalidated);
18270 if *singleton_buffer_edited {
18271 if let Some(project) = &self.project {
18272 #[allow(clippy::mutable_key_type)]
18273 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18274 multibuffer
18275 .all_buffers()
18276 .into_iter()
18277 .filter_map(|buffer| {
18278 buffer.update(cx, |buffer, cx| {
18279 let language = buffer.language()?;
18280 let should_discard = project.update(cx, |project, cx| {
18281 project.is_local()
18282 && !project.has_language_servers_for(buffer, cx)
18283 });
18284 should_discard.not().then_some(language.clone())
18285 })
18286 })
18287 .collect::<HashSet<_>>()
18288 });
18289 if !languages_affected.is_empty() {
18290 self.refresh_inlay_hints(
18291 InlayHintRefreshReason::BufferEdited(languages_affected),
18292 cx,
18293 );
18294 }
18295 }
18296 }
18297
18298 let Some(project) = &self.project else { return };
18299 let (telemetry, is_via_ssh) = {
18300 let project = project.read(cx);
18301 let telemetry = project.client().telemetry().clone();
18302 let is_via_ssh = project.is_via_ssh();
18303 (telemetry, is_via_ssh)
18304 };
18305 refresh_linked_ranges(self, window, cx);
18306 telemetry.log_edit_event("editor", is_via_ssh);
18307 }
18308 multi_buffer::Event::ExcerptsAdded {
18309 buffer,
18310 predecessor,
18311 excerpts,
18312 } => {
18313 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18314 let buffer_id = buffer.read(cx).remote_id();
18315 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18316 if let Some(project) = &self.project {
18317 update_uncommitted_diff_for_buffer(
18318 cx.entity(),
18319 project,
18320 [buffer.clone()],
18321 self.buffer.clone(),
18322 cx,
18323 )
18324 .detach();
18325 }
18326 }
18327 cx.emit(EditorEvent::ExcerptsAdded {
18328 buffer: buffer.clone(),
18329 predecessor: *predecessor,
18330 excerpts: excerpts.clone(),
18331 });
18332 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18333 }
18334 multi_buffer::Event::ExcerptsRemoved {
18335 ids,
18336 removed_buffer_ids,
18337 } => {
18338 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18339 let buffer = self.buffer.read(cx);
18340 self.registered_buffers
18341 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18342 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18343 cx.emit(EditorEvent::ExcerptsRemoved {
18344 ids: ids.clone(),
18345 removed_buffer_ids: removed_buffer_ids.clone(),
18346 })
18347 }
18348 multi_buffer::Event::ExcerptsEdited {
18349 excerpt_ids,
18350 buffer_ids,
18351 } => {
18352 self.display_map.update(cx, |map, cx| {
18353 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18354 });
18355 cx.emit(EditorEvent::ExcerptsEdited {
18356 ids: excerpt_ids.clone(),
18357 })
18358 }
18359 multi_buffer::Event::ExcerptsExpanded { ids } => {
18360 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18361 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18362 }
18363 multi_buffer::Event::Reparsed(buffer_id) => {
18364 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18365 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18366
18367 cx.emit(EditorEvent::Reparsed(*buffer_id));
18368 }
18369 multi_buffer::Event::DiffHunksToggled => {
18370 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18371 }
18372 multi_buffer::Event::LanguageChanged(buffer_id) => {
18373 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18374 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18375 cx.emit(EditorEvent::Reparsed(*buffer_id));
18376 cx.notify();
18377 }
18378 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18379 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18380 multi_buffer::Event::FileHandleChanged
18381 | multi_buffer::Event::Reloaded
18382 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18383 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18384 multi_buffer::Event::DiagnosticsUpdated => {
18385 self.refresh_active_diagnostics(cx);
18386 self.refresh_inline_diagnostics(true, window, cx);
18387 self.scrollbar_marker_state.dirty = true;
18388 cx.notify();
18389 }
18390 _ => {}
18391 };
18392 }
18393
18394 pub fn start_temporary_diff_override(&mut self) {
18395 self.load_diff_task.take();
18396 self.temporary_diff_override = true;
18397 }
18398
18399 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18400 self.temporary_diff_override = false;
18401 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18402 self.buffer.update(cx, |buffer, cx| {
18403 buffer.set_all_diff_hunks_collapsed(cx);
18404 });
18405
18406 if let Some(project) = self.project.clone() {
18407 self.load_diff_task = Some(
18408 update_uncommitted_diff_for_buffer(
18409 cx.entity(),
18410 &project,
18411 self.buffer.read(cx).all_buffers(),
18412 self.buffer.clone(),
18413 cx,
18414 )
18415 .shared(),
18416 );
18417 }
18418 }
18419
18420 fn on_display_map_changed(
18421 &mut self,
18422 _: Entity<DisplayMap>,
18423 _: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) {
18426 cx.notify();
18427 }
18428
18429 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18430 let new_severity = if self.diagnostics_enabled() {
18431 EditorSettings::get_global(cx)
18432 .diagnostics_max_severity
18433 .unwrap_or(DiagnosticSeverity::Hint)
18434 } else {
18435 DiagnosticSeverity::Off
18436 };
18437 self.set_max_diagnostics_severity(new_severity, cx);
18438 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18439 self.update_edit_prediction_settings(cx);
18440 self.refresh_inline_completion(true, false, window, cx);
18441 self.refresh_inlay_hints(
18442 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18443 self.selections.newest_anchor().head(),
18444 &self.buffer.read(cx).snapshot(cx),
18445 cx,
18446 )),
18447 cx,
18448 );
18449
18450 let old_cursor_shape = self.cursor_shape;
18451
18452 {
18453 let editor_settings = EditorSettings::get_global(cx);
18454 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18455 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18456 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18457 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18458 }
18459
18460 if old_cursor_shape != self.cursor_shape {
18461 cx.emit(EditorEvent::CursorShapeChanged);
18462 }
18463
18464 let project_settings = ProjectSettings::get_global(cx);
18465 self.serialize_dirty_buffers =
18466 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18467
18468 if self.mode.is_full() {
18469 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18470 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18471 if self.show_inline_diagnostics != show_inline_diagnostics {
18472 self.show_inline_diagnostics = show_inline_diagnostics;
18473 self.refresh_inline_diagnostics(false, window, cx);
18474 }
18475
18476 if self.git_blame_inline_enabled != inline_blame_enabled {
18477 self.toggle_git_blame_inline_internal(false, window, cx);
18478 }
18479
18480 let minimap_settings = EditorSettings::get_global(cx).minimap;
18481 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18482 self.set_minimap_visibility(
18483 self.minimap_visibility.toggle_visibility(),
18484 window,
18485 cx,
18486 );
18487 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18488 minimap_entity.update(cx, |minimap_editor, cx| {
18489 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18490 })
18491 }
18492 }
18493
18494 cx.notify();
18495 }
18496
18497 pub fn set_searchable(&mut self, searchable: bool) {
18498 self.searchable = searchable;
18499 }
18500
18501 pub fn searchable(&self) -> bool {
18502 self.searchable
18503 }
18504
18505 fn open_proposed_changes_editor(
18506 &mut self,
18507 _: &OpenProposedChangesEditor,
18508 window: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let Some(workspace) = self.workspace() else {
18512 cx.propagate();
18513 return;
18514 };
18515
18516 let selections = self.selections.all::<usize>(cx);
18517 let multi_buffer = self.buffer.read(cx);
18518 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18519 let mut new_selections_by_buffer = HashMap::default();
18520 for selection in selections {
18521 for (buffer, range, _) in
18522 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18523 {
18524 let mut range = range.to_point(buffer);
18525 range.start.column = 0;
18526 range.end.column = buffer.line_len(range.end.row);
18527 new_selections_by_buffer
18528 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18529 .or_insert(Vec::new())
18530 .push(range)
18531 }
18532 }
18533
18534 let proposed_changes_buffers = new_selections_by_buffer
18535 .into_iter()
18536 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18537 .collect::<Vec<_>>();
18538 let proposed_changes_editor = cx.new(|cx| {
18539 ProposedChangesEditor::new(
18540 "Proposed changes",
18541 proposed_changes_buffers,
18542 self.project.clone(),
18543 window,
18544 cx,
18545 )
18546 });
18547
18548 window.defer(cx, move |window, cx| {
18549 workspace.update(cx, |workspace, cx| {
18550 workspace.active_pane().update(cx, |pane, cx| {
18551 pane.add_item(
18552 Box::new(proposed_changes_editor),
18553 true,
18554 true,
18555 None,
18556 window,
18557 cx,
18558 );
18559 });
18560 });
18561 });
18562 }
18563
18564 pub fn open_excerpts_in_split(
18565 &mut self,
18566 _: &OpenExcerptsSplit,
18567 window: &mut Window,
18568 cx: &mut Context<Self>,
18569 ) {
18570 self.open_excerpts_common(None, true, window, cx)
18571 }
18572
18573 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18574 self.open_excerpts_common(None, false, window, cx)
18575 }
18576
18577 fn open_excerpts_common(
18578 &mut self,
18579 jump_data: Option<JumpData>,
18580 split: bool,
18581 window: &mut Window,
18582 cx: &mut Context<Self>,
18583 ) {
18584 let Some(workspace) = self.workspace() else {
18585 cx.propagate();
18586 return;
18587 };
18588
18589 if self.buffer.read(cx).is_singleton() {
18590 cx.propagate();
18591 return;
18592 }
18593
18594 let mut new_selections_by_buffer = HashMap::default();
18595 match &jump_data {
18596 Some(JumpData::MultiBufferPoint {
18597 excerpt_id,
18598 position,
18599 anchor,
18600 line_offset_from_top,
18601 }) => {
18602 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18603 if let Some(buffer) = multi_buffer_snapshot
18604 .buffer_id_for_excerpt(*excerpt_id)
18605 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18606 {
18607 let buffer_snapshot = buffer.read(cx).snapshot();
18608 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18609 language::ToPoint::to_point(anchor, &buffer_snapshot)
18610 } else {
18611 buffer_snapshot.clip_point(*position, Bias::Left)
18612 };
18613 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18614 new_selections_by_buffer.insert(
18615 buffer,
18616 (
18617 vec![jump_to_offset..jump_to_offset],
18618 Some(*line_offset_from_top),
18619 ),
18620 );
18621 }
18622 }
18623 Some(JumpData::MultiBufferRow {
18624 row,
18625 line_offset_from_top,
18626 }) => {
18627 let point = MultiBufferPoint::new(row.0, 0);
18628 if let Some((buffer, buffer_point, _)) =
18629 self.buffer.read(cx).point_to_buffer_point(point, cx)
18630 {
18631 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18632 new_selections_by_buffer
18633 .entry(buffer)
18634 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18635 .0
18636 .push(buffer_offset..buffer_offset)
18637 }
18638 }
18639 None => {
18640 let selections = self.selections.all::<usize>(cx);
18641 let multi_buffer = self.buffer.read(cx);
18642 for selection in selections {
18643 for (snapshot, range, _, anchor) in multi_buffer
18644 .snapshot(cx)
18645 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18646 {
18647 if let Some(anchor) = anchor {
18648 // selection is in a deleted hunk
18649 let Some(buffer_id) = anchor.buffer_id else {
18650 continue;
18651 };
18652 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18653 continue;
18654 };
18655 let offset = text::ToOffset::to_offset(
18656 &anchor.text_anchor,
18657 &buffer_handle.read(cx).snapshot(),
18658 );
18659 let range = offset..offset;
18660 new_selections_by_buffer
18661 .entry(buffer_handle)
18662 .or_insert((Vec::new(), None))
18663 .0
18664 .push(range)
18665 } else {
18666 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18667 else {
18668 continue;
18669 };
18670 new_selections_by_buffer
18671 .entry(buffer_handle)
18672 .or_insert((Vec::new(), None))
18673 .0
18674 .push(range)
18675 }
18676 }
18677 }
18678 }
18679 }
18680
18681 new_selections_by_buffer
18682 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18683
18684 if new_selections_by_buffer.is_empty() {
18685 return;
18686 }
18687
18688 // We defer the pane interaction because we ourselves are a workspace item
18689 // and activating a new item causes the pane to call a method on us reentrantly,
18690 // which panics if we're on the stack.
18691 window.defer(cx, move |window, cx| {
18692 workspace.update(cx, |workspace, cx| {
18693 let pane = if split {
18694 workspace.adjacent_pane(window, cx)
18695 } else {
18696 workspace.active_pane().clone()
18697 };
18698
18699 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18700 let editor = buffer
18701 .read(cx)
18702 .file()
18703 .is_none()
18704 .then(|| {
18705 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18706 // so `workspace.open_project_item` will never find them, always opening a new editor.
18707 // Instead, we try to activate the existing editor in the pane first.
18708 let (editor, pane_item_index) =
18709 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18710 let editor = item.downcast::<Editor>()?;
18711 let singleton_buffer =
18712 editor.read(cx).buffer().read(cx).as_singleton()?;
18713 if singleton_buffer == buffer {
18714 Some((editor, i))
18715 } else {
18716 None
18717 }
18718 })?;
18719 pane.update(cx, |pane, cx| {
18720 pane.activate_item(pane_item_index, true, true, window, cx)
18721 });
18722 Some(editor)
18723 })
18724 .flatten()
18725 .unwrap_or_else(|| {
18726 workspace.open_project_item::<Self>(
18727 pane.clone(),
18728 buffer,
18729 true,
18730 true,
18731 window,
18732 cx,
18733 )
18734 });
18735
18736 editor.update(cx, |editor, cx| {
18737 let autoscroll = match scroll_offset {
18738 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18739 None => Autoscroll::newest(),
18740 };
18741 let nav_history = editor.nav_history.take();
18742 editor.change_selections(Some(autoscroll), window, cx, |s| {
18743 s.select_ranges(ranges);
18744 });
18745 editor.nav_history = nav_history;
18746 });
18747 }
18748 })
18749 });
18750 }
18751
18752 // For now, don't allow opening excerpts in buffers that aren't backed by
18753 // regular project files.
18754 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18755 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18756 }
18757
18758 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18759 let snapshot = self.buffer.read(cx).read(cx);
18760 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18761 Some(
18762 ranges
18763 .iter()
18764 .map(move |range| {
18765 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18766 })
18767 .collect(),
18768 )
18769 }
18770
18771 fn selection_replacement_ranges(
18772 &self,
18773 range: Range<OffsetUtf16>,
18774 cx: &mut App,
18775 ) -> Vec<Range<OffsetUtf16>> {
18776 let selections = self.selections.all::<OffsetUtf16>(cx);
18777 let newest_selection = selections
18778 .iter()
18779 .max_by_key(|selection| selection.id)
18780 .unwrap();
18781 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18782 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18783 let snapshot = self.buffer.read(cx).read(cx);
18784 selections
18785 .into_iter()
18786 .map(|mut selection| {
18787 selection.start.0 =
18788 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18789 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18790 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18791 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18792 })
18793 .collect()
18794 }
18795
18796 fn report_editor_event(
18797 &self,
18798 event_type: &'static str,
18799 file_extension: Option<String>,
18800 cx: &App,
18801 ) {
18802 if cfg!(any(test, feature = "test-support")) {
18803 return;
18804 }
18805
18806 let Some(project) = &self.project else { return };
18807
18808 // If None, we are in a file without an extension
18809 let file = self
18810 .buffer
18811 .read(cx)
18812 .as_singleton()
18813 .and_then(|b| b.read(cx).file());
18814 let file_extension = file_extension.or(file
18815 .as_ref()
18816 .and_then(|file| Path::new(file.file_name(cx)).extension())
18817 .and_then(|e| e.to_str())
18818 .map(|a| a.to_string()));
18819
18820 let vim_mode = vim_enabled(cx);
18821
18822 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18823 let copilot_enabled = edit_predictions_provider
18824 == language::language_settings::EditPredictionProvider::Copilot;
18825 let copilot_enabled_for_language = self
18826 .buffer
18827 .read(cx)
18828 .language_settings(cx)
18829 .show_edit_predictions;
18830
18831 let project = project.read(cx);
18832 telemetry::event!(
18833 event_type,
18834 file_extension,
18835 vim_mode,
18836 copilot_enabled,
18837 copilot_enabled_for_language,
18838 edit_predictions_provider,
18839 is_via_ssh = project.is_via_ssh(),
18840 );
18841 }
18842
18843 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18844 /// with each line being an array of {text, highlight} objects.
18845 fn copy_highlight_json(
18846 &mut self,
18847 _: &CopyHighlightJson,
18848 window: &mut Window,
18849 cx: &mut Context<Self>,
18850 ) {
18851 #[derive(Serialize)]
18852 struct Chunk<'a> {
18853 text: String,
18854 highlight: Option<&'a str>,
18855 }
18856
18857 let snapshot = self.buffer.read(cx).snapshot(cx);
18858 let range = self
18859 .selected_text_range(false, window, cx)
18860 .and_then(|selection| {
18861 if selection.range.is_empty() {
18862 None
18863 } else {
18864 Some(selection.range)
18865 }
18866 })
18867 .unwrap_or_else(|| 0..snapshot.len());
18868
18869 let chunks = snapshot.chunks(range, true);
18870 let mut lines = Vec::new();
18871 let mut line: VecDeque<Chunk> = VecDeque::new();
18872
18873 let Some(style) = self.style.as_ref() else {
18874 return;
18875 };
18876
18877 for chunk in chunks {
18878 let highlight = chunk
18879 .syntax_highlight_id
18880 .and_then(|id| id.name(&style.syntax));
18881 let mut chunk_lines = chunk.text.split('\n').peekable();
18882 while let Some(text) = chunk_lines.next() {
18883 let mut merged_with_last_token = false;
18884 if let Some(last_token) = line.back_mut() {
18885 if last_token.highlight == highlight {
18886 last_token.text.push_str(text);
18887 merged_with_last_token = true;
18888 }
18889 }
18890
18891 if !merged_with_last_token {
18892 line.push_back(Chunk {
18893 text: text.into(),
18894 highlight,
18895 });
18896 }
18897
18898 if chunk_lines.peek().is_some() {
18899 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18900 line.pop_front();
18901 }
18902 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18903 line.pop_back();
18904 }
18905
18906 lines.push(mem::take(&mut line));
18907 }
18908 }
18909 }
18910
18911 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18912 return;
18913 };
18914 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18915 }
18916
18917 pub fn open_context_menu(
18918 &mut self,
18919 _: &OpenContextMenu,
18920 window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 self.request_autoscroll(Autoscroll::newest(), cx);
18924 let position = self.selections.newest_display(cx).start;
18925 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18926 }
18927
18928 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18929 &self.inlay_hint_cache
18930 }
18931
18932 pub fn replay_insert_event(
18933 &mut self,
18934 text: &str,
18935 relative_utf16_range: Option<Range<isize>>,
18936 window: &mut Window,
18937 cx: &mut Context<Self>,
18938 ) {
18939 if !self.input_enabled {
18940 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18941 return;
18942 }
18943 if let Some(relative_utf16_range) = relative_utf16_range {
18944 let selections = self.selections.all::<OffsetUtf16>(cx);
18945 self.change_selections(None, window, cx, |s| {
18946 let new_ranges = selections.into_iter().map(|range| {
18947 let start = OffsetUtf16(
18948 range
18949 .head()
18950 .0
18951 .saturating_add_signed(relative_utf16_range.start),
18952 );
18953 let end = OffsetUtf16(
18954 range
18955 .head()
18956 .0
18957 .saturating_add_signed(relative_utf16_range.end),
18958 );
18959 start..end
18960 });
18961 s.select_ranges(new_ranges);
18962 });
18963 }
18964
18965 self.handle_input(text, window, cx);
18966 }
18967
18968 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18969 let Some(provider) = self.semantics_provider.as_ref() else {
18970 return false;
18971 };
18972
18973 let mut supports = false;
18974 self.buffer().update(cx, |this, cx| {
18975 this.for_each_buffer(|buffer| {
18976 supports |= provider.supports_inlay_hints(buffer, cx);
18977 });
18978 });
18979
18980 supports
18981 }
18982
18983 pub fn is_focused(&self, window: &Window) -> bool {
18984 self.focus_handle.is_focused(window)
18985 }
18986
18987 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18988 cx.emit(EditorEvent::Focused);
18989
18990 if let Some(descendant) = self
18991 .last_focused_descendant
18992 .take()
18993 .and_then(|descendant| descendant.upgrade())
18994 {
18995 window.focus(&descendant);
18996 } else {
18997 if let Some(blame) = self.blame.as_ref() {
18998 blame.update(cx, GitBlame::focus)
18999 }
19000
19001 self.blink_manager.update(cx, BlinkManager::enable);
19002 self.show_cursor_names(window, cx);
19003 self.buffer.update(cx, |buffer, cx| {
19004 buffer.finalize_last_transaction(cx);
19005 if self.leader_id.is_none() {
19006 buffer.set_active_selections(
19007 &self.selections.disjoint_anchors(),
19008 self.selections.line_mode,
19009 self.cursor_shape,
19010 cx,
19011 );
19012 }
19013 });
19014 }
19015 }
19016
19017 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19018 cx.emit(EditorEvent::FocusedIn)
19019 }
19020
19021 fn handle_focus_out(
19022 &mut self,
19023 event: FocusOutEvent,
19024 _window: &mut Window,
19025 cx: &mut Context<Self>,
19026 ) {
19027 if event.blurred != self.focus_handle {
19028 self.last_focused_descendant = Some(event.blurred);
19029 }
19030 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19031 }
19032
19033 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19034 self.blink_manager.update(cx, BlinkManager::disable);
19035 self.buffer
19036 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19037
19038 if let Some(blame) = self.blame.as_ref() {
19039 blame.update(cx, GitBlame::blur)
19040 }
19041 if !self.hover_state.focused(window, cx) {
19042 hide_hover(self, cx);
19043 }
19044 if !self
19045 .context_menu
19046 .borrow()
19047 .as_ref()
19048 .is_some_and(|context_menu| context_menu.focused(window, cx))
19049 {
19050 self.hide_context_menu(window, cx);
19051 }
19052 self.discard_inline_completion(false, cx);
19053 cx.emit(EditorEvent::Blurred);
19054 cx.notify();
19055 }
19056
19057 pub fn register_action<A: Action>(
19058 &mut self,
19059 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19060 ) -> Subscription {
19061 let id = self.next_editor_action_id.post_inc();
19062 let listener = Arc::new(listener);
19063 self.editor_actions.borrow_mut().insert(
19064 id,
19065 Box::new(move |window, _| {
19066 let listener = listener.clone();
19067 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19068 let action = action.downcast_ref().unwrap();
19069 if phase == DispatchPhase::Bubble {
19070 listener(action, window, cx)
19071 }
19072 })
19073 }),
19074 );
19075
19076 let editor_actions = self.editor_actions.clone();
19077 Subscription::new(move || {
19078 editor_actions.borrow_mut().remove(&id);
19079 })
19080 }
19081
19082 pub fn file_header_size(&self) -> u32 {
19083 FILE_HEADER_HEIGHT
19084 }
19085
19086 pub fn restore(
19087 &mut self,
19088 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19089 window: &mut Window,
19090 cx: &mut Context<Self>,
19091 ) {
19092 let workspace = self.workspace();
19093 let project = self.project.as_ref();
19094 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19095 let mut tasks = Vec::new();
19096 for (buffer_id, changes) in revert_changes {
19097 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19098 buffer.update(cx, |buffer, cx| {
19099 buffer.edit(
19100 changes
19101 .into_iter()
19102 .map(|(range, text)| (range, text.to_string())),
19103 None,
19104 cx,
19105 );
19106 });
19107
19108 if let Some(project) =
19109 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19110 {
19111 project.update(cx, |project, cx| {
19112 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19113 })
19114 }
19115 }
19116 }
19117 tasks
19118 });
19119 cx.spawn_in(window, async move |_, cx| {
19120 for (buffer, task) in save_tasks {
19121 let result = task.await;
19122 if result.is_err() {
19123 let Some(path) = buffer
19124 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19125 .ok()
19126 else {
19127 continue;
19128 };
19129 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19130 let Some(task) = cx
19131 .update_window_entity(&workspace, |workspace, window, cx| {
19132 workspace
19133 .open_path_preview(path, None, false, false, false, window, cx)
19134 })
19135 .ok()
19136 else {
19137 continue;
19138 };
19139 task.await.log_err();
19140 }
19141 }
19142 }
19143 })
19144 .detach();
19145 self.change_selections(None, window, cx, |selections| selections.refresh());
19146 }
19147
19148 pub fn to_pixel_point(
19149 &self,
19150 source: multi_buffer::Anchor,
19151 editor_snapshot: &EditorSnapshot,
19152 window: &mut Window,
19153 ) -> Option<gpui::Point<Pixels>> {
19154 let source_point = source.to_display_point(editor_snapshot);
19155 self.display_to_pixel_point(source_point, editor_snapshot, window)
19156 }
19157
19158 pub fn display_to_pixel_point(
19159 &self,
19160 source: DisplayPoint,
19161 editor_snapshot: &EditorSnapshot,
19162 window: &mut Window,
19163 ) -> Option<gpui::Point<Pixels>> {
19164 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19165 let text_layout_details = self.text_layout_details(window);
19166 let scroll_top = text_layout_details
19167 .scroll_anchor
19168 .scroll_position(editor_snapshot)
19169 .y;
19170
19171 if source.row().as_f32() < scroll_top.floor() {
19172 return None;
19173 }
19174 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19175 let source_y = line_height * (source.row().as_f32() - scroll_top);
19176 Some(gpui::Point::new(source_x, source_y))
19177 }
19178
19179 pub fn has_visible_completions_menu(&self) -> bool {
19180 !self.edit_prediction_preview_is_active()
19181 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19182 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19183 })
19184 }
19185
19186 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19187 if self.mode.is_minimap() {
19188 return;
19189 }
19190 self.addons
19191 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19192 }
19193
19194 pub fn unregister_addon<T: Addon>(&mut self) {
19195 self.addons.remove(&std::any::TypeId::of::<T>());
19196 }
19197
19198 pub fn addon<T: Addon>(&self) -> Option<&T> {
19199 let type_id = std::any::TypeId::of::<T>();
19200 self.addons
19201 .get(&type_id)
19202 .and_then(|item| item.to_any().downcast_ref::<T>())
19203 }
19204
19205 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19206 let type_id = std::any::TypeId::of::<T>();
19207 self.addons
19208 .get_mut(&type_id)
19209 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19210 }
19211
19212 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19213 let text_layout_details = self.text_layout_details(window);
19214 let style = &text_layout_details.editor_style;
19215 let font_id = window.text_system().resolve_font(&style.text.font());
19216 let font_size = style.text.font_size.to_pixels(window.rem_size());
19217 let line_height = style.text.line_height_in_pixels(window.rem_size());
19218 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19219
19220 gpui::Size::new(em_width, line_height)
19221 }
19222
19223 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19224 self.load_diff_task.clone()
19225 }
19226
19227 fn read_metadata_from_db(
19228 &mut self,
19229 item_id: u64,
19230 workspace_id: WorkspaceId,
19231 window: &mut Window,
19232 cx: &mut Context<Editor>,
19233 ) {
19234 if self.is_singleton(cx)
19235 && !self.mode.is_minimap()
19236 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19237 {
19238 let buffer_snapshot = OnceCell::new();
19239
19240 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19241 if !folds.is_empty() {
19242 let snapshot =
19243 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19244 self.fold_ranges(
19245 folds
19246 .into_iter()
19247 .map(|(start, end)| {
19248 snapshot.clip_offset(start, Bias::Left)
19249 ..snapshot.clip_offset(end, Bias::Right)
19250 })
19251 .collect(),
19252 false,
19253 window,
19254 cx,
19255 );
19256 }
19257 }
19258
19259 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19260 if !selections.is_empty() {
19261 let snapshot =
19262 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19263 self.change_selections(None, window, cx, |s| {
19264 s.select_ranges(selections.into_iter().map(|(start, end)| {
19265 snapshot.clip_offset(start, Bias::Left)
19266 ..snapshot.clip_offset(end, Bias::Right)
19267 }));
19268 });
19269 }
19270 };
19271 }
19272
19273 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19274 }
19275}
19276
19277fn vim_enabled(cx: &App) -> bool {
19278 cx.global::<SettingsStore>()
19279 .raw_user_settings()
19280 .get("vim_mode")
19281 == Some(&serde_json::Value::Bool(true))
19282}
19283
19284// Consider user intent and default settings
19285fn choose_completion_range(
19286 completion: &Completion,
19287 intent: CompletionIntent,
19288 buffer: &Entity<Buffer>,
19289 cx: &mut Context<Editor>,
19290) -> Range<usize> {
19291 fn should_replace(
19292 completion: &Completion,
19293 insert_range: &Range<text::Anchor>,
19294 intent: CompletionIntent,
19295 completion_mode_setting: LspInsertMode,
19296 buffer: &Buffer,
19297 ) -> bool {
19298 // specific actions take precedence over settings
19299 match intent {
19300 CompletionIntent::CompleteWithInsert => return false,
19301 CompletionIntent::CompleteWithReplace => return true,
19302 CompletionIntent::Complete | CompletionIntent::Compose => {}
19303 }
19304
19305 match completion_mode_setting {
19306 LspInsertMode::Insert => false,
19307 LspInsertMode::Replace => true,
19308 LspInsertMode::ReplaceSubsequence => {
19309 let mut text_to_replace = buffer.chars_for_range(
19310 buffer.anchor_before(completion.replace_range.start)
19311 ..buffer.anchor_after(completion.replace_range.end),
19312 );
19313 let mut completion_text = completion.new_text.chars();
19314
19315 // is `text_to_replace` a subsequence of `completion_text`
19316 text_to_replace
19317 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19318 }
19319 LspInsertMode::ReplaceSuffix => {
19320 let range_after_cursor = insert_range.end..completion.replace_range.end;
19321
19322 let text_after_cursor = buffer
19323 .text_for_range(
19324 buffer.anchor_before(range_after_cursor.start)
19325 ..buffer.anchor_after(range_after_cursor.end),
19326 )
19327 .collect::<String>();
19328 completion.new_text.ends_with(&text_after_cursor)
19329 }
19330 }
19331 }
19332
19333 let buffer = buffer.read(cx);
19334
19335 if let CompletionSource::Lsp {
19336 insert_range: Some(insert_range),
19337 ..
19338 } = &completion.source
19339 {
19340 let completion_mode_setting =
19341 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19342 .completions
19343 .lsp_insert_mode;
19344
19345 if !should_replace(
19346 completion,
19347 &insert_range,
19348 intent,
19349 completion_mode_setting,
19350 buffer,
19351 ) {
19352 return insert_range.to_offset(buffer);
19353 }
19354 }
19355
19356 completion.replace_range.to_offset(buffer)
19357}
19358
19359fn insert_extra_newline_brackets(
19360 buffer: &MultiBufferSnapshot,
19361 range: Range<usize>,
19362 language: &language::LanguageScope,
19363) -> bool {
19364 let leading_whitespace_len = buffer
19365 .reversed_chars_at(range.start)
19366 .take_while(|c| c.is_whitespace() && *c != '\n')
19367 .map(|c| c.len_utf8())
19368 .sum::<usize>();
19369 let trailing_whitespace_len = buffer
19370 .chars_at(range.end)
19371 .take_while(|c| c.is_whitespace() && *c != '\n')
19372 .map(|c| c.len_utf8())
19373 .sum::<usize>();
19374 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19375
19376 language.brackets().any(|(pair, enabled)| {
19377 let pair_start = pair.start.trim_end();
19378 let pair_end = pair.end.trim_start();
19379
19380 enabled
19381 && pair.newline
19382 && buffer.contains_str_at(range.end, pair_end)
19383 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19384 })
19385}
19386
19387fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19388 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19389 [(buffer, range, _)] => (*buffer, range.clone()),
19390 _ => return false,
19391 };
19392 let pair = {
19393 let mut result: Option<BracketMatch> = None;
19394
19395 for pair in buffer
19396 .all_bracket_ranges(range.clone())
19397 .filter(move |pair| {
19398 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19399 })
19400 {
19401 let len = pair.close_range.end - pair.open_range.start;
19402
19403 if let Some(existing) = &result {
19404 let existing_len = existing.close_range.end - existing.open_range.start;
19405 if len > existing_len {
19406 continue;
19407 }
19408 }
19409
19410 result = Some(pair);
19411 }
19412
19413 result
19414 };
19415 let Some(pair) = pair else {
19416 return false;
19417 };
19418 pair.newline_only
19419 && buffer
19420 .chars_for_range(pair.open_range.end..range.start)
19421 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19422 .all(|c| c.is_whitespace() && c != '\n')
19423}
19424
19425fn update_uncommitted_diff_for_buffer(
19426 editor: Entity<Editor>,
19427 project: &Entity<Project>,
19428 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19429 buffer: Entity<MultiBuffer>,
19430 cx: &mut App,
19431) -> Task<()> {
19432 let mut tasks = Vec::new();
19433 project.update(cx, |project, cx| {
19434 for buffer in buffers {
19435 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19436 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19437 }
19438 }
19439 });
19440 cx.spawn(async move |cx| {
19441 let diffs = future::join_all(tasks).await;
19442 if editor
19443 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19444 .unwrap_or(false)
19445 {
19446 return;
19447 }
19448
19449 buffer
19450 .update(cx, |buffer, cx| {
19451 for diff in diffs.into_iter().flatten() {
19452 buffer.add_diff(diff, cx);
19453 }
19454 })
19455 .ok();
19456 })
19457}
19458
19459fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19460 let tab_size = tab_size.get() as usize;
19461 let mut width = offset;
19462
19463 for ch in text.chars() {
19464 width += if ch == '\t' {
19465 tab_size - (width % tab_size)
19466 } else {
19467 1
19468 };
19469 }
19470
19471 width - offset
19472}
19473
19474#[cfg(test)]
19475mod tests {
19476 use super::*;
19477
19478 #[test]
19479 fn test_string_size_with_expanded_tabs() {
19480 let nz = |val| NonZeroU32::new(val).unwrap();
19481 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19482 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19483 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19484 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19485 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19486 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19487 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19488 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19489 }
19490}
19491
19492/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19493struct WordBreakingTokenizer<'a> {
19494 input: &'a str,
19495}
19496
19497impl<'a> WordBreakingTokenizer<'a> {
19498 fn new(input: &'a str) -> Self {
19499 Self { input }
19500 }
19501}
19502
19503fn is_char_ideographic(ch: char) -> bool {
19504 use unicode_script::Script::*;
19505 use unicode_script::UnicodeScript;
19506 matches!(ch.script(), Han | Tangut | Yi)
19507}
19508
19509fn is_grapheme_ideographic(text: &str) -> bool {
19510 text.chars().any(is_char_ideographic)
19511}
19512
19513fn is_grapheme_whitespace(text: &str) -> bool {
19514 text.chars().any(|x| x.is_whitespace())
19515}
19516
19517fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19518 text.chars().next().map_or(false, |ch| {
19519 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19520 })
19521}
19522
19523#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19524enum WordBreakToken<'a> {
19525 Word { token: &'a str, grapheme_len: usize },
19526 InlineWhitespace { token: &'a str, grapheme_len: usize },
19527 Newline,
19528}
19529
19530impl<'a> Iterator for WordBreakingTokenizer<'a> {
19531 /// Yields a span, the count of graphemes in the token, and whether it was
19532 /// whitespace. Note that it also breaks at word boundaries.
19533 type Item = WordBreakToken<'a>;
19534
19535 fn next(&mut self) -> Option<Self::Item> {
19536 use unicode_segmentation::UnicodeSegmentation;
19537 if self.input.is_empty() {
19538 return None;
19539 }
19540
19541 let mut iter = self.input.graphemes(true).peekable();
19542 let mut offset = 0;
19543 let mut grapheme_len = 0;
19544 if let Some(first_grapheme) = iter.next() {
19545 let is_newline = first_grapheme == "\n";
19546 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19547 offset += first_grapheme.len();
19548 grapheme_len += 1;
19549 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19550 if let Some(grapheme) = iter.peek().copied() {
19551 if should_stay_with_preceding_ideograph(grapheme) {
19552 offset += grapheme.len();
19553 grapheme_len += 1;
19554 }
19555 }
19556 } else {
19557 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19558 let mut next_word_bound = words.peek().copied();
19559 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19560 next_word_bound = words.next();
19561 }
19562 while let Some(grapheme) = iter.peek().copied() {
19563 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19564 break;
19565 };
19566 if is_grapheme_whitespace(grapheme) != is_whitespace
19567 || (grapheme == "\n") != is_newline
19568 {
19569 break;
19570 };
19571 offset += grapheme.len();
19572 grapheme_len += 1;
19573 iter.next();
19574 }
19575 }
19576 let token = &self.input[..offset];
19577 self.input = &self.input[offset..];
19578 if token == "\n" {
19579 Some(WordBreakToken::Newline)
19580 } else if is_whitespace {
19581 Some(WordBreakToken::InlineWhitespace {
19582 token,
19583 grapheme_len,
19584 })
19585 } else {
19586 Some(WordBreakToken::Word {
19587 token,
19588 grapheme_len,
19589 })
19590 }
19591 } else {
19592 None
19593 }
19594 }
19595}
19596
19597#[test]
19598fn test_word_breaking_tokenizer() {
19599 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19600 ("", &[]),
19601 (" ", &[whitespace(" ", 2)]),
19602 ("Ʒ", &[word("Ʒ", 1)]),
19603 ("Ǽ", &[word("Ǽ", 1)]),
19604 ("⋑", &[word("⋑", 1)]),
19605 ("⋑⋑", &[word("⋑⋑", 2)]),
19606 (
19607 "原理,进而",
19608 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19609 ),
19610 (
19611 "hello world",
19612 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19613 ),
19614 (
19615 "hello, world",
19616 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19617 ),
19618 (
19619 " hello world",
19620 &[
19621 whitespace(" ", 2),
19622 word("hello", 5),
19623 whitespace(" ", 1),
19624 word("world", 5),
19625 ],
19626 ),
19627 (
19628 "这是什么 \n 钢笔",
19629 &[
19630 word("这", 1),
19631 word("是", 1),
19632 word("什", 1),
19633 word("么", 1),
19634 whitespace(" ", 1),
19635 newline(),
19636 whitespace(" ", 1),
19637 word("钢", 1),
19638 word("笔", 1),
19639 ],
19640 ),
19641 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19642 ];
19643
19644 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19645 WordBreakToken::Word {
19646 token,
19647 grapheme_len,
19648 }
19649 }
19650
19651 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19652 WordBreakToken::InlineWhitespace {
19653 token,
19654 grapheme_len,
19655 }
19656 }
19657
19658 fn newline() -> WordBreakToken<'static> {
19659 WordBreakToken::Newline
19660 }
19661
19662 for (input, result) in tests {
19663 assert_eq!(
19664 WordBreakingTokenizer::new(input)
19665 .collect::<Vec<_>>()
19666 .as_slice(),
19667 *result,
19668 );
19669 }
19670}
19671
19672fn wrap_with_prefix(
19673 line_prefix: String,
19674 unwrapped_text: String,
19675 wrap_column: usize,
19676 tab_size: NonZeroU32,
19677 preserve_existing_whitespace: bool,
19678) -> String {
19679 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19680 let mut wrapped_text = String::new();
19681 let mut current_line = line_prefix.clone();
19682
19683 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19684 let mut current_line_len = line_prefix_len;
19685 let mut in_whitespace = false;
19686 for token in tokenizer {
19687 let have_preceding_whitespace = in_whitespace;
19688 match token {
19689 WordBreakToken::Word {
19690 token,
19691 grapheme_len,
19692 } => {
19693 in_whitespace = false;
19694 if current_line_len + grapheme_len > wrap_column
19695 && current_line_len != line_prefix_len
19696 {
19697 wrapped_text.push_str(current_line.trim_end());
19698 wrapped_text.push('\n');
19699 current_line.truncate(line_prefix.len());
19700 current_line_len = line_prefix_len;
19701 }
19702 current_line.push_str(token);
19703 current_line_len += grapheme_len;
19704 }
19705 WordBreakToken::InlineWhitespace {
19706 mut token,
19707 mut grapheme_len,
19708 } => {
19709 in_whitespace = true;
19710 if have_preceding_whitespace && !preserve_existing_whitespace {
19711 continue;
19712 }
19713 if !preserve_existing_whitespace {
19714 token = " ";
19715 grapheme_len = 1;
19716 }
19717 if current_line_len + grapheme_len > wrap_column {
19718 wrapped_text.push_str(current_line.trim_end());
19719 wrapped_text.push('\n');
19720 current_line.truncate(line_prefix.len());
19721 current_line_len = line_prefix_len;
19722 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19723 current_line.push_str(token);
19724 current_line_len += grapheme_len;
19725 }
19726 }
19727 WordBreakToken::Newline => {
19728 in_whitespace = true;
19729 if preserve_existing_whitespace {
19730 wrapped_text.push_str(current_line.trim_end());
19731 wrapped_text.push('\n');
19732 current_line.truncate(line_prefix.len());
19733 current_line_len = line_prefix_len;
19734 } else if have_preceding_whitespace {
19735 continue;
19736 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19737 {
19738 wrapped_text.push_str(current_line.trim_end());
19739 wrapped_text.push('\n');
19740 current_line.truncate(line_prefix.len());
19741 current_line_len = line_prefix_len;
19742 } else if current_line_len != line_prefix_len {
19743 current_line.push(' ');
19744 current_line_len += 1;
19745 }
19746 }
19747 }
19748 }
19749
19750 if !current_line.is_empty() {
19751 wrapped_text.push_str(¤t_line);
19752 }
19753 wrapped_text
19754}
19755
19756#[test]
19757fn test_wrap_with_prefix() {
19758 assert_eq!(
19759 wrap_with_prefix(
19760 "# ".to_string(),
19761 "abcdefg".to_string(),
19762 4,
19763 NonZeroU32::new(4).unwrap(),
19764 false,
19765 ),
19766 "# abcdefg"
19767 );
19768 assert_eq!(
19769 wrap_with_prefix(
19770 "".to_string(),
19771 "\thello world".to_string(),
19772 8,
19773 NonZeroU32::new(4).unwrap(),
19774 false,
19775 ),
19776 "hello\nworld"
19777 );
19778 assert_eq!(
19779 wrap_with_prefix(
19780 "// ".to_string(),
19781 "xx \nyy zz aa bb cc".to_string(),
19782 12,
19783 NonZeroU32::new(4).unwrap(),
19784 false,
19785 ),
19786 "// xx yy zz\n// aa bb cc"
19787 );
19788 assert_eq!(
19789 wrap_with_prefix(
19790 String::new(),
19791 "这是什么 \n 钢笔".to_string(),
19792 3,
19793 NonZeroU32::new(4).unwrap(),
19794 false,
19795 ),
19796 "这是什\n么 钢\n笔"
19797 );
19798}
19799
19800pub trait CollaborationHub {
19801 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19802 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19803 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19804}
19805
19806impl CollaborationHub for Entity<Project> {
19807 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19808 self.read(cx).collaborators()
19809 }
19810
19811 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19812 self.read(cx).user_store().read(cx).participant_indices()
19813 }
19814
19815 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19816 let this = self.read(cx);
19817 let user_ids = this.collaborators().values().map(|c| c.user_id);
19818 this.user_store().read_with(cx, |user_store, cx| {
19819 user_store.participant_names(user_ids, cx)
19820 })
19821 }
19822}
19823
19824pub trait SemanticsProvider {
19825 fn hover(
19826 &self,
19827 buffer: &Entity<Buffer>,
19828 position: text::Anchor,
19829 cx: &mut App,
19830 ) -> Option<Task<Vec<project::Hover>>>;
19831
19832 fn inline_values(
19833 &self,
19834 buffer_handle: Entity<Buffer>,
19835 range: Range<text::Anchor>,
19836 cx: &mut App,
19837 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19838
19839 fn inlay_hints(
19840 &self,
19841 buffer_handle: Entity<Buffer>,
19842 range: Range<text::Anchor>,
19843 cx: &mut App,
19844 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19845
19846 fn resolve_inlay_hint(
19847 &self,
19848 hint: InlayHint,
19849 buffer_handle: Entity<Buffer>,
19850 server_id: LanguageServerId,
19851 cx: &mut App,
19852 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19853
19854 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19855
19856 fn document_highlights(
19857 &self,
19858 buffer: &Entity<Buffer>,
19859 position: text::Anchor,
19860 cx: &mut App,
19861 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19862
19863 fn definitions(
19864 &self,
19865 buffer: &Entity<Buffer>,
19866 position: text::Anchor,
19867 kind: GotoDefinitionKind,
19868 cx: &mut App,
19869 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19870
19871 fn range_for_rename(
19872 &self,
19873 buffer: &Entity<Buffer>,
19874 position: text::Anchor,
19875 cx: &mut App,
19876 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19877
19878 fn perform_rename(
19879 &self,
19880 buffer: &Entity<Buffer>,
19881 position: text::Anchor,
19882 new_name: String,
19883 cx: &mut App,
19884 ) -> Option<Task<Result<ProjectTransaction>>>;
19885}
19886
19887pub trait CompletionProvider {
19888 fn completions(
19889 &self,
19890 excerpt_id: ExcerptId,
19891 buffer: &Entity<Buffer>,
19892 buffer_position: text::Anchor,
19893 trigger: CompletionContext,
19894 window: &mut Window,
19895 cx: &mut Context<Editor>,
19896 ) -> Task<Result<Option<Vec<Completion>>>>;
19897
19898 fn resolve_completions(
19899 &self,
19900 buffer: Entity<Buffer>,
19901 completion_indices: Vec<usize>,
19902 completions: Rc<RefCell<Box<[Completion]>>>,
19903 cx: &mut Context<Editor>,
19904 ) -> Task<Result<bool>>;
19905
19906 fn apply_additional_edits_for_completion(
19907 &self,
19908 _buffer: Entity<Buffer>,
19909 _completions: Rc<RefCell<Box<[Completion]>>>,
19910 _completion_index: usize,
19911 _push_to_history: bool,
19912 _cx: &mut Context<Editor>,
19913 ) -> Task<Result<Option<language::Transaction>>> {
19914 Task::ready(Ok(None))
19915 }
19916
19917 fn is_completion_trigger(
19918 &self,
19919 buffer: &Entity<Buffer>,
19920 position: language::Anchor,
19921 text: &str,
19922 trigger_in_words: bool,
19923 cx: &mut Context<Editor>,
19924 ) -> bool;
19925
19926 fn sort_completions(&self) -> bool {
19927 true
19928 }
19929
19930 fn filter_completions(&self) -> bool {
19931 true
19932 }
19933}
19934
19935pub trait CodeActionProvider {
19936 fn id(&self) -> Arc<str>;
19937
19938 fn code_actions(
19939 &self,
19940 buffer: &Entity<Buffer>,
19941 range: Range<text::Anchor>,
19942 window: &mut Window,
19943 cx: &mut App,
19944 ) -> Task<Result<Vec<CodeAction>>>;
19945
19946 fn apply_code_action(
19947 &self,
19948 buffer_handle: Entity<Buffer>,
19949 action: CodeAction,
19950 excerpt_id: ExcerptId,
19951 push_to_history: bool,
19952 window: &mut Window,
19953 cx: &mut App,
19954 ) -> Task<Result<ProjectTransaction>>;
19955}
19956
19957impl CodeActionProvider for Entity<Project> {
19958 fn id(&self) -> Arc<str> {
19959 "project".into()
19960 }
19961
19962 fn code_actions(
19963 &self,
19964 buffer: &Entity<Buffer>,
19965 range: Range<text::Anchor>,
19966 _window: &mut Window,
19967 cx: &mut App,
19968 ) -> Task<Result<Vec<CodeAction>>> {
19969 self.update(cx, |project, cx| {
19970 let code_lens = project.code_lens(buffer, range.clone(), cx);
19971 let code_actions = project.code_actions(buffer, range, None, cx);
19972 cx.background_spawn(async move {
19973 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19974 Ok(code_lens
19975 .context("code lens fetch")?
19976 .into_iter()
19977 .chain(code_actions.context("code action fetch")?)
19978 .collect())
19979 })
19980 })
19981 }
19982
19983 fn apply_code_action(
19984 &self,
19985 buffer_handle: Entity<Buffer>,
19986 action: CodeAction,
19987 _excerpt_id: ExcerptId,
19988 push_to_history: bool,
19989 _window: &mut Window,
19990 cx: &mut App,
19991 ) -> Task<Result<ProjectTransaction>> {
19992 self.update(cx, |project, cx| {
19993 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19994 })
19995 }
19996}
19997
19998fn snippet_completions(
19999 project: &Project,
20000 buffer: &Entity<Buffer>,
20001 buffer_position: text::Anchor,
20002 cx: &mut App,
20003) -> Task<Result<Vec<Completion>>> {
20004 let languages = buffer.read(cx).languages_at(buffer_position);
20005 let snippet_store = project.snippets().read(cx);
20006
20007 let scopes: Vec<_> = languages
20008 .iter()
20009 .filter_map(|language| {
20010 let language_name = language.lsp_id();
20011 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20012
20013 if snippets.is_empty() {
20014 None
20015 } else {
20016 Some((language.default_scope(), snippets))
20017 }
20018 })
20019 .collect();
20020
20021 if scopes.is_empty() {
20022 return Task::ready(Ok(vec![]));
20023 }
20024
20025 let snapshot = buffer.read(cx).text_snapshot();
20026 let chars: String = snapshot
20027 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20028 .collect();
20029 let executor = cx.background_executor().clone();
20030
20031 cx.background_spawn(async move {
20032 let mut all_results: Vec<Completion> = Vec::new();
20033 for (scope, snippets) in scopes.into_iter() {
20034 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20035 let mut last_word = chars
20036 .chars()
20037 .take_while(|c| classifier.is_word(*c))
20038 .collect::<String>();
20039 last_word = last_word.chars().rev().collect();
20040
20041 if last_word.is_empty() {
20042 return Ok(vec![]);
20043 }
20044
20045 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20046 let to_lsp = |point: &text::Anchor| {
20047 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20048 point_to_lsp(end)
20049 };
20050 let lsp_end = to_lsp(&buffer_position);
20051
20052 let candidates = snippets
20053 .iter()
20054 .enumerate()
20055 .flat_map(|(ix, snippet)| {
20056 snippet
20057 .prefix
20058 .iter()
20059 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20060 })
20061 .collect::<Vec<StringMatchCandidate>>();
20062
20063 let mut matches = fuzzy::match_strings(
20064 &candidates,
20065 &last_word,
20066 last_word.chars().any(|c| c.is_uppercase()),
20067 100,
20068 &Default::default(),
20069 executor.clone(),
20070 )
20071 .await;
20072
20073 // Remove all candidates where the query's start does not match the start of any word in the candidate
20074 if let Some(query_start) = last_word.chars().next() {
20075 matches.retain(|string_match| {
20076 split_words(&string_match.string).any(|word| {
20077 // Check that the first codepoint of the word as lowercase matches the first
20078 // codepoint of the query as lowercase
20079 word.chars()
20080 .flat_map(|codepoint| codepoint.to_lowercase())
20081 .zip(query_start.to_lowercase())
20082 .all(|(word_cp, query_cp)| word_cp == query_cp)
20083 })
20084 });
20085 }
20086
20087 let matched_strings = matches
20088 .into_iter()
20089 .map(|m| m.string)
20090 .collect::<HashSet<_>>();
20091
20092 let mut result: Vec<Completion> = snippets
20093 .iter()
20094 .filter_map(|snippet| {
20095 let matching_prefix = snippet
20096 .prefix
20097 .iter()
20098 .find(|prefix| matched_strings.contains(*prefix))?;
20099 let start = as_offset - last_word.len();
20100 let start = snapshot.anchor_before(start);
20101 let range = start..buffer_position;
20102 let lsp_start = to_lsp(&start);
20103 let lsp_range = lsp::Range {
20104 start: lsp_start,
20105 end: lsp_end,
20106 };
20107 Some(Completion {
20108 replace_range: range,
20109 new_text: snippet.body.clone(),
20110 source: CompletionSource::Lsp {
20111 insert_range: None,
20112 server_id: LanguageServerId(usize::MAX),
20113 resolved: true,
20114 lsp_completion: Box::new(lsp::CompletionItem {
20115 label: snippet.prefix.first().unwrap().clone(),
20116 kind: Some(CompletionItemKind::SNIPPET),
20117 label_details: snippet.description.as_ref().map(|description| {
20118 lsp::CompletionItemLabelDetails {
20119 detail: Some(description.clone()),
20120 description: None,
20121 }
20122 }),
20123 insert_text_format: Some(InsertTextFormat::SNIPPET),
20124 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20125 lsp::InsertReplaceEdit {
20126 new_text: snippet.body.clone(),
20127 insert: lsp_range,
20128 replace: lsp_range,
20129 },
20130 )),
20131 filter_text: Some(snippet.body.clone()),
20132 sort_text: Some(char::MAX.to_string()),
20133 ..lsp::CompletionItem::default()
20134 }),
20135 lsp_defaults: None,
20136 },
20137 label: CodeLabel {
20138 text: matching_prefix.clone(),
20139 runs: Vec::new(),
20140 filter_range: 0..matching_prefix.len(),
20141 },
20142 icon_path: None,
20143 documentation: Some(
20144 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20145 single_line: snippet.name.clone().into(),
20146 plain_text: snippet
20147 .description
20148 .clone()
20149 .map(|description| description.into()),
20150 },
20151 ),
20152 insert_text_mode: None,
20153 confirm: None,
20154 })
20155 })
20156 .collect();
20157
20158 all_results.append(&mut result);
20159 }
20160
20161 Ok(all_results)
20162 })
20163}
20164
20165impl CompletionProvider for Entity<Project> {
20166 fn completions(
20167 &self,
20168 _excerpt_id: ExcerptId,
20169 buffer: &Entity<Buffer>,
20170 buffer_position: text::Anchor,
20171 options: CompletionContext,
20172 _window: &mut Window,
20173 cx: &mut Context<Editor>,
20174 ) -> Task<Result<Option<Vec<Completion>>>> {
20175 self.update(cx, |project, cx| {
20176 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20177 let project_completions = project.completions(buffer, buffer_position, options, cx);
20178 cx.background_spawn(async move {
20179 let snippets_completions = snippets.await?;
20180 match project_completions.await? {
20181 Some(mut completions) => {
20182 completions.extend(snippets_completions);
20183 Ok(Some(completions))
20184 }
20185 None => {
20186 if snippets_completions.is_empty() {
20187 Ok(None)
20188 } else {
20189 Ok(Some(snippets_completions))
20190 }
20191 }
20192 }
20193 })
20194 })
20195 }
20196
20197 fn resolve_completions(
20198 &self,
20199 buffer: Entity<Buffer>,
20200 completion_indices: Vec<usize>,
20201 completions: Rc<RefCell<Box<[Completion]>>>,
20202 cx: &mut Context<Editor>,
20203 ) -> Task<Result<bool>> {
20204 self.update(cx, |project, cx| {
20205 project.lsp_store().update(cx, |lsp_store, cx| {
20206 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20207 })
20208 })
20209 }
20210
20211 fn apply_additional_edits_for_completion(
20212 &self,
20213 buffer: Entity<Buffer>,
20214 completions: Rc<RefCell<Box<[Completion]>>>,
20215 completion_index: usize,
20216 push_to_history: bool,
20217 cx: &mut Context<Editor>,
20218 ) -> Task<Result<Option<language::Transaction>>> {
20219 self.update(cx, |project, cx| {
20220 project.lsp_store().update(cx, |lsp_store, cx| {
20221 lsp_store.apply_additional_edits_for_completion(
20222 buffer,
20223 completions,
20224 completion_index,
20225 push_to_history,
20226 cx,
20227 )
20228 })
20229 })
20230 }
20231
20232 fn is_completion_trigger(
20233 &self,
20234 buffer: &Entity<Buffer>,
20235 position: language::Anchor,
20236 text: &str,
20237 trigger_in_words: bool,
20238 cx: &mut Context<Editor>,
20239 ) -> bool {
20240 let mut chars = text.chars();
20241 let char = if let Some(char) = chars.next() {
20242 char
20243 } else {
20244 return false;
20245 };
20246 if chars.next().is_some() {
20247 return false;
20248 }
20249
20250 let buffer = buffer.read(cx);
20251 let snapshot = buffer.snapshot();
20252 if !snapshot.settings_at(position, cx).show_completions_on_input {
20253 return false;
20254 }
20255 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20256 if trigger_in_words && classifier.is_word(char) {
20257 return true;
20258 }
20259
20260 buffer.completion_triggers().contains(text)
20261 }
20262}
20263
20264impl SemanticsProvider for Entity<Project> {
20265 fn hover(
20266 &self,
20267 buffer: &Entity<Buffer>,
20268 position: text::Anchor,
20269 cx: &mut App,
20270 ) -> Option<Task<Vec<project::Hover>>> {
20271 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20272 }
20273
20274 fn document_highlights(
20275 &self,
20276 buffer: &Entity<Buffer>,
20277 position: text::Anchor,
20278 cx: &mut App,
20279 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20280 Some(self.update(cx, |project, cx| {
20281 project.document_highlights(buffer, position, cx)
20282 }))
20283 }
20284
20285 fn definitions(
20286 &self,
20287 buffer: &Entity<Buffer>,
20288 position: text::Anchor,
20289 kind: GotoDefinitionKind,
20290 cx: &mut App,
20291 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20292 Some(self.update(cx, |project, cx| match kind {
20293 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20294 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20295 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20296 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20297 }))
20298 }
20299
20300 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20301 // TODO: make this work for remote projects
20302 self.update(cx, |project, cx| {
20303 if project
20304 .active_debug_session(cx)
20305 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20306 {
20307 return true;
20308 }
20309
20310 buffer.update(cx, |buffer, cx| {
20311 project.any_language_server_supports_inlay_hints(buffer, cx)
20312 })
20313 })
20314 }
20315
20316 fn inline_values(
20317 &self,
20318 buffer_handle: Entity<Buffer>,
20319
20320 range: Range<text::Anchor>,
20321 cx: &mut App,
20322 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20323 self.update(cx, |project, cx| {
20324 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20325
20326 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20327 })
20328 }
20329
20330 fn inlay_hints(
20331 &self,
20332 buffer_handle: Entity<Buffer>,
20333 range: Range<text::Anchor>,
20334 cx: &mut App,
20335 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20336 Some(self.update(cx, |project, cx| {
20337 project.inlay_hints(buffer_handle, range, cx)
20338 }))
20339 }
20340
20341 fn resolve_inlay_hint(
20342 &self,
20343 hint: InlayHint,
20344 buffer_handle: Entity<Buffer>,
20345 server_id: LanguageServerId,
20346 cx: &mut App,
20347 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20348 Some(self.update(cx, |project, cx| {
20349 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20350 }))
20351 }
20352
20353 fn range_for_rename(
20354 &self,
20355 buffer: &Entity<Buffer>,
20356 position: text::Anchor,
20357 cx: &mut App,
20358 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20359 Some(self.update(cx, |project, cx| {
20360 let buffer = buffer.clone();
20361 let task = project.prepare_rename(buffer.clone(), position, cx);
20362 cx.spawn(async move |_, cx| {
20363 Ok(match task.await? {
20364 PrepareRenameResponse::Success(range) => Some(range),
20365 PrepareRenameResponse::InvalidPosition => None,
20366 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20367 // Fallback on using TreeSitter info to determine identifier range
20368 buffer.update(cx, |buffer, _| {
20369 let snapshot = buffer.snapshot();
20370 let (range, kind) = snapshot.surrounding_word(position);
20371 if kind != Some(CharKind::Word) {
20372 return None;
20373 }
20374 Some(
20375 snapshot.anchor_before(range.start)
20376 ..snapshot.anchor_after(range.end),
20377 )
20378 })?
20379 }
20380 })
20381 })
20382 }))
20383 }
20384
20385 fn perform_rename(
20386 &self,
20387 buffer: &Entity<Buffer>,
20388 position: text::Anchor,
20389 new_name: String,
20390 cx: &mut App,
20391 ) -> Option<Task<Result<ProjectTransaction>>> {
20392 Some(self.update(cx, |project, cx| {
20393 project.perform_rename(buffer.clone(), position, new_name, cx)
20394 }))
20395 }
20396}
20397
20398fn inlay_hint_settings(
20399 location: Anchor,
20400 snapshot: &MultiBufferSnapshot,
20401 cx: &mut Context<Editor>,
20402) -> InlayHintSettings {
20403 let file = snapshot.file_at(location);
20404 let language = snapshot.language_at(location).map(|l| l.name());
20405 language_settings(language, file, cx).inlay_hints
20406}
20407
20408fn consume_contiguous_rows(
20409 contiguous_row_selections: &mut Vec<Selection<Point>>,
20410 selection: &Selection<Point>,
20411 display_map: &DisplaySnapshot,
20412 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20413) -> (MultiBufferRow, MultiBufferRow) {
20414 contiguous_row_selections.push(selection.clone());
20415 let start_row = MultiBufferRow(selection.start.row);
20416 let mut end_row = ending_row(selection, display_map);
20417
20418 while let Some(next_selection) = selections.peek() {
20419 if next_selection.start.row <= end_row.0 {
20420 end_row = ending_row(next_selection, display_map);
20421 contiguous_row_selections.push(selections.next().unwrap().clone());
20422 } else {
20423 break;
20424 }
20425 }
20426 (start_row, end_row)
20427}
20428
20429fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20430 if next_selection.end.column > 0 || next_selection.is_empty() {
20431 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20432 } else {
20433 MultiBufferRow(next_selection.end.row)
20434 }
20435}
20436
20437impl EditorSnapshot {
20438 pub fn remote_selections_in_range<'a>(
20439 &'a self,
20440 range: &'a Range<Anchor>,
20441 collaboration_hub: &dyn CollaborationHub,
20442 cx: &'a App,
20443 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20444 let participant_names = collaboration_hub.user_names(cx);
20445 let participant_indices = collaboration_hub.user_participant_indices(cx);
20446 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20447 let collaborators_by_replica_id = collaborators_by_peer_id
20448 .values()
20449 .map(|collaborator| (collaborator.replica_id, collaborator))
20450 .collect::<HashMap<_, _>>();
20451 self.buffer_snapshot
20452 .selections_in_range(range, false)
20453 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20454 if replica_id == AGENT_REPLICA_ID {
20455 Some(RemoteSelection {
20456 replica_id,
20457 selection,
20458 cursor_shape,
20459 line_mode,
20460 collaborator_id: CollaboratorId::Agent,
20461 user_name: Some("Agent".into()),
20462 color: cx.theme().players().agent(),
20463 })
20464 } else {
20465 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20466 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20467 let user_name = participant_names.get(&collaborator.user_id).cloned();
20468 Some(RemoteSelection {
20469 replica_id,
20470 selection,
20471 cursor_shape,
20472 line_mode,
20473 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20474 user_name,
20475 color: if let Some(index) = participant_index {
20476 cx.theme().players().color_for_participant(index.0)
20477 } else {
20478 cx.theme().players().absent()
20479 },
20480 })
20481 }
20482 })
20483 }
20484
20485 pub fn hunks_for_ranges(
20486 &self,
20487 ranges: impl IntoIterator<Item = Range<Point>>,
20488 ) -> Vec<MultiBufferDiffHunk> {
20489 let mut hunks = Vec::new();
20490 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20491 HashMap::default();
20492 for query_range in ranges {
20493 let query_rows =
20494 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20495 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20496 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20497 ) {
20498 // Include deleted hunks that are adjacent to the query range, because
20499 // otherwise they would be missed.
20500 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20501 if hunk.status().is_deleted() {
20502 intersects_range |= hunk.row_range.start == query_rows.end;
20503 intersects_range |= hunk.row_range.end == query_rows.start;
20504 }
20505 if intersects_range {
20506 if !processed_buffer_rows
20507 .entry(hunk.buffer_id)
20508 .or_default()
20509 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20510 {
20511 continue;
20512 }
20513 hunks.push(hunk);
20514 }
20515 }
20516 }
20517
20518 hunks
20519 }
20520
20521 fn display_diff_hunks_for_rows<'a>(
20522 &'a self,
20523 display_rows: Range<DisplayRow>,
20524 folded_buffers: &'a HashSet<BufferId>,
20525 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20526 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20527 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20528
20529 self.buffer_snapshot
20530 .diff_hunks_in_range(buffer_start..buffer_end)
20531 .filter_map(|hunk| {
20532 if folded_buffers.contains(&hunk.buffer_id) {
20533 return None;
20534 }
20535
20536 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20537 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20538
20539 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20540 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20541
20542 let display_hunk = if hunk_display_start.column() != 0 {
20543 DisplayDiffHunk::Folded {
20544 display_row: hunk_display_start.row(),
20545 }
20546 } else {
20547 let mut end_row = hunk_display_end.row();
20548 if hunk_display_end.column() > 0 {
20549 end_row.0 += 1;
20550 }
20551 let is_created_file = hunk.is_created_file();
20552 DisplayDiffHunk::Unfolded {
20553 status: hunk.status(),
20554 diff_base_byte_range: hunk.diff_base_byte_range,
20555 display_row_range: hunk_display_start.row()..end_row,
20556 multi_buffer_range: Anchor::range_in_buffer(
20557 hunk.excerpt_id,
20558 hunk.buffer_id,
20559 hunk.buffer_range,
20560 ),
20561 is_created_file,
20562 }
20563 };
20564
20565 Some(display_hunk)
20566 })
20567 }
20568
20569 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20570 self.display_snapshot.buffer_snapshot.language_at(position)
20571 }
20572
20573 pub fn is_focused(&self) -> bool {
20574 self.is_focused
20575 }
20576
20577 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20578 self.placeholder_text.as_ref()
20579 }
20580
20581 pub fn scroll_position(&self) -> gpui::Point<f32> {
20582 self.scroll_anchor.scroll_position(&self.display_snapshot)
20583 }
20584
20585 fn gutter_dimensions(
20586 &self,
20587 font_id: FontId,
20588 font_size: Pixels,
20589 max_line_number_width: Pixels,
20590 cx: &App,
20591 ) -> Option<GutterDimensions> {
20592 if !self.show_gutter {
20593 return None;
20594 }
20595
20596 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20597 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20598
20599 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20600 matches!(
20601 ProjectSettings::get_global(cx).git.git_gutter,
20602 Some(GitGutterSetting::TrackedFiles)
20603 )
20604 });
20605 let gutter_settings = EditorSettings::get_global(cx).gutter;
20606 let show_line_numbers = self
20607 .show_line_numbers
20608 .unwrap_or(gutter_settings.line_numbers);
20609 let line_gutter_width = if show_line_numbers {
20610 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20611 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20612 max_line_number_width.max(min_width_for_number_on_gutter)
20613 } else {
20614 0.0.into()
20615 };
20616
20617 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20618 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20619
20620 let git_blame_entries_width =
20621 self.git_blame_gutter_max_author_length
20622 .map(|max_author_length| {
20623 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20624 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20625
20626 /// The number of characters to dedicate to gaps and margins.
20627 const SPACING_WIDTH: usize = 4;
20628
20629 let max_char_count = max_author_length.min(renderer.max_author_length())
20630 + ::git::SHORT_SHA_LENGTH
20631 + MAX_RELATIVE_TIMESTAMP.len()
20632 + SPACING_WIDTH;
20633
20634 em_advance * max_char_count
20635 });
20636
20637 let is_singleton = self.buffer_snapshot.is_singleton();
20638
20639 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20640 left_padding += if !is_singleton {
20641 em_width * 4.0
20642 } else if show_runnables || show_breakpoints {
20643 em_width * 3.0
20644 } else if show_git_gutter && show_line_numbers {
20645 em_width * 2.0
20646 } else if show_git_gutter || show_line_numbers {
20647 em_width
20648 } else {
20649 px(0.)
20650 };
20651
20652 let shows_folds = is_singleton && gutter_settings.folds;
20653
20654 let right_padding = if shows_folds && show_line_numbers {
20655 em_width * 4.0
20656 } else if shows_folds || (!is_singleton && show_line_numbers) {
20657 em_width * 3.0
20658 } else if show_line_numbers {
20659 em_width
20660 } else {
20661 px(0.)
20662 };
20663
20664 Some(GutterDimensions {
20665 left_padding,
20666 right_padding,
20667 width: line_gutter_width + left_padding + right_padding,
20668 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20669 git_blame_entries_width,
20670 })
20671 }
20672
20673 pub fn render_crease_toggle(
20674 &self,
20675 buffer_row: MultiBufferRow,
20676 row_contains_cursor: bool,
20677 editor: Entity<Editor>,
20678 window: &mut Window,
20679 cx: &mut App,
20680 ) -> Option<AnyElement> {
20681 let folded = self.is_line_folded(buffer_row);
20682 let mut is_foldable = false;
20683
20684 if let Some(crease) = self
20685 .crease_snapshot
20686 .query_row(buffer_row, &self.buffer_snapshot)
20687 {
20688 is_foldable = true;
20689 match crease {
20690 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20691 if let Some(render_toggle) = render_toggle {
20692 let toggle_callback =
20693 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20694 if folded {
20695 editor.update(cx, |editor, cx| {
20696 editor.fold_at(buffer_row, window, cx)
20697 });
20698 } else {
20699 editor.update(cx, |editor, cx| {
20700 editor.unfold_at(buffer_row, window, cx)
20701 });
20702 }
20703 });
20704 return Some((render_toggle)(
20705 buffer_row,
20706 folded,
20707 toggle_callback,
20708 window,
20709 cx,
20710 ));
20711 }
20712 }
20713 }
20714 }
20715
20716 is_foldable |= self.starts_indent(buffer_row);
20717
20718 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20719 Some(
20720 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20721 .toggle_state(folded)
20722 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20723 if folded {
20724 this.unfold_at(buffer_row, window, cx);
20725 } else {
20726 this.fold_at(buffer_row, window, cx);
20727 }
20728 }))
20729 .into_any_element(),
20730 )
20731 } else {
20732 None
20733 }
20734 }
20735
20736 pub fn render_crease_trailer(
20737 &self,
20738 buffer_row: MultiBufferRow,
20739 window: &mut Window,
20740 cx: &mut App,
20741 ) -> Option<AnyElement> {
20742 let folded = self.is_line_folded(buffer_row);
20743 if let Crease::Inline { render_trailer, .. } = self
20744 .crease_snapshot
20745 .query_row(buffer_row, &self.buffer_snapshot)?
20746 {
20747 let render_trailer = render_trailer.as_ref()?;
20748 Some(render_trailer(buffer_row, folded, window, cx))
20749 } else {
20750 None
20751 }
20752 }
20753}
20754
20755impl Deref for EditorSnapshot {
20756 type Target = DisplaySnapshot;
20757
20758 fn deref(&self) -> &Self::Target {
20759 &self.display_snapshot
20760 }
20761}
20762
20763#[derive(Clone, Debug, PartialEq, Eq)]
20764pub enum EditorEvent {
20765 InputIgnored {
20766 text: Arc<str>,
20767 },
20768 InputHandled {
20769 utf16_range_to_replace: Option<Range<isize>>,
20770 text: Arc<str>,
20771 },
20772 ExcerptsAdded {
20773 buffer: Entity<Buffer>,
20774 predecessor: ExcerptId,
20775 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20776 },
20777 ExcerptsRemoved {
20778 ids: Vec<ExcerptId>,
20779 removed_buffer_ids: Vec<BufferId>,
20780 },
20781 BufferFoldToggled {
20782 ids: Vec<ExcerptId>,
20783 folded: bool,
20784 },
20785 ExcerptsEdited {
20786 ids: Vec<ExcerptId>,
20787 },
20788 ExcerptsExpanded {
20789 ids: Vec<ExcerptId>,
20790 },
20791 BufferEdited,
20792 Edited {
20793 transaction_id: clock::Lamport,
20794 },
20795 Reparsed(BufferId),
20796 Focused,
20797 FocusedIn,
20798 Blurred,
20799 DirtyChanged,
20800 Saved,
20801 TitleChanged,
20802 DiffBaseChanged,
20803 SelectionsChanged {
20804 local: bool,
20805 },
20806 ScrollPositionChanged {
20807 local: bool,
20808 autoscroll: bool,
20809 },
20810 Closed,
20811 TransactionUndone {
20812 transaction_id: clock::Lamport,
20813 },
20814 TransactionBegun {
20815 transaction_id: clock::Lamport,
20816 },
20817 Reloaded,
20818 CursorShapeChanged,
20819 PushedToNavHistory {
20820 anchor: Anchor,
20821 is_deactivate: bool,
20822 },
20823}
20824
20825impl EventEmitter<EditorEvent> for Editor {}
20826
20827impl Focusable for Editor {
20828 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20829 self.focus_handle.clone()
20830 }
20831}
20832
20833impl Render for Editor {
20834 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20835 let settings = ThemeSettings::get_global(cx);
20836
20837 let mut text_style = match self.mode {
20838 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20839 color: cx.theme().colors().editor_foreground,
20840 font_family: settings.ui_font.family.clone(),
20841 font_features: settings.ui_font.features.clone(),
20842 font_fallbacks: settings.ui_font.fallbacks.clone(),
20843 font_size: rems(0.875).into(),
20844 font_weight: settings.ui_font.weight,
20845 line_height: relative(settings.buffer_line_height.value()),
20846 ..Default::default()
20847 },
20848 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20849 color: cx.theme().colors().editor_foreground,
20850 font_family: settings.buffer_font.family.clone(),
20851 font_features: settings.buffer_font.features.clone(),
20852 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20853 font_size: settings.buffer_font_size(cx).into(),
20854 font_weight: settings.buffer_font.weight,
20855 line_height: relative(settings.buffer_line_height.value()),
20856 ..Default::default()
20857 },
20858 };
20859 if let Some(text_style_refinement) = &self.text_style_refinement {
20860 text_style.refine(text_style_refinement)
20861 }
20862
20863 let background = match self.mode {
20864 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20865 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20866 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20867 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20868 };
20869
20870 EditorElement::new(
20871 &cx.entity(),
20872 EditorStyle {
20873 background,
20874 local_player: cx.theme().players().local(),
20875 text: text_style,
20876 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20877 syntax: cx.theme().syntax().clone(),
20878 status: cx.theme().status().clone(),
20879 inlay_hints_style: make_inlay_hints_style(cx),
20880 inline_completion_styles: make_suggestion_styles(cx),
20881 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20882 show_underlines: !self.mode.is_minimap(),
20883 },
20884 )
20885 }
20886}
20887
20888impl EntityInputHandler for Editor {
20889 fn text_for_range(
20890 &mut self,
20891 range_utf16: Range<usize>,
20892 adjusted_range: &mut Option<Range<usize>>,
20893 _: &mut Window,
20894 cx: &mut Context<Self>,
20895 ) -> Option<String> {
20896 let snapshot = self.buffer.read(cx).read(cx);
20897 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20898 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20899 if (start.0..end.0) != range_utf16 {
20900 adjusted_range.replace(start.0..end.0);
20901 }
20902 Some(snapshot.text_for_range(start..end).collect())
20903 }
20904
20905 fn selected_text_range(
20906 &mut self,
20907 ignore_disabled_input: bool,
20908 _: &mut Window,
20909 cx: &mut Context<Self>,
20910 ) -> Option<UTF16Selection> {
20911 // Prevent the IME menu from appearing when holding down an alphabetic key
20912 // while input is disabled.
20913 if !ignore_disabled_input && !self.input_enabled {
20914 return None;
20915 }
20916
20917 let selection = self.selections.newest::<OffsetUtf16>(cx);
20918 let range = selection.range();
20919
20920 Some(UTF16Selection {
20921 range: range.start.0..range.end.0,
20922 reversed: selection.reversed,
20923 })
20924 }
20925
20926 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20927 let snapshot = self.buffer.read(cx).read(cx);
20928 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20929 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20930 }
20931
20932 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20933 self.clear_highlights::<InputComposition>(cx);
20934 self.ime_transaction.take();
20935 }
20936
20937 fn replace_text_in_range(
20938 &mut self,
20939 range_utf16: Option<Range<usize>>,
20940 text: &str,
20941 window: &mut Window,
20942 cx: &mut Context<Self>,
20943 ) {
20944 if !self.input_enabled {
20945 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20946 return;
20947 }
20948
20949 self.transact(window, cx, |this, window, cx| {
20950 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20951 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20952 Some(this.selection_replacement_ranges(range_utf16, cx))
20953 } else {
20954 this.marked_text_ranges(cx)
20955 };
20956
20957 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20958 let newest_selection_id = this.selections.newest_anchor().id;
20959 this.selections
20960 .all::<OffsetUtf16>(cx)
20961 .iter()
20962 .zip(ranges_to_replace.iter())
20963 .find_map(|(selection, range)| {
20964 if selection.id == newest_selection_id {
20965 Some(
20966 (range.start.0 as isize - selection.head().0 as isize)
20967 ..(range.end.0 as isize - selection.head().0 as isize),
20968 )
20969 } else {
20970 None
20971 }
20972 })
20973 });
20974
20975 cx.emit(EditorEvent::InputHandled {
20976 utf16_range_to_replace: range_to_replace,
20977 text: text.into(),
20978 });
20979
20980 if let Some(new_selected_ranges) = new_selected_ranges {
20981 this.change_selections(None, window, cx, |selections| {
20982 selections.select_ranges(new_selected_ranges)
20983 });
20984 this.backspace(&Default::default(), window, cx);
20985 }
20986
20987 this.handle_input(text, window, cx);
20988 });
20989
20990 if let Some(transaction) = self.ime_transaction {
20991 self.buffer.update(cx, |buffer, cx| {
20992 buffer.group_until_transaction(transaction, cx);
20993 });
20994 }
20995
20996 self.unmark_text(window, cx);
20997 }
20998
20999 fn replace_and_mark_text_in_range(
21000 &mut self,
21001 range_utf16: Option<Range<usize>>,
21002 text: &str,
21003 new_selected_range_utf16: Option<Range<usize>>,
21004 window: &mut Window,
21005 cx: &mut Context<Self>,
21006 ) {
21007 if !self.input_enabled {
21008 return;
21009 }
21010
21011 let transaction = self.transact(window, cx, |this, window, cx| {
21012 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21013 let snapshot = this.buffer.read(cx).read(cx);
21014 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21015 for marked_range in &mut marked_ranges {
21016 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21017 marked_range.start.0 += relative_range_utf16.start;
21018 marked_range.start =
21019 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21020 marked_range.end =
21021 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
21022 }
21023 }
21024 Some(marked_ranges)
21025 } else if let Some(range_utf16) = range_utf16 {
21026 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21027 Some(this.selection_replacement_ranges(range_utf16, cx))
21028 } else {
21029 None
21030 };
21031
21032 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
21033 let newest_selection_id = this.selections.newest_anchor().id;
21034 this.selections
21035 .all::<OffsetUtf16>(cx)
21036 .iter()
21037 .zip(ranges_to_replace.iter())
21038 .find_map(|(selection, range)| {
21039 if selection.id == newest_selection_id {
21040 Some(
21041 (range.start.0 as isize - selection.head().0 as isize)
21042 ..(range.end.0 as isize - selection.head().0 as isize),
21043 )
21044 } else {
21045 None
21046 }
21047 })
21048 });
21049
21050 cx.emit(EditorEvent::InputHandled {
21051 utf16_range_to_replace: range_to_replace,
21052 text: text.into(),
21053 });
21054
21055 if let Some(ranges) = ranges_to_replace {
21056 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21057 }
21058
21059 let marked_ranges = {
21060 let snapshot = this.buffer.read(cx).read(cx);
21061 this.selections
21062 .disjoint_anchors()
21063 .iter()
21064 .map(|selection| {
21065 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21066 })
21067 .collect::<Vec<_>>()
21068 };
21069
21070 if text.is_empty() {
21071 this.unmark_text(window, cx);
21072 } else {
21073 this.highlight_text::<InputComposition>(
21074 marked_ranges.clone(),
21075 HighlightStyle {
21076 underline: Some(UnderlineStyle {
21077 thickness: px(1.),
21078 color: None,
21079 wavy: false,
21080 }),
21081 ..Default::default()
21082 },
21083 cx,
21084 );
21085 }
21086
21087 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21088 let use_autoclose = this.use_autoclose;
21089 let use_auto_surround = this.use_auto_surround;
21090 this.set_use_autoclose(false);
21091 this.set_use_auto_surround(false);
21092 this.handle_input(text, window, cx);
21093 this.set_use_autoclose(use_autoclose);
21094 this.set_use_auto_surround(use_auto_surround);
21095
21096 if let Some(new_selected_range) = new_selected_range_utf16 {
21097 let snapshot = this.buffer.read(cx).read(cx);
21098 let new_selected_ranges = marked_ranges
21099 .into_iter()
21100 .map(|marked_range| {
21101 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21102 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21103 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21104 snapshot.clip_offset_utf16(new_start, Bias::Left)
21105 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21106 })
21107 .collect::<Vec<_>>();
21108
21109 drop(snapshot);
21110 this.change_selections(None, window, cx, |selections| {
21111 selections.select_ranges(new_selected_ranges)
21112 });
21113 }
21114 });
21115
21116 self.ime_transaction = self.ime_transaction.or(transaction);
21117 if let Some(transaction) = self.ime_transaction {
21118 self.buffer.update(cx, |buffer, cx| {
21119 buffer.group_until_transaction(transaction, cx);
21120 });
21121 }
21122
21123 if self.text_highlights::<InputComposition>(cx).is_none() {
21124 self.ime_transaction.take();
21125 }
21126 }
21127
21128 fn bounds_for_range(
21129 &mut self,
21130 range_utf16: Range<usize>,
21131 element_bounds: gpui::Bounds<Pixels>,
21132 window: &mut Window,
21133 cx: &mut Context<Self>,
21134 ) -> Option<gpui::Bounds<Pixels>> {
21135 let text_layout_details = self.text_layout_details(window);
21136 let gpui::Size {
21137 width: em_width,
21138 height: line_height,
21139 } = self.character_size(window);
21140
21141 let snapshot = self.snapshot(window, cx);
21142 let scroll_position = snapshot.scroll_position();
21143 let scroll_left = scroll_position.x * em_width;
21144
21145 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21146 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21147 + self.gutter_dimensions.width
21148 + self.gutter_dimensions.margin;
21149 let y = line_height * (start.row().as_f32() - scroll_position.y);
21150
21151 Some(Bounds {
21152 origin: element_bounds.origin + point(x, y),
21153 size: size(em_width, line_height),
21154 })
21155 }
21156
21157 fn character_index_for_point(
21158 &mut self,
21159 point: gpui::Point<Pixels>,
21160 _window: &mut Window,
21161 _cx: &mut Context<Self>,
21162 ) -> Option<usize> {
21163 let position_map = self.last_position_map.as_ref()?;
21164 if !position_map.text_hitbox.contains(&point) {
21165 return None;
21166 }
21167 let display_point = position_map.point_for_position(point).previous_valid;
21168 let anchor = position_map
21169 .snapshot
21170 .display_point_to_anchor(display_point, Bias::Left);
21171 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21172 Some(utf16_offset.0)
21173 }
21174}
21175
21176trait SelectionExt {
21177 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21178 fn spanned_rows(
21179 &self,
21180 include_end_if_at_line_start: bool,
21181 map: &DisplaySnapshot,
21182 ) -> Range<MultiBufferRow>;
21183}
21184
21185impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21186 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21187 let start = self
21188 .start
21189 .to_point(&map.buffer_snapshot)
21190 .to_display_point(map);
21191 let end = self
21192 .end
21193 .to_point(&map.buffer_snapshot)
21194 .to_display_point(map);
21195 if self.reversed {
21196 end..start
21197 } else {
21198 start..end
21199 }
21200 }
21201
21202 fn spanned_rows(
21203 &self,
21204 include_end_if_at_line_start: bool,
21205 map: &DisplaySnapshot,
21206 ) -> Range<MultiBufferRow> {
21207 let start = self.start.to_point(&map.buffer_snapshot);
21208 let mut end = self.end.to_point(&map.buffer_snapshot);
21209 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21210 end.row -= 1;
21211 }
21212
21213 let buffer_start = map.prev_line_boundary(start).0;
21214 let buffer_end = map.next_line_boundary(end).0;
21215 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21216 }
21217}
21218
21219impl<T: InvalidationRegion> InvalidationStack<T> {
21220 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21221 where
21222 S: Clone + ToOffset,
21223 {
21224 while let Some(region) = self.last() {
21225 let all_selections_inside_invalidation_ranges =
21226 if selections.len() == region.ranges().len() {
21227 selections
21228 .iter()
21229 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21230 .all(|(selection, invalidation_range)| {
21231 let head = selection.head().to_offset(buffer);
21232 invalidation_range.start <= head && invalidation_range.end >= head
21233 })
21234 } else {
21235 false
21236 };
21237
21238 if all_selections_inside_invalidation_ranges {
21239 break;
21240 } else {
21241 self.pop();
21242 }
21243 }
21244 }
21245}
21246
21247impl<T> Default for InvalidationStack<T> {
21248 fn default() -> Self {
21249 Self(Default::default())
21250 }
21251}
21252
21253impl<T> Deref for InvalidationStack<T> {
21254 type Target = Vec<T>;
21255
21256 fn deref(&self) -> &Self::Target {
21257 &self.0
21258 }
21259}
21260
21261impl<T> DerefMut for InvalidationStack<T> {
21262 fn deref_mut(&mut self) -> &mut Self::Target {
21263 &mut self.0
21264 }
21265}
21266
21267impl InvalidationRegion for SnippetState {
21268 fn ranges(&self) -> &[Range<Anchor>] {
21269 &self.ranges[self.active_index]
21270 }
21271}
21272
21273fn inline_completion_edit_text(
21274 current_snapshot: &BufferSnapshot,
21275 edits: &[(Range<Anchor>, String)],
21276 edit_preview: &EditPreview,
21277 include_deletions: bool,
21278 cx: &App,
21279) -> HighlightedText {
21280 let edits = edits
21281 .iter()
21282 .map(|(anchor, text)| {
21283 (
21284 anchor.start.text_anchor..anchor.end.text_anchor,
21285 text.clone(),
21286 )
21287 })
21288 .collect::<Vec<_>>();
21289
21290 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21291}
21292
21293pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21294 match severity {
21295 lsp::DiagnosticSeverity::ERROR => colors.error,
21296 lsp::DiagnosticSeverity::WARNING => colors.warning,
21297 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21298 lsp::DiagnosticSeverity::HINT => colors.info,
21299 _ => colors.ignored,
21300 }
21301}
21302
21303pub fn styled_runs_for_code_label<'a>(
21304 label: &'a CodeLabel,
21305 syntax_theme: &'a theme::SyntaxTheme,
21306) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21307 let fade_out = HighlightStyle {
21308 fade_out: Some(0.35),
21309 ..Default::default()
21310 };
21311
21312 let mut prev_end = label.filter_range.end;
21313 label
21314 .runs
21315 .iter()
21316 .enumerate()
21317 .flat_map(move |(ix, (range, highlight_id))| {
21318 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21319 style
21320 } else {
21321 return Default::default();
21322 };
21323 let mut muted_style = style;
21324 muted_style.highlight(fade_out);
21325
21326 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21327 if range.start >= label.filter_range.end {
21328 if range.start > prev_end {
21329 runs.push((prev_end..range.start, fade_out));
21330 }
21331 runs.push((range.clone(), muted_style));
21332 } else if range.end <= label.filter_range.end {
21333 runs.push((range.clone(), style));
21334 } else {
21335 runs.push((range.start..label.filter_range.end, style));
21336 runs.push((label.filter_range.end..range.end, muted_style));
21337 }
21338 prev_end = cmp::max(prev_end, range.end);
21339
21340 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21341 runs.push((prev_end..label.text.len(), fade_out));
21342 }
21343
21344 runs
21345 })
21346}
21347
21348pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21349 let mut prev_index = 0;
21350 let mut prev_codepoint: Option<char> = None;
21351 text.char_indices()
21352 .chain([(text.len(), '\0')])
21353 .filter_map(move |(index, codepoint)| {
21354 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21355 let is_boundary = index == text.len()
21356 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21357 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21358 if is_boundary {
21359 let chunk = &text[prev_index..index];
21360 prev_index = index;
21361 Some(chunk)
21362 } else {
21363 None
21364 }
21365 })
21366}
21367
21368pub trait RangeToAnchorExt: Sized {
21369 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21370
21371 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21372 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21373 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21374 }
21375}
21376
21377impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21378 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21379 let start_offset = self.start.to_offset(snapshot);
21380 let end_offset = self.end.to_offset(snapshot);
21381 if start_offset == end_offset {
21382 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21383 } else {
21384 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21385 }
21386 }
21387}
21388
21389pub trait RowExt {
21390 fn as_f32(&self) -> f32;
21391
21392 fn next_row(&self) -> Self;
21393
21394 fn previous_row(&self) -> Self;
21395
21396 fn minus(&self, other: Self) -> u32;
21397}
21398
21399impl RowExt for DisplayRow {
21400 fn as_f32(&self) -> f32 {
21401 self.0 as f32
21402 }
21403
21404 fn next_row(&self) -> Self {
21405 Self(self.0 + 1)
21406 }
21407
21408 fn previous_row(&self) -> Self {
21409 Self(self.0.saturating_sub(1))
21410 }
21411
21412 fn minus(&self, other: Self) -> u32 {
21413 self.0 - other.0
21414 }
21415}
21416
21417impl RowExt for MultiBufferRow {
21418 fn as_f32(&self) -> f32 {
21419 self.0 as f32
21420 }
21421
21422 fn next_row(&self) -> Self {
21423 Self(self.0 + 1)
21424 }
21425
21426 fn previous_row(&self) -> Self {
21427 Self(self.0.saturating_sub(1))
21428 }
21429
21430 fn minus(&self, other: Self) -> u32 {
21431 self.0 - other.0
21432 }
21433}
21434
21435trait RowRangeExt {
21436 type Row;
21437
21438 fn len(&self) -> usize;
21439
21440 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21441}
21442
21443impl RowRangeExt for Range<MultiBufferRow> {
21444 type Row = MultiBufferRow;
21445
21446 fn len(&self) -> usize {
21447 (self.end.0 - self.start.0) as usize
21448 }
21449
21450 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21451 (self.start.0..self.end.0).map(MultiBufferRow)
21452 }
21453}
21454
21455impl RowRangeExt for Range<DisplayRow> {
21456 type Row = DisplayRow;
21457
21458 fn len(&self) -> usize {
21459 (self.end.0 - self.start.0) as usize
21460 }
21461
21462 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21463 (self.start.0..self.end.0).map(DisplayRow)
21464 }
21465}
21466
21467/// If select range has more than one line, we
21468/// just point the cursor to range.start.
21469fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21470 if range.start.row == range.end.row {
21471 range
21472 } else {
21473 range.start..range.start
21474 }
21475}
21476pub struct KillRing(ClipboardItem);
21477impl Global for KillRing {}
21478
21479const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21480
21481enum BreakpointPromptEditAction {
21482 Log,
21483 Condition,
21484 HitCondition,
21485}
21486
21487struct BreakpointPromptEditor {
21488 pub(crate) prompt: Entity<Editor>,
21489 editor: WeakEntity<Editor>,
21490 breakpoint_anchor: Anchor,
21491 breakpoint: Breakpoint,
21492 edit_action: BreakpointPromptEditAction,
21493 block_ids: HashSet<CustomBlockId>,
21494 editor_margins: Arc<Mutex<EditorMargins>>,
21495 _subscriptions: Vec<Subscription>,
21496}
21497
21498impl BreakpointPromptEditor {
21499 const MAX_LINES: u8 = 4;
21500
21501 fn new(
21502 editor: WeakEntity<Editor>,
21503 breakpoint_anchor: Anchor,
21504 breakpoint: Breakpoint,
21505 edit_action: BreakpointPromptEditAction,
21506 window: &mut Window,
21507 cx: &mut Context<Self>,
21508 ) -> Self {
21509 let base_text = match edit_action {
21510 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21511 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21512 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21513 }
21514 .map(|msg| msg.to_string())
21515 .unwrap_or_default();
21516
21517 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21518 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21519
21520 let prompt = cx.new(|cx| {
21521 let mut prompt = Editor::new(
21522 EditorMode::AutoHeight {
21523 max_lines: Self::MAX_LINES as usize,
21524 },
21525 buffer,
21526 None,
21527 window,
21528 cx,
21529 );
21530 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21531 prompt.set_show_cursor_when_unfocused(false, cx);
21532 prompt.set_placeholder_text(
21533 match edit_action {
21534 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21535 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21536 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21537 },
21538 cx,
21539 );
21540
21541 prompt
21542 });
21543
21544 Self {
21545 prompt,
21546 editor,
21547 breakpoint_anchor,
21548 breakpoint,
21549 edit_action,
21550 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21551 block_ids: Default::default(),
21552 _subscriptions: vec![],
21553 }
21554 }
21555
21556 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21557 self.block_ids.extend(block_ids)
21558 }
21559
21560 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21561 if let Some(editor) = self.editor.upgrade() {
21562 let message = self
21563 .prompt
21564 .read(cx)
21565 .buffer
21566 .read(cx)
21567 .as_singleton()
21568 .expect("A multi buffer in breakpoint prompt isn't possible")
21569 .read(cx)
21570 .as_rope()
21571 .to_string();
21572
21573 editor.update(cx, |editor, cx| {
21574 editor.edit_breakpoint_at_anchor(
21575 self.breakpoint_anchor,
21576 self.breakpoint.clone(),
21577 match self.edit_action {
21578 BreakpointPromptEditAction::Log => {
21579 BreakpointEditAction::EditLogMessage(message.into())
21580 }
21581 BreakpointPromptEditAction::Condition => {
21582 BreakpointEditAction::EditCondition(message.into())
21583 }
21584 BreakpointPromptEditAction::HitCondition => {
21585 BreakpointEditAction::EditHitCondition(message.into())
21586 }
21587 },
21588 cx,
21589 );
21590
21591 editor.remove_blocks(self.block_ids.clone(), None, cx);
21592 cx.focus_self(window);
21593 });
21594 }
21595 }
21596
21597 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21598 self.editor
21599 .update(cx, |editor, cx| {
21600 editor.remove_blocks(self.block_ids.clone(), None, cx);
21601 window.focus(&editor.focus_handle);
21602 })
21603 .log_err();
21604 }
21605
21606 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21607 let settings = ThemeSettings::get_global(cx);
21608 let text_style = TextStyle {
21609 color: if self.prompt.read(cx).read_only(cx) {
21610 cx.theme().colors().text_disabled
21611 } else {
21612 cx.theme().colors().text
21613 },
21614 font_family: settings.buffer_font.family.clone(),
21615 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21616 font_size: settings.buffer_font_size(cx).into(),
21617 font_weight: settings.buffer_font.weight,
21618 line_height: relative(settings.buffer_line_height.value()),
21619 ..Default::default()
21620 };
21621 EditorElement::new(
21622 &self.prompt,
21623 EditorStyle {
21624 background: cx.theme().colors().editor_background,
21625 local_player: cx.theme().players().local(),
21626 text: text_style,
21627 ..Default::default()
21628 },
21629 )
21630 }
21631}
21632
21633impl Render for BreakpointPromptEditor {
21634 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21635 let editor_margins = *self.editor_margins.lock();
21636 let gutter_dimensions = editor_margins.gutter;
21637 h_flex()
21638 .key_context("Editor")
21639 .bg(cx.theme().colors().editor_background)
21640 .border_y_1()
21641 .border_color(cx.theme().status().info_border)
21642 .size_full()
21643 .py(window.line_height() / 2.5)
21644 .on_action(cx.listener(Self::confirm))
21645 .on_action(cx.listener(Self::cancel))
21646 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21647 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21648 }
21649}
21650
21651impl Focusable for BreakpointPromptEditor {
21652 fn focus_handle(&self, cx: &App) -> FocusHandle {
21653 self.prompt.focus_handle(cx)
21654 }
21655}
21656
21657fn all_edits_insertions_or_deletions(
21658 edits: &Vec<(Range<Anchor>, String)>,
21659 snapshot: &MultiBufferSnapshot,
21660) -> bool {
21661 let mut all_insertions = true;
21662 let mut all_deletions = true;
21663
21664 for (range, new_text) in edits.iter() {
21665 let range_is_empty = range.to_offset(&snapshot).is_empty();
21666 let text_is_empty = new_text.is_empty();
21667
21668 if range_is_empty != text_is_empty {
21669 if range_is_empty {
21670 all_deletions = false;
21671 } else {
21672 all_insertions = false;
21673 }
21674 } else {
21675 return false;
21676 }
21677
21678 if !all_insertions && !all_deletions {
21679 return false;
21680 }
21681 }
21682 all_insertions || all_deletions
21683}
21684
21685struct MissingEditPredictionKeybindingTooltip;
21686
21687impl Render for MissingEditPredictionKeybindingTooltip {
21688 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21689 ui::tooltip_container(window, cx, |container, _, cx| {
21690 container
21691 .flex_shrink_0()
21692 .max_w_80()
21693 .min_h(rems_from_px(124.))
21694 .justify_between()
21695 .child(
21696 v_flex()
21697 .flex_1()
21698 .text_ui_sm(cx)
21699 .child(Label::new("Conflict with Accept Keybinding"))
21700 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21701 )
21702 .child(
21703 h_flex()
21704 .pb_1()
21705 .gap_1()
21706 .items_end()
21707 .w_full()
21708 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21709 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21710 }))
21711 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21712 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21713 })),
21714 )
21715 })
21716 }
21717}
21718
21719#[derive(Debug, Clone, Copy, PartialEq)]
21720pub struct LineHighlight {
21721 pub background: Background,
21722 pub border: Option<gpui::Hsla>,
21723 pub include_gutter: bool,
21724 pub type_id: Option<TypeId>,
21725}
21726
21727fn render_diff_hunk_controls(
21728 row: u32,
21729 status: &DiffHunkStatus,
21730 hunk_range: Range<Anchor>,
21731 is_created_file: bool,
21732 line_height: Pixels,
21733 editor: &Entity<Editor>,
21734 _window: &mut Window,
21735 cx: &mut App,
21736) -> AnyElement {
21737 h_flex()
21738 .h(line_height)
21739 .mr_1()
21740 .gap_1()
21741 .px_0p5()
21742 .pb_1()
21743 .border_x_1()
21744 .border_b_1()
21745 .border_color(cx.theme().colors().border_variant)
21746 .rounded_b_lg()
21747 .bg(cx.theme().colors().editor_background)
21748 .gap_1()
21749 .occlude()
21750 .shadow_md()
21751 .child(if status.has_secondary_hunk() {
21752 Button::new(("stage", row as u64), "Stage")
21753 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21754 .tooltip({
21755 let focus_handle = editor.focus_handle(cx);
21756 move |window, cx| {
21757 Tooltip::for_action_in(
21758 "Stage Hunk",
21759 &::git::ToggleStaged,
21760 &focus_handle,
21761 window,
21762 cx,
21763 )
21764 }
21765 })
21766 .on_click({
21767 let editor = editor.clone();
21768 move |_event, _window, cx| {
21769 editor.update(cx, |editor, cx| {
21770 editor.stage_or_unstage_diff_hunks(
21771 true,
21772 vec![hunk_range.start..hunk_range.start],
21773 cx,
21774 );
21775 });
21776 }
21777 })
21778 } else {
21779 Button::new(("unstage", row as u64), "Unstage")
21780 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21781 .tooltip({
21782 let focus_handle = editor.focus_handle(cx);
21783 move |window, cx| {
21784 Tooltip::for_action_in(
21785 "Unstage Hunk",
21786 &::git::ToggleStaged,
21787 &focus_handle,
21788 window,
21789 cx,
21790 )
21791 }
21792 })
21793 .on_click({
21794 let editor = editor.clone();
21795 move |_event, _window, cx| {
21796 editor.update(cx, |editor, cx| {
21797 editor.stage_or_unstage_diff_hunks(
21798 false,
21799 vec![hunk_range.start..hunk_range.start],
21800 cx,
21801 );
21802 });
21803 }
21804 })
21805 })
21806 .child(
21807 Button::new(("restore", row as u64), "Restore")
21808 .tooltip({
21809 let focus_handle = editor.focus_handle(cx);
21810 move |window, cx| {
21811 Tooltip::for_action_in(
21812 "Restore Hunk",
21813 &::git::Restore,
21814 &focus_handle,
21815 window,
21816 cx,
21817 )
21818 }
21819 })
21820 .on_click({
21821 let editor = editor.clone();
21822 move |_event, window, cx| {
21823 editor.update(cx, |editor, cx| {
21824 let snapshot = editor.snapshot(window, cx);
21825 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21826 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21827 });
21828 }
21829 })
21830 .disabled(is_created_file),
21831 )
21832 .when(
21833 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21834 |el| {
21835 el.child(
21836 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21837 .shape(IconButtonShape::Square)
21838 .icon_size(IconSize::Small)
21839 // .disabled(!has_multiple_hunks)
21840 .tooltip({
21841 let focus_handle = editor.focus_handle(cx);
21842 move |window, cx| {
21843 Tooltip::for_action_in(
21844 "Next Hunk",
21845 &GoToHunk,
21846 &focus_handle,
21847 window,
21848 cx,
21849 )
21850 }
21851 })
21852 .on_click({
21853 let editor = editor.clone();
21854 move |_event, window, cx| {
21855 editor.update(cx, |editor, cx| {
21856 let snapshot = editor.snapshot(window, cx);
21857 let position =
21858 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21859 editor.go_to_hunk_before_or_after_position(
21860 &snapshot,
21861 position,
21862 Direction::Next,
21863 window,
21864 cx,
21865 );
21866 editor.expand_selected_diff_hunks(cx);
21867 });
21868 }
21869 }),
21870 )
21871 .child(
21872 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21873 .shape(IconButtonShape::Square)
21874 .icon_size(IconSize::Small)
21875 // .disabled(!has_multiple_hunks)
21876 .tooltip({
21877 let focus_handle = editor.focus_handle(cx);
21878 move |window, cx| {
21879 Tooltip::for_action_in(
21880 "Previous Hunk",
21881 &GoToPreviousHunk,
21882 &focus_handle,
21883 window,
21884 cx,
21885 )
21886 }
21887 })
21888 .on_click({
21889 let editor = editor.clone();
21890 move |_event, window, cx| {
21891 editor.update(cx, |editor, cx| {
21892 let snapshot = editor.snapshot(window, cx);
21893 let point =
21894 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21895 editor.go_to_hunk_before_or_after_position(
21896 &snapshot,
21897 point,
21898 Direction::Prev,
21899 window,
21900 cx,
21901 );
21902 editor.expand_selected_diff_hunks(cx);
21903 });
21904 }
21905 }),
21906 )
21907 },
21908 )
21909 .into_any_element()
21910}