1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::StringMatchCandidate;
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use smallvec::smallvec;
142use std::{cell::OnceCell, iter::Peekable, ops::Not};
143use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
144
145pub use lsp::CompletionContext;
146use lsp::{
147 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
148 LanguageServerId, LanguageServerName,
149};
150
151use language::BufferSnapshot;
152pub use lsp_ext::lsp_tasks;
153use movement::TextLayoutDetails;
154pub use multi_buffer::{
155 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
156 RowInfo, ToOffset, ToPoint,
157};
158use multi_buffer::{
159 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
160 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
161};
162use parking_lot::Mutex;
163use project::{
164 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
165 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
166 TaskSourceKind,
167 debugger::breakpoint_store::Breakpoint,
168 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
169 project_settings::{GitGutterSetting, ProjectSettings},
170};
171use rand::prelude::*;
172use rpc::{ErrorExt, proto::*};
173use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
174use selections_collection::{
175 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
176};
177use serde::{Deserialize, Serialize};
178use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
179use smallvec::SmallVec;
180use snippet::Snippet;
181use std::sync::Arc;
182use std::{
183 any::TypeId,
184 borrow::Cow,
185 cell::RefCell,
186 cmp::{self, Ordering, Reverse},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 time::{Duration, Instant},
193};
194pub use sum_tree::Bias;
195use sum_tree::TreeMap;
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
197use theme::{
198 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
199 observe_buffer_font_size_adjustment,
200};
201use ui::{
202 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
203 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
204};
205use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
206use workspace::{
207 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
208 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
209 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
210 item::{ItemHandle, PreviewTabsSettings},
211 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
212 searchable::SearchEvent,
213};
214
215use crate::hover_links::{find_url, find_url_from_range};
216use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
217
218pub const FILE_HEADER_HEIGHT: u32 = 2;
219pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
220pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233
234pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
235pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
236pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
253 alt: true,
254 shift: true,
255 control: false,
256 platform: false,
257 function: false,
258};
259
260struct InlineValueCache {
261 enabled: bool,
262 inlays: Vec<InlayId>,
263 refresh_task: Task<Option<()>>,
264}
265
266impl InlineValueCache {
267 fn new(enabled: bool) -> Self {
268 Self {
269 enabled,
270 inlays: Vec::new(),
271 refresh_task: Task::ready(None),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
277pub enum InlayId {
278 InlineCompletion(usize),
279 Hint(usize),
280 DebuggerValue(usize),
281}
282
283impl InlayId {
284 fn id(&self) -> usize {
285 match self {
286 Self::InlineCompletion(id) => *id,
287 Self::Hint(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 }
290 }
291}
292
293pub enum ActiveDebugLine {}
294pub enum DebugStackFrameLine {}
295enum DocumentHighlightRead {}
296enum DocumentHighlightWrite {}
297enum InputComposition {}
298enum SelectedTextHighlight {}
299
300pub enum ConflictsOuter {}
301pub enum ConflictsOurs {}
302pub enum ConflictsTheirs {}
303pub enum ConflictsOursMarker {}
304pub enum ConflictsTheirsMarker {}
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 },
330}
331
332pub enum HideMouseCursorOrigin {
333 TypingAction,
334 MovementAction,
335}
336
337pub fn init_settings(cx: &mut App) {
338 EditorSettings::register(cx);
339}
340
341pub fn init(cx: &mut App) {
342 init_settings(cx);
343
344 cx.set_global(GlobalBlameRenderer(Arc::new(())));
345
346 workspace::register_project_item::<Editor>(cx);
347 workspace::FollowableViewRegistry::register::<Editor>(cx);
348 workspace::register_serializable_item::<Editor>(cx);
349
350 cx.observe_new(
351 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
352 workspace.register_action(Editor::new_file);
353 workspace.register_action(Editor::new_file_vertical);
354 workspace.register_action(Editor::new_file_horizontal);
355 workspace.register_action(Editor::cancel_language_server_work);
356 },
357 )
358 .detach();
359
360 cx.on_action(move |_: &workspace::NewFile, cx| {
361 let app_state = workspace::AppState::global(cx);
362 if let Some(app_state) = app_state.upgrade() {
363 workspace::open_new(
364 Default::default(),
365 app_state,
366 cx,
367 |workspace, window, cx| {
368 Editor::new_file(workspace, &Default::default(), window, cx)
369 },
370 )
371 .detach();
372 }
373 });
374 cx.on_action(move |_: &workspace::NewWindow, cx| {
375 let app_state = workspace::AppState::global(cx);
376 if let Some(app_state) = app_state.upgrade() {
377 workspace::open_new(
378 Default::default(),
379 app_state,
380 cx,
381 |workspace, window, cx| {
382 cx.activate(true);
383 Editor::new_file(workspace, &Default::default(), window, cx)
384 },
385 )
386 .detach();
387 }
388 });
389}
390
391pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
392 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
393}
394
395pub trait DiagnosticRenderer {
396 fn render_group(
397 &self,
398 diagnostic_group: Vec<DiagnosticEntry<Point>>,
399 buffer_id: BufferId,
400 snapshot: EditorSnapshot,
401 editor: WeakEntity<Editor>,
402 cx: &mut App,
403 ) -> Vec<BlockProperties<Anchor>>;
404
405 fn render_hover(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 range: Range<Point>,
409 buffer_id: BufferId,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug)]
466pub enum SelectMode {
467 Character,
468 Word(Range<Anchor>),
469 Line(Range<Anchor>),
470 All,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine {
476 auto_width: bool,
477 },
478 AutoHeight {
479 max_lines: usize,
480 },
481 Full {
482 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
483 scale_ui_elements_with_buffer_font_size: bool,
484 /// When set to `true`, the editor will render a background for the active line.
485 show_active_line_background: bool,
486 /// When set to `true`, the editor's height will be determined by its content.
487 sized_by_content: bool,
488 },
489 Minimap {
490 parent: WeakEntity<Editor>,
491 },
492}
493
494impl EditorMode {
495 pub fn full() -> Self {
496 Self::Full {
497 scale_ui_elements_with_buffer_font_size: true,
498 show_active_line_background: true,
499 sized_by_content: false,
500 }
501 }
502
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 fn is_minimap(&self) -> bool {
508 matches!(self, Self::Minimap { .. })
509 }
510}
511
512#[derive(Copy, Clone, Debug)]
513pub enum SoftWrap {
514 /// Prefer not to wrap at all.
515 ///
516 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
517 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
518 GitDiff,
519 /// Prefer a single line generally, unless an overly long line is encountered.
520 None,
521 /// Soft wrap lines that exceed the editor width.
522 EditorWidth,
523 /// Soft wrap lines at the preferred line length.
524 Column(u32),
525 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
526 Bounded(u32),
527}
528
529#[derive(Clone)]
530pub struct EditorStyle {
531 pub background: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub inline_completion_styles: InlineCompletionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 local_player: PlayerColor::default(),
548 text: TextStyle::default(),
549 scrollbar_width: Pixels::default(),
550 syntax: Default::default(),
551 // HACK: Status colors don't have a real default.
552 // We should look into removing the status colors from the editor
553 // style and retrieve them directly from the theme.
554 status: StatusColors::dark(),
555 inlay_hints_style: HighlightStyle::default(),
556 inline_completion_styles: InlineCompletionStyles {
557 insertion: HighlightStyle::default(),
558 whitespace: HighlightStyle::default(),
559 },
560 unnecessary_code_fade: Default::default(),
561 show_underlines: true,
562 }
563 }
564}
565
566pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
567 let show_background = language_settings::language_settings(None, None, cx)
568 .inlay_hints
569 .show_background;
570
571 HighlightStyle {
572 color: Some(cx.theme().status().hint),
573 background_color: show_background.then(|| cx.theme().status().hint_background),
574 ..HighlightStyle::default()
575 }
576}
577
578pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
579 InlineCompletionStyles {
580 insertion: HighlightStyle {
581 color: Some(cx.theme().status().predictive),
582 ..HighlightStyle::default()
583 },
584 whitespace: HighlightStyle {
585 background_color: Some(cx.theme().status().created_background),
586 ..HighlightStyle::default()
587 },
588 }
589}
590
591type CompletionId = usize;
592
593pub(crate) enum EditDisplayMode {
594 TabAccept,
595 DiffPopover,
596 Inline,
597}
598
599enum InlineCompletion {
600 Edit {
601 edits: Vec<(Range<Anchor>, String)>,
602 edit_preview: Option<EditPreview>,
603 display_mode: EditDisplayMode,
604 snapshot: BufferSnapshot,
605 },
606 Move {
607 target: Anchor,
608 snapshot: BufferSnapshot,
609 },
610}
611
612struct InlineCompletionState {
613 inlay_ids: Vec<InlayId>,
614 completion: InlineCompletion,
615 completion_id: Option<SharedString>,
616 invalidation_range: Range<Anchor>,
617}
618
619enum EditPredictionSettings {
620 Disabled,
621 Enabled {
622 show_in_menu: bool,
623 preview_requires_modifier: bool,
624 },
625}
626
627enum InlineCompletionHighlight {}
628
629#[derive(Debug, Clone)]
630struct InlineDiagnostic {
631 message: SharedString,
632 group_id: usize,
633 is_primary: bool,
634 start: Point,
635 severity: lsp::DiagnosticSeverity,
636}
637
638pub enum MenuInlineCompletionsPolicy {
639 Never,
640 ByProvider,
641}
642
643pub enum EditPredictionPreview {
644 /// Modifier is not pressed
645 Inactive { released_too_fast: bool },
646 /// Modifier pressed
647 Active {
648 since: Instant,
649 previous_scroll_position: Option<ScrollAnchor>,
650 },
651}
652
653impl EditPredictionPreview {
654 pub fn released_too_fast(&self) -> bool {
655 match self {
656 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
657 EditPredictionPreview::Active { .. } => false,
658 }
659 }
660
661 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
662 if let EditPredictionPreview::Active {
663 previous_scroll_position,
664 ..
665 } = self
666 {
667 *previous_scroll_position = scroll_position;
668 }
669 }
670}
671
672pub struct ContextMenuOptions {
673 pub min_entries_visible: usize,
674 pub max_entries_visible: usize,
675 pub placement: Option<ContextMenuPlacement>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq)]
679pub enum ContextMenuPlacement {
680 Above,
681 Below,
682}
683
684#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
685struct EditorActionId(usize);
686
687impl EditorActionId {
688 pub fn post_inc(&mut self) -> Self {
689 let answer = self.0;
690
691 *self = Self(answer + 1);
692
693 Self(answer)
694 }
695}
696
697// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
698// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
699
700type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
701type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
702
703#[derive(Default)]
704struct ScrollbarMarkerState {
705 scrollbar_size: Size<Pixels>,
706 dirty: bool,
707 markers: Arc<[PaintQuad]>,
708 pending_refresh: Option<Task<Result<()>>>,
709}
710
711impl ScrollbarMarkerState {
712 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
713 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
714 }
715}
716
717#[derive(Clone, Copy, PartialEq, Eq)]
718pub enum MinimapVisibility {
719 Disabled,
720 Enabled(bool),
721}
722
723impl MinimapVisibility {
724 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
725 if mode.is_full() {
726 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
727 } else {
728 Self::Disabled
729 }
730 }
731
732 fn disabled(&self) -> bool {
733 match *self {
734 Self::Disabled => true,
735 _ => false,
736 }
737 }
738
739 fn visible(&self) -> bool {
740 match *self {
741 Self::Enabled(visible) => visible,
742 _ => false,
743 }
744 }
745
746 fn toggle_visibility(&self) -> Self {
747 match *self {
748 Self::Enabled(visible) => Self::Enabled(!visible),
749 Self::Disabled => Self::Disabled,
750 }
751 }
752}
753
754#[derive(Clone, Debug)]
755struct RunnableTasks {
756 templates: Vec<(TaskSourceKind, TaskTemplate)>,
757 offset: multi_buffer::Anchor,
758 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
759 column: u32,
760 // Values of all named captures, including those starting with '_'
761 extra_variables: HashMap<String, String>,
762 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
763 context_range: Range<BufferOffset>,
764}
765
766impl RunnableTasks {
767 fn resolve<'a>(
768 &'a self,
769 cx: &'a task::TaskContext,
770 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
771 self.templates.iter().filter_map(|(kind, template)| {
772 template
773 .resolve_task(&kind.to_id_base(), cx)
774 .map(|task| (kind.clone(), task))
775 })
776 }
777}
778
779#[derive(Clone)]
780struct ResolvedTasks {
781 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
782 position: Anchor,
783}
784
785#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
786struct BufferOffset(usize);
787
788// Addons allow storing per-editor state in other crates (e.g. Vim)
789pub trait Addon: 'static {
790 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
791
792 fn render_buffer_header_controls(
793 &self,
794 _: &ExcerptInfo,
795 _: &Window,
796 _: &App,
797 ) -> Option<AnyElement> {
798 None
799 }
800
801 fn to_any(&self) -> &dyn std::any::Any;
802
803 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
804 None
805 }
806}
807
808/// A set of caret positions, registered when the editor was edited.
809pub struct ChangeList {
810 changes: Vec<Vec<Anchor>>,
811 /// Currently "selected" change.
812 position: Option<usize>,
813}
814
815impl ChangeList {
816 pub fn new() -> Self {
817 Self {
818 changes: Vec::new(),
819 position: None,
820 }
821 }
822
823 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
824 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
825 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
826 if self.changes.is_empty() {
827 return None;
828 }
829
830 let prev = self.position.unwrap_or(self.changes.len());
831 let next = if direction == Direction::Prev {
832 prev.saturating_sub(count)
833 } else {
834 (prev + count).min(self.changes.len() - 1)
835 };
836 self.position = Some(next);
837 self.changes.get(next).map(|anchors| anchors.as_slice())
838 }
839
840 /// Adds a new change to the list, resetting the change list position.
841 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
842 self.position.take();
843 if pop_state {
844 self.changes.pop();
845 }
846 self.changes.push(new_positions.clone());
847 }
848
849 pub fn last(&self) -> Option<&[Anchor]> {
850 self.changes.last().map(|anchors| anchors.as_slice())
851 }
852}
853
854#[derive(Clone)]
855struct InlineBlamePopoverState {
856 scroll_handle: ScrollHandle,
857 commit_message: Option<ParsedCommitMessage>,
858 markdown: Entity<Markdown>,
859}
860
861struct InlineBlamePopover {
862 position: gpui::Point<Pixels>,
863 show_task: Option<Task<()>>,
864 hide_task: Option<Task<()>>,
865 popover_bounds: Option<Bounds<Pixels>>,
866 popover_state: InlineBlamePopoverState,
867}
868
869/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
870/// a breakpoint on them.
871#[derive(Clone, Copy, Debug)]
872struct PhantomBreakpointIndicator {
873 display_row: DisplayRow,
874 /// There's a small debounce between hovering over the line and showing the indicator.
875 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
876 is_active: bool,
877 collides_with_existing_breakpoint: bool,
878}
879/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
880///
881/// See the [module level documentation](self) for more information.
882pub struct Editor {
883 focus_handle: FocusHandle,
884 last_focused_descendant: Option<WeakFocusHandle>,
885 /// The text buffer being edited
886 buffer: Entity<MultiBuffer>,
887 /// Map of how text in the buffer should be displayed.
888 /// Handles soft wraps, folds, fake inlay text insertions, etc.
889 pub display_map: Entity<DisplayMap>,
890 pub selections: SelectionsCollection,
891 pub scroll_manager: ScrollManager,
892 /// When inline assist editors are linked, they all render cursors because
893 /// typing enters text into each of them, even the ones that aren't focused.
894 pub(crate) show_cursor_when_unfocused: bool,
895 columnar_selection_tail: Option<Anchor>,
896 add_selections_state: Option<AddSelectionsState>,
897 select_next_state: Option<SelectNextState>,
898 select_prev_state: Option<SelectNextState>,
899 selection_history: SelectionHistory,
900 autoclose_regions: Vec<AutocloseRegion>,
901 snippet_stack: InvalidationStack<SnippetState>,
902 select_syntax_node_history: SelectSyntaxNodeHistory,
903 ime_transaction: Option<TransactionId>,
904 pub diagnostics_max_severity: DiagnosticSeverity,
905 active_diagnostics: ActiveDiagnostic,
906 show_inline_diagnostics: bool,
907 inline_diagnostics_update: Task<()>,
908 inline_diagnostics_enabled: bool,
909 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
910 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
911 hard_wrap: Option<usize>,
912
913 // TODO: make this a access method
914 pub project: Option<Entity<Project>>,
915 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
916 completion_provider: Option<Box<dyn CompletionProvider>>,
917 collaboration_hub: Option<Box<dyn CollaborationHub>>,
918 blink_manager: Entity<BlinkManager>,
919 show_cursor_names: bool,
920 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
921 pub show_local_selections: bool,
922 mode: EditorMode,
923 show_breadcrumbs: bool,
924 show_gutter: bool,
925 show_scrollbars: bool,
926 minimap_visibility: MinimapVisibility,
927 disable_expand_excerpt_buttons: bool,
928 show_line_numbers: Option<bool>,
929 use_relative_line_numbers: Option<bool>,
930 show_git_diff_gutter: Option<bool>,
931 show_code_actions: Option<bool>,
932 show_runnables: Option<bool>,
933 show_breakpoints: Option<bool>,
934 show_wrap_guides: Option<bool>,
935 show_indent_guides: Option<bool>,
936 placeholder_text: Option<Arc<str>>,
937 highlight_order: usize,
938 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
939 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
940 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
941 scrollbar_marker_state: ScrollbarMarkerState,
942 active_indent_guides_state: ActiveIndentGuidesState,
943 nav_history: Option<ItemNavHistory>,
944 context_menu: RefCell<Option<CodeContextMenu>>,
945 context_menu_options: Option<ContextMenuOptions>,
946 mouse_context_menu: Option<MouseContextMenu>,
947 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
948 inline_blame_popover: Option<InlineBlamePopover>,
949 signature_help_state: SignatureHelpState,
950 auto_signature_help: Option<bool>,
951 find_all_references_task_sources: Vec<Anchor>,
952 next_completion_id: CompletionId,
953 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
954 code_actions_task: Option<Task<Result<()>>>,
955 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
957 document_highlights_task: Option<Task<()>>,
958 linked_editing_range_task: Option<Task<Option<()>>>,
959 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
960 pending_rename: Option<RenameState>,
961 searchable: bool,
962 cursor_shape: CursorShape,
963 current_line_highlight: Option<CurrentLineHighlight>,
964 collapse_matches: bool,
965 autoindent_mode: Option<AutoindentMode>,
966 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
967 input_enabled: bool,
968 use_modal_editing: bool,
969 read_only: bool,
970 leader_id: Option<CollaboratorId>,
971 remote_id: Option<ViewId>,
972 pub hover_state: HoverState,
973 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
974 gutter_hovered: bool,
975 hovered_link_state: Option<HoveredLinkState>,
976 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
977 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
978 active_inline_completion: Option<InlineCompletionState>,
979 /// Used to prevent flickering as the user types while the menu is open
980 stale_inline_completion_in_menu: Option<InlineCompletionState>,
981 edit_prediction_settings: EditPredictionSettings,
982 inline_completions_hidden_for_vim_mode: bool,
983 show_inline_completions_override: Option<bool>,
984 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
985 edit_prediction_preview: EditPredictionPreview,
986 edit_prediction_indent_conflict: bool,
987 edit_prediction_requires_modifier_in_indent_conflict: bool,
988 inlay_hint_cache: InlayHintCache,
989 next_inlay_id: usize,
990 _subscriptions: Vec<Subscription>,
991 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
992 gutter_dimensions: GutterDimensions,
993 style: Option<EditorStyle>,
994 text_style_refinement: Option<TextStyleRefinement>,
995 next_editor_action_id: EditorActionId,
996 editor_actions:
997 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
998 use_autoclose: bool,
999 use_auto_surround: bool,
1000 auto_replace_emoji_shortcode: bool,
1001 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1002 show_git_blame_gutter: bool,
1003 show_git_blame_inline: bool,
1004 show_git_blame_inline_delay_task: Option<Task<()>>,
1005 git_blame_inline_enabled: bool,
1006 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1007 serialize_dirty_buffers: bool,
1008 show_selection_menu: Option<bool>,
1009 blame: Option<Entity<GitBlame>>,
1010 blame_subscription: Option<Subscription>,
1011 custom_context_menu: Option<
1012 Box<
1013 dyn 'static
1014 + Fn(
1015 &mut Self,
1016 DisplayPoint,
1017 &mut Window,
1018 &mut Context<Self>,
1019 ) -> Option<Entity<ui::ContextMenu>>,
1020 >,
1021 >,
1022 last_bounds: Option<Bounds<Pixels>>,
1023 last_position_map: Option<Rc<PositionMap>>,
1024 expect_bounds_change: Option<Bounds<Pixels>>,
1025 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1026 tasks_update_task: Option<Task<()>>,
1027 breakpoint_store: Option<Entity<BreakpointStore>>,
1028 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1029 in_project_search: bool,
1030 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1031 breadcrumb_header: Option<String>,
1032 focused_block: Option<FocusedBlock>,
1033 next_scroll_position: NextScrollCursorCenterTopBottom,
1034 addons: HashMap<TypeId, Box<dyn Addon>>,
1035 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1036 load_diff_task: Option<Shared<Task<()>>>,
1037 /// Whether we are temporarily displaying a diff other than git's
1038 temporary_diff_override: bool,
1039 selection_mark_mode: bool,
1040 toggle_fold_multiple_buffers: Task<()>,
1041 _scroll_cursor_center_top_bottom_task: Task<()>,
1042 serialize_selections: Task<()>,
1043 serialize_folds: Task<()>,
1044 mouse_cursor_hidden: bool,
1045 minimap: Option<Entity<Self>>,
1046 hide_mouse_mode: HideMouseMode,
1047 pub change_list: ChangeList,
1048 inline_value_cache: InlineValueCache,
1049}
1050
1051#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1052enum NextScrollCursorCenterTopBottom {
1053 #[default]
1054 Center,
1055 Top,
1056 Bottom,
1057}
1058
1059impl NextScrollCursorCenterTopBottom {
1060 fn next(&self) -> Self {
1061 match self {
1062 Self::Center => Self::Top,
1063 Self::Top => Self::Bottom,
1064 Self::Bottom => Self::Center,
1065 }
1066 }
1067}
1068
1069#[derive(Clone)]
1070pub struct EditorSnapshot {
1071 pub mode: EditorMode,
1072 show_gutter: bool,
1073 show_line_numbers: Option<bool>,
1074 show_git_diff_gutter: Option<bool>,
1075 show_runnables: Option<bool>,
1076 show_breakpoints: Option<bool>,
1077 git_blame_gutter_max_author_length: Option<usize>,
1078 pub display_snapshot: DisplaySnapshot,
1079 pub placeholder_text: Option<Arc<str>>,
1080 is_focused: bool,
1081 scroll_anchor: ScrollAnchor,
1082 ongoing_scroll: OngoingScroll,
1083 current_line_highlight: CurrentLineHighlight,
1084 gutter_hovered: bool,
1085}
1086
1087#[derive(Default, Debug, Clone, Copy)]
1088pub struct GutterDimensions {
1089 pub left_padding: Pixels,
1090 pub right_padding: Pixels,
1091 pub width: Pixels,
1092 pub margin: Pixels,
1093 pub git_blame_entries_width: Option<Pixels>,
1094}
1095
1096impl GutterDimensions {
1097 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1098 Self {
1099 margin: Self::default_gutter_margin(font_id, font_size, cx),
1100 ..Default::default()
1101 }
1102 }
1103
1104 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1105 -cx.text_system().descent(font_id, font_size)
1106 }
1107 /// The full width of the space taken up by the gutter.
1108 pub fn full_width(&self) -> Pixels {
1109 self.margin + self.width
1110 }
1111
1112 /// The width of the space reserved for the fold indicators,
1113 /// use alongside 'justify_end' and `gutter_width` to
1114 /// right align content with the line numbers
1115 pub fn fold_area_width(&self) -> Pixels {
1116 self.margin + self.right_padding
1117 }
1118}
1119
1120#[derive(Debug)]
1121pub struct RemoteSelection {
1122 pub replica_id: ReplicaId,
1123 pub selection: Selection<Anchor>,
1124 pub cursor_shape: CursorShape,
1125 pub collaborator_id: CollaboratorId,
1126 pub line_mode: bool,
1127 pub user_name: Option<SharedString>,
1128 pub color: PlayerColor,
1129}
1130
1131#[derive(Clone, Debug)]
1132struct SelectionHistoryEntry {
1133 selections: Arc<[Selection<Anchor>]>,
1134 select_next_state: Option<SelectNextState>,
1135 select_prev_state: Option<SelectNextState>,
1136 add_selections_state: Option<AddSelectionsState>,
1137}
1138
1139enum SelectionHistoryMode {
1140 Normal,
1141 Undoing,
1142 Redoing,
1143}
1144
1145#[derive(Clone, PartialEq, Eq, Hash)]
1146struct HoveredCursor {
1147 replica_id: u16,
1148 selection_id: usize,
1149}
1150
1151impl Default for SelectionHistoryMode {
1152 fn default() -> Self {
1153 Self::Normal
1154 }
1155}
1156
1157#[derive(Default)]
1158struct SelectionHistory {
1159 #[allow(clippy::type_complexity)]
1160 selections_by_transaction:
1161 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1162 mode: SelectionHistoryMode,
1163 undo_stack: VecDeque<SelectionHistoryEntry>,
1164 redo_stack: VecDeque<SelectionHistoryEntry>,
1165}
1166
1167impl SelectionHistory {
1168 fn insert_transaction(
1169 &mut self,
1170 transaction_id: TransactionId,
1171 selections: Arc<[Selection<Anchor>]>,
1172 ) {
1173 self.selections_by_transaction
1174 .insert(transaction_id, (selections, None));
1175 }
1176
1177 #[allow(clippy::type_complexity)]
1178 fn transaction(
1179 &self,
1180 transaction_id: TransactionId,
1181 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1182 self.selections_by_transaction.get(&transaction_id)
1183 }
1184
1185 #[allow(clippy::type_complexity)]
1186 fn transaction_mut(
1187 &mut self,
1188 transaction_id: TransactionId,
1189 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1190 self.selections_by_transaction.get_mut(&transaction_id)
1191 }
1192
1193 fn push(&mut self, entry: SelectionHistoryEntry) {
1194 if !entry.selections.is_empty() {
1195 match self.mode {
1196 SelectionHistoryMode::Normal => {
1197 self.push_undo(entry);
1198 self.redo_stack.clear();
1199 }
1200 SelectionHistoryMode::Undoing => self.push_redo(entry),
1201 SelectionHistoryMode::Redoing => self.push_undo(entry),
1202 }
1203 }
1204 }
1205
1206 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1207 if self
1208 .undo_stack
1209 .back()
1210 .map_or(true, |e| e.selections != entry.selections)
1211 {
1212 self.undo_stack.push_back(entry);
1213 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1214 self.undo_stack.pop_front();
1215 }
1216 }
1217 }
1218
1219 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1220 if self
1221 .redo_stack
1222 .back()
1223 .map_or(true, |e| e.selections != entry.selections)
1224 {
1225 self.redo_stack.push_back(entry);
1226 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1227 self.redo_stack.pop_front();
1228 }
1229 }
1230 }
1231}
1232
1233#[derive(Clone, Copy)]
1234pub struct RowHighlightOptions {
1235 pub autoscroll: bool,
1236 pub include_gutter: bool,
1237}
1238
1239impl Default for RowHighlightOptions {
1240 fn default() -> Self {
1241 Self {
1242 autoscroll: Default::default(),
1243 include_gutter: true,
1244 }
1245 }
1246}
1247
1248struct RowHighlight {
1249 index: usize,
1250 range: Range<Anchor>,
1251 color: Hsla,
1252 options: RowHighlightOptions,
1253 type_id: TypeId,
1254}
1255
1256#[derive(Clone, Debug)]
1257struct AddSelectionsState {
1258 above: bool,
1259 stack: Vec<usize>,
1260}
1261
1262#[derive(Clone)]
1263struct SelectNextState {
1264 query: AhoCorasick,
1265 wordwise: bool,
1266 done: bool,
1267}
1268
1269impl std::fmt::Debug for SelectNextState {
1270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1271 f.debug_struct(std::any::type_name::<Self>())
1272 .field("wordwise", &self.wordwise)
1273 .field("done", &self.done)
1274 .finish()
1275 }
1276}
1277
1278#[derive(Debug)]
1279struct AutocloseRegion {
1280 selection_id: usize,
1281 range: Range<Anchor>,
1282 pair: BracketPair,
1283}
1284
1285#[derive(Debug)]
1286struct SnippetState {
1287 ranges: Vec<Vec<Range<Anchor>>>,
1288 active_index: usize,
1289 choices: Vec<Option<Vec<String>>>,
1290}
1291
1292#[doc(hidden)]
1293pub struct RenameState {
1294 pub range: Range<Anchor>,
1295 pub old_name: Arc<str>,
1296 pub editor: Entity<Editor>,
1297 block_id: CustomBlockId,
1298}
1299
1300struct InvalidationStack<T>(Vec<T>);
1301
1302struct RegisteredInlineCompletionProvider {
1303 provider: Arc<dyn InlineCompletionProviderHandle>,
1304 _subscription: Subscription,
1305}
1306
1307#[derive(Debug, PartialEq, Eq)]
1308pub struct ActiveDiagnosticGroup {
1309 pub active_range: Range<Anchor>,
1310 pub active_message: String,
1311 pub group_id: usize,
1312 pub blocks: HashSet<CustomBlockId>,
1313}
1314
1315#[derive(Debug, PartialEq, Eq)]
1316
1317pub(crate) enum ActiveDiagnostic {
1318 None,
1319 All,
1320 Group(ActiveDiagnosticGroup),
1321}
1322
1323#[derive(Serialize, Deserialize, Clone, Debug)]
1324pub struct ClipboardSelection {
1325 /// The number of bytes in this selection.
1326 pub len: usize,
1327 /// Whether this was a full-line selection.
1328 pub is_entire_line: bool,
1329 /// The indentation of the first line when this content was originally copied.
1330 pub first_line_indent: u32,
1331}
1332
1333// selections, scroll behavior, was newest selection reversed
1334type SelectSyntaxNodeHistoryState = (
1335 Box<[Selection<usize>]>,
1336 SelectSyntaxNodeScrollBehavior,
1337 bool,
1338);
1339
1340#[derive(Default)]
1341struct SelectSyntaxNodeHistory {
1342 stack: Vec<SelectSyntaxNodeHistoryState>,
1343 // disable temporarily to allow changing selections without losing the stack
1344 pub disable_clearing: bool,
1345}
1346
1347impl SelectSyntaxNodeHistory {
1348 pub fn try_clear(&mut self) {
1349 if !self.disable_clearing {
1350 self.stack.clear();
1351 }
1352 }
1353
1354 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1355 self.stack.push(selection);
1356 }
1357
1358 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1359 self.stack.pop()
1360 }
1361}
1362
1363enum SelectSyntaxNodeScrollBehavior {
1364 CursorTop,
1365 FitSelection,
1366 CursorBottom,
1367}
1368
1369#[derive(Debug)]
1370pub(crate) struct NavigationData {
1371 cursor_anchor: Anchor,
1372 cursor_position: Point,
1373 scroll_anchor: ScrollAnchor,
1374 scroll_top_row: u32,
1375}
1376
1377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1378pub enum GotoDefinitionKind {
1379 Symbol,
1380 Declaration,
1381 Type,
1382 Implementation,
1383}
1384
1385#[derive(Debug, Clone)]
1386enum InlayHintRefreshReason {
1387 ModifiersChanged(bool),
1388 Toggle(bool),
1389 SettingsChange(InlayHintSettings),
1390 NewLinesShown,
1391 BufferEdited(HashSet<Arc<Language>>),
1392 RefreshRequested,
1393 ExcerptsRemoved(Vec<ExcerptId>),
1394}
1395
1396impl InlayHintRefreshReason {
1397 fn description(&self) -> &'static str {
1398 match self {
1399 Self::ModifiersChanged(_) => "modifiers changed",
1400 Self::Toggle(_) => "toggle",
1401 Self::SettingsChange(_) => "settings change",
1402 Self::NewLinesShown => "new lines shown",
1403 Self::BufferEdited(_) => "buffer edited",
1404 Self::RefreshRequested => "refresh requested",
1405 Self::ExcerptsRemoved(_) => "excerpts removed",
1406 }
1407 }
1408}
1409
1410pub enum FormatTarget {
1411 Buffers,
1412 Ranges(Vec<Range<MultiBufferPoint>>),
1413}
1414
1415pub(crate) struct FocusedBlock {
1416 id: BlockId,
1417 focus_handle: WeakFocusHandle,
1418}
1419
1420#[derive(Clone)]
1421enum JumpData {
1422 MultiBufferRow {
1423 row: MultiBufferRow,
1424 line_offset_from_top: u32,
1425 },
1426 MultiBufferPoint {
1427 excerpt_id: ExcerptId,
1428 position: Point,
1429 anchor: text::Anchor,
1430 line_offset_from_top: u32,
1431 },
1432}
1433
1434pub enum MultibufferSelectionMode {
1435 First,
1436 All,
1437}
1438
1439#[derive(Clone, Copy, Debug, Default)]
1440pub struct RewrapOptions {
1441 pub override_language_settings: bool,
1442 pub preserve_existing_whitespace: bool,
1443}
1444
1445impl Editor {
1446 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1447 let buffer = cx.new(|cx| Buffer::local("", cx));
1448 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1449 Self::new(
1450 EditorMode::SingleLine { auto_width: false },
1451 buffer,
1452 None,
1453 window,
1454 cx,
1455 )
1456 }
1457
1458 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1459 let buffer = cx.new(|cx| Buffer::local("", cx));
1460 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1461 Self::new(EditorMode::full(), buffer, None, window, cx)
1462 }
1463
1464 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1465 let buffer = cx.new(|cx| Buffer::local("", cx));
1466 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1467 Self::new(
1468 EditorMode::SingleLine { auto_width: true },
1469 buffer,
1470 None,
1471 window,
1472 cx,
1473 )
1474 }
1475
1476 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1477 let buffer = cx.new(|cx| Buffer::local("", cx));
1478 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1479 Self::new(
1480 EditorMode::AutoHeight { max_lines },
1481 buffer,
1482 None,
1483 window,
1484 cx,
1485 )
1486 }
1487
1488 pub fn for_buffer(
1489 buffer: Entity<Buffer>,
1490 project: Option<Entity<Project>>,
1491 window: &mut Window,
1492 cx: &mut Context<Self>,
1493 ) -> Self {
1494 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1495 Self::new(EditorMode::full(), buffer, project, window, cx)
1496 }
1497
1498 pub fn for_multibuffer(
1499 buffer: Entity<MultiBuffer>,
1500 project: Option<Entity<Project>>,
1501 window: &mut Window,
1502 cx: &mut Context<Self>,
1503 ) -> Self {
1504 Self::new(EditorMode::full(), buffer, project, window, cx)
1505 }
1506
1507 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1508 let mut clone = Self::new(
1509 self.mode.clone(),
1510 self.buffer.clone(),
1511 self.project.clone(),
1512 window,
1513 cx,
1514 );
1515 self.display_map.update(cx, |display_map, cx| {
1516 let snapshot = display_map.snapshot(cx);
1517 clone.display_map.update(cx, |display_map, cx| {
1518 display_map.set_state(&snapshot, cx);
1519 });
1520 });
1521 clone.folds_did_change(cx);
1522 clone.selections.clone_state(&self.selections);
1523 clone.scroll_manager.clone_state(&self.scroll_manager);
1524 clone.searchable = self.searchable;
1525 clone.read_only = self.read_only;
1526 clone
1527 }
1528
1529 pub fn new(
1530 mode: EditorMode,
1531 buffer: Entity<MultiBuffer>,
1532 project: Option<Entity<Project>>,
1533 window: &mut Window,
1534 cx: &mut Context<Self>,
1535 ) -> Self {
1536 Editor::new_internal(mode, buffer, project, None, window, cx)
1537 }
1538
1539 fn new_internal(
1540 mode: EditorMode,
1541 buffer: Entity<MultiBuffer>,
1542 project: Option<Entity<Project>>,
1543 display_map: Option<Entity<DisplayMap>>,
1544 window: &mut Window,
1545 cx: &mut Context<Self>,
1546 ) -> Self {
1547 debug_assert!(
1548 display_map.is_none() || mode.is_minimap(),
1549 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1550 );
1551
1552 let full_mode = mode.is_full();
1553 let diagnostics_max_severity = if full_mode {
1554 EditorSettings::get_global(cx)
1555 .diagnostics_max_severity
1556 .unwrap_or(DiagnosticSeverity::Hint)
1557 } else {
1558 DiagnosticSeverity::Off
1559 };
1560 let style = window.text_style();
1561 let font_size = style.font_size.to_pixels(window.rem_size());
1562 let editor = cx.entity().downgrade();
1563 let fold_placeholder = FoldPlaceholder {
1564 constrain_width: true,
1565 render: Arc::new(move |fold_id, fold_range, cx| {
1566 let editor = editor.clone();
1567 div()
1568 .id(fold_id)
1569 .bg(cx.theme().colors().ghost_element_background)
1570 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1571 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1572 .rounded_xs()
1573 .size_full()
1574 .cursor_pointer()
1575 .child("⋯")
1576 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1577 .on_click(move |_, _window, cx| {
1578 editor
1579 .update(cx, |editor, cx| {
1580 editor.unfold_ranges(
1581 &[fold_range.start..fold_range.end],
1582 true,
1583 false,
1584 cx,
1585 );
1586 cx.stop_propagation();
1587 })
1588 .ok();
1589 })
1590 .into_any()
1591 }),
1592 merge_adjacent: true,
1593 ..FoldPlaceholder::default()
1594 };
1595 let display_map = display_map.unwrap_or_else(|| {
1596 cx.new(|cx| {
1597 DisplayMap::new(
1598 buffer.clone(),
1599 style.font(),
1600 font_size,
1601 None,
1602 FILE_HEADER_HEIGHT,
1603 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1604 fold_placeholder,
1605 diagnostics_max_severity,
1606 cx,
1607 )
1608 })
1609 });
1610
1611 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1612
1613 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1614
1615 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1616 .then(|| language_settings::SoftWrap::None);
1617
1618 let mut project_subscriptions = Vec::new();
1619 if mode.is_full() {
1620 if let Some(project) = project.as_ref() {
1621 project_subscriptions.push(cx.subscribe_in(
1622 project,
1623 window,
1624 |editor, _, event, window, cx| match event {
1625 project::Event::RefreshCodeLens => {
1626 // we always query lens with actions, without storing them, always refreshing them
1627 }
1628 project::Event::RefreshInlayHints => {
1629 editor
1630 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1631 }
1632 project::Event::SnippetEdit(id, snippet_edits) => {
1633 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1634 let focus_handle = editor.focus_handle(cx);
1635 if focus_handle.is_focused(window) {
1636 let snapshot = buffer.read(cx).snapshot();
1637 for (range, snippet) in snippet_edits {
1638 let editor_range =
1639 language::range_from_lsp(*range).to_offset(&snapshot);
1640 editor
1641 .insert_snippet(
1642 &[editor_range],
1643 snippet.clone(),
1644 window,
1645 cx,
1646 )
1647 .ok();
1648 }
1649 }
1650 }
1651 }
1652 _ => {}
1653 },
1654 ));
1655 if let Some(task_inventory) = project
1656 .read(cx)
1657 .task_store()
1658 .read(cx)
1659 .task_inventory()
1660 .cloned()
1661 {
1662 project_subscriptions.push(cx.observe_in(
1663 &task_inventory,
1664 window,
1665 |editor, _, window, cx| {
1666 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1667 },
1668 ));
1669 };
1670
1671 project_subscriptions.push(cx.subscribe_in(
1672 &project.read(cx).breakpoint_store(),
1673 window,
1674 |editor, _, event, window, cx| match event {
1675 BreakpointStoreEvent::ClearDebugLines => {
1676 editor.clear_row_highlights::<ActiveDebugLine>();
1677 editor.refresh_inline_values(cx);
1678 }
1679 BreakpointStoreEvent::SetDebugLine => {
1680 if editor.go_to_active_debug_line(window, cx) {
1681 cx.stop_propagation();
1682 }
1683
1684 editor.refresh_inline_values(cx);
1685 }
1686 _ => {}
1687 },
1688 ));
1689 }
1690 }
1691
1692 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1693
1694 let inlay_hint_settings =
1695 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1696 let focus_handle = cx.focus_handle();
1697 cx.on_focus(&focus_handle, window, Self::handle_focus)
1698 .detach();
1699 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1700 .detach();
1701 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1702 .detach();
1703 cx.on_blur(&focus_handle, window, Self::handle_blur)
1704 .detach();
1705
1706 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1707 Some(false)
1708 } else {
1709 None
1710 };
1711
1712 let breakpoint_store = match (&mode, project.as_ref()) {
1713 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1714 _ => None,
1715 };
1716
1717 let mut code_action_providers = Vec::new();
1718 let mut load_uncommitted_diff = None;
1719 if let Some(project) = project.clone() {
1720 load_uncommitted_diff = Some(
1721 update_uncommitted_diff_for_buffer(
1722 cx.entity(),
1723 &project,
1724 buffer.read(cx).all_buffers(),
1725 buffer.clone(),
1726 cx,
1727 )
1728 .shared(),
1729 );
1730 code_action_providers.push(Rc::new(project) as Rc<_>);
1731 }
1732
1733 let mut this = Self {
1734 focus_handle,
1735 show_cursor_when_unfocused: false,
1736 last_focused_descendant: None,
1737 buffer: buffer.clone(),
1738 display_map: display_map.clone(),
1739 selections,
1740 scroll_manager: ScrollManager::new(cx),
1741 columnar_selection_tail: None,
1742 add_selections_state: None,
1743 select_next_state: None,
1744 select_prev_state: None,
1745 selection_history: SelectionHistory::default(),
1746 autoclose_regions: Vec::new(),
1747 snippet_stack: InvalidationStack::default(),
1748 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1749 ime_transaction: None,
1750 active_diagnostics: ActiveDiagnostic::None,
1751 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1752 inline_diagnostics_update: Task::ready(()),
1753 inline_diagnostics: Vec::new(),
1754 soft_wrap_mode_override,
1755 diagnostics_max_severity,
1756 hard_wrap: None,
1757 completion_provider: project.clone().map(|project| Box::new(project) as _),
1758 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1759 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1760 project,
1761 blink_manager: blink_manager.clone(),
1762 show_local_selections: true,
1763 show_scrollbars: full_mode,
1764 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1765 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1766 show_gutter: mode.is_full(),
1767 show_line_numbers: None,
1768 use_relative_line_numbers: None,
1769 disable_expand_excerpt_buttons: false,
1770 show_git_diff_gutter: None,
1771 show_code_actions: None,
1772 show_runnables: None,
1773 show_breakpoints: None,
1774 show_wrap_guides: None,
1775 show_indent_guides,
1776 placeholder_text: None,
1777 highlight_order: 0,
1778 highlighted_rows: HashMap::default(),
1779 background_highlights: TreeMap::default(),
1780 gutter_highlights: TreeMap::default(),
1781 scrollbar_marker_state: ScrollbarMarkerState::default(),
1782 active_indent_guides_state: ActiveIndentGuidesState::default(),
1783 nav_history: None,
1784 context_menu: RefCell::new(None),
1785 context_menu_options: None,
1786 mouse_context_menu: None,
1787 completion_tasks: Vec::new(),
1788 inline_blame_popover: None,
1789 signature_help_state: SignatureHelpState::default(),
1790 auto_signature_help: None,
1791 find_all_references_task_sources: Vec::new(),
1792 next_completion_id: 0,
1793 next_inlay_id: 0,
1794 code_action_providers,
1795 available_code_actions: None,
1796 code_actions_task: None,
1797 quick_selection_highlight_task: None,
1798 debounced_selection_highlight_task: None,
1799 document_highlights_task: None,
1800 linked_editing_range_task: None,
1801 pending_rename: None,
1802 searchable: true,
1803 cursor_shape: EditorSettings::get_global(cx)
1804 .cursor_shape
1805 .unwrap_or_default(),
1806 current_line_highlight: None,
1807 autoindent_mode: Some(AutoindentMode::EachLine),
1808 collapse_matches: false,
1809 workspace: None,
1810 input_enabled: true,
1811 use_modal_editing: mode.is_full(),
1812 read_only: mode.is_minimap(),
1813 use_autoclose: true,
1814 use_auto_surround: true,
1815 auto_replace_emoji_shortcode: false,
1816 jsx_tag_auto_close_enabled_in_any_buffer: false,
1817 leader_id: None,
1818 remote_id: None,
1819 hover_state: HoverState::default(),
1820 pending_mouse_down: None,
1821 hovered_link_state: None,
1822 edit_prediction_provider: None,
1823 active_inline_completion: None,
1824 stale_inline_completion_in_menu: None,
1825 edit_prediction_preview: EditPredictionPreview::Inactive {
1826 released_too_fast: false,
1827 },
1828 inline_diagnostics_enabled: mode.is_full(),
1829 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1830 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1831
1832 gutter_hovered: false,
1833 pixel_position_of_newest_cursor: None,
1834 last_bounds: None,
1835 last_position_map: None,
1836 expect_bounds_change: None,
1837 gutter_dimensions: GutterDimensions::default(),
1838 style: None,
1839 show_cursor_names: false,
1840 hovered_cursors: HashMap::default(),
1841 next_editor_action_id: EditorActionId::default(),
1842 editor_actions: Rc::default(),
1843 inline_completions_hidden_for_vim_mode: false,
1844 show_inline_completions_override: None,
1845 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1846 edit_prediction_settings: EditPredictionSettings::Disabled,
1847 edit_prediction_indent_conflict: false,
1848 edit_prediction_requires_modifier_in_indent_conflict: true,
1849 custom_context_menu: None,
1850 show_git_blame_gutter: false,
1851 show_git_blame_inline: false,
1852 show_selection_menu: None,
1853 show_git_blame_inline_delay_task: None,
1854 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1855 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1856 serialize_dirty_buffers: !mode.is_minimap()
1857 && ProjectSettings::get_global(cx)
1858 .session
1859 .restore_unsaved_buffers,
1860 blame: None,
1861 blame_subscription: None,
1862 tasks: BTreeMap::default(),
1863
1864 breakpoint_store,
1865 gutter_breakpoint_indicator: (None, None),
1866 _subscriptions: vec![
1867 cx.observe(&buffer, Self::on_buffer_changed),
1868 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1869 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1870 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1871 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1872 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1873 cx.observe_window_activation(window, |editor, window, cx| {
1874 let active = window.is_window_active();
1875 editor.blink_manager.update(cx, |blink_manager, cx| {
1876 if active {
1877 blink_manager.enable(cx);
1878 } else {
1879 blink_manager.disable(cx);
1880 }
1881 });
1882 }),
1883 ],
1884 tasks_update_task: None,
1885 linked_edit_ranges: Default::default(),
1886 in_project_search: false,
1887 previous_search_ranges: None,
1888 breadcrumb_header: None,
1889 focused_block: None,
1890 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1891 addons: HashMap::default(),
1892 registered_buffers: HashMap::default(),
1893 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1894 selection_mark_mode: false,
1895 toggle_fold_multiple_buffers: Task::ready(()),
1896 serialize_selections: Task::ready(()),
1897 serialize_folds: Task::ready(()),
1898 text_style_refinement: None,
1899 load_diff_task: load_uncommitted_diff,
1900 temporary_diff_override: false,
1901 mouse_cursor_hidden: false,
1902 minimap: None,
1903 hide_mouse_mode: EditorSettings::get_global(cx)
1904 .hide_mouse
1905 .unwrap_or_default(),
1906 change_list: ChangeList::new(),
1907 mode,
1908 };
1909 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1910 this._subscriptions
1911 .push(cx.observe(breakpoints, |_, _, cx| {
1912 cx.notify();
1913 }));
1914 }
1915 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1916 this._subscriptions.extend(project_subscriptions);
1917
1918 this._subscriptions.push(cx.subscribe_in(
1919 &cx.entity(),
1920 window,
1921 |editor, _, e: &EditorEvent, window, cx| match e {
1922 EditorEvent::ScrollPositionChanged { local, .. } => {
1923 if *local {
1924 let new_anchor = editor.scroll_manager.anchor();
1925 let snapshot = editor.snapshot(window, cx);
1926 editor.update_restoration_data(cx, move |data| {
1927 data.scroll_position = (
1928 new_anchor.top_row(&snapshot.buffer_snapshot),
1929 new_anchor.offset,
1930 );
1931 });
1932 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1933 editor.inline_blame_popover.take();
1934 }
1935 }
1936 EditorEvent::Edited { .. } => {
1937 if !vim_enabled(cx) {
1938 let (map, selections) = editor.selections.all_adjusted_display(cx);
1939 let pop_state = editor
1940 .change_list
1941 .last()
1942 .map(|previous| {
1943 previous.len() == selections.len()
1944 && previous.iter().enumerate().all(|(ix, p)| {
1945 p.to_display_point(&map).row()
1946 == selections[ix].head().row()
1947 })
1948 })
1949 .unwrap_or(false);
1950 let new_positions = selections
1951 .into_iter()
1952 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1953 .collect();
1954 editor
1955 .change_list
1956 .push_to_change_list(pop_state, new_positions);
1957 }
1958 }
1959 _ => (),
1960 },
1961 ));
1962
1963 if let Some(dap_store) = this
1964 .project
1965 .as_ref()
1966 .map(|project| project.read(cx).dap_store())
1967 {
1968 let weak_editor = cx.weak_entity();
1969
1970 this._subscriptions
1971 .push(
1972 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1973 let session_entity = cx.entity();
1974 weak_editor
1975 .update(cx, |editor, cx| {
1976 editor._subscriptions.push(
1977 cx.subscribe(&session_entity, Self::on_debug_session_event),
1978 );
1979 })
1980 .ok();
1981 }),
1982 );
1983
1984 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1985 this._subscriptions
1986 .push(cx.subscribe(&session, Self::on_debug_session_event));
1987 }
1988 }
1989
1990 this.end_selection(window, cx);
1991 this.scroll_manager.show_scrollbars(window, cx);
1992 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1993
1994 if full_mode {
1995 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1996 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1997
1998 if this.git_blame_inline_enabled {
1999 this.start_git_blame_inline(false, window, cx);
2000 }
2001
2002 this.go_to_active_debug_line(window, cx);
2003
2004 if let Some(buffer) = buffer.read(cx).as_singleton() {
2005 if let Some(project) = this.project.as_ref() {
2006 let handle = project.update(cx, |project, cx| {
2007 project.register_buffer_with_language_servers(&buffer, cx)
2008 });
2009 this.registered_buffers
2010 .insert(buffer.read(cx).remote_id(), handle);
2011 }
2012 }
2013
2014 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2015 }
2016
2017 this.report_editor_event("Editor Opened", None, cx);
2018 this
2019 }
2020
2021 pub fn deploy_mouse_context_menu(
2022 &mut self,
2023 position: gpui::Point<Pixels>,
2024 context_menu: Entity<ContextMenu>,
2025 window: &mut Window,
2026 cx: &mut Context<Self>,
2027 ) {
2028 self.mouse_context_menu = Some(MouseContextMenu::new(
2029 self,
2030 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2031 context_menu,
2032 window,
2033 cx,
2034 ));
2035 }
2036
2037 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2038 self.mouse_context_menu
2039 .as_ref()
2040 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2041 }
2042
2043 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2044 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2045 }
2046
2047 fn key_context_internal(
2048 &self,
2049 has_active_edit_prediction: bool,
2050 window: &Window,
2051 cx: &App,
2052 ) -> KeyContext {
2053 let mut key_context = KeyContext::new_with_defaults();
2054 key_context.add("Editor");
2055 let mode = match self.mode {
2056 EditorMode::SingleLine { .. } => "single_line",
2057 EditorMode::AutoHeight { .. } => "auto_height",
2058 EditorMode::Minimap { .. } => "minimap",
2059 EditorMode::Full { .. } => "full",
2060 };
2061
2062 if EditorSettings::jupyter_enabled(cx) {
2063 key_context.add("jupyter");
2064 }
2065
2066 key_context.set("mode", mode);
2067 if self.pending_rename.is_some() {
2068 key_context.add("renaming");
2069 }
2070
2071 match self.context_menu.borrow().as_ref() {
2072 Some(CodeContextMenu::Completions(_)) => {
2073 key_context.add("menu");
2074 key_context.add("showing_completions");
2075 }
2076 Some(CodeContextMenu::CodeActions(_)) => {
2077 key_context.add("menu");
2078 key_context.add("showing_code_actions")
2079 }
2080 None => {}
2081 }
2082
2083 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2084 if !self.focus_handle(cx).contains_focused(window, cx)
2085 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2086 {
2087 for addon in self.addons.values() {
2088 addon.extend_key_context(&mut key_context, cx)
2089 }
2090 }
2091
2092 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2093 if let Some(extension) = singleton_buffer
2094 .read(cx)
2095 .file()
2096 .and_then(|file| file.path().extension()?.to_str())
2097 {
2098 key_context.set("extension", extension.to_string());
2099 }
2100 } else {
2101 key_context.add("multibuffer");
2102 }
2103
2104 if has_active_edit_prediction {
2105 if self.edit_prediction_in_conflict() {
2106 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2107 } else {
2108 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2109 key_context.add("copilot_suggestion");
2110 }
2111 }
2112
2113 if self.selection_mark_mode {
2114 key_context.add("selection_mode");
2115 }
2116
2117 key_context
2118 }
2119
2120 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2121 self.mouse_cursor_hidden = match origin {
2122 HideMouseCursorOrigin::TypingAction => {
2123 matches!(
2124 self.hide_mouse_mode,
2125 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2126 )
2127 }
2128 HideMouseCursorOrigin::MovementAction => {
2129 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2130 }
2131 };
2132 }
2133
2134 pub fn edit_prediction_in_conflict(&self) -> bool {
2135 if !self.show_edit_predictions_in_menu() {
2136 return false;
2137 }
2138
2139 let showing_completions = self
2140 .context_menu
2141 .borrow()
2142 .as_ref()
2143 .map_or(false, |context| {
2144 matches!(context, CodeContextMenu::Completions(_))
2145 });
2146
2147 showing_completions
2148 || self.edit_prediction_requires_modifier()
2149 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2150 // bindings to insert tab characters.
2151 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2152 }
2153
2154 pub fn accept_edit_prediction_keybind(
2155 &self,
2156 window: &Window,
2157 cx: &App,
2158 ) -> AcceptEditPredictionBinding {
2159 let key_context = self.key_context_internal(true, window, cx);
2160 let in_conflict = self.edit_prediction_in_conflict();
2161
2162 AcceptEditPredictionBinding(
2163 window
2164 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2165 .into_iter()
2166 .filter(|binding| {
2167 !in_conflict
2168 || binding
2169 .keystrokes()
2170 .first()
2171 .map_or(false, |keystroke| keystroke.modifiers.modified())
2172 })
2173 .rev()
2174 .min_by_key(|binding| {
2175 binding
2176 .keystrokes()
2177 .first()
2178 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2179 }),
2180 )
2181 }
2182
2183 pub fn new_file(
2184 workspace: &mut Workspace,
2185 _: &workspace::NewFile,
2186 window: &mut Window,
2187 cx: &mut Context<Workspace>,
2188 ) {
2189 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2190 "Failed to create buffer",
2191 window,
2192 cx,
2193 |e, _, _| match e.error_code() {
2194 ErrorCode::RemoteUpgradeRequired => Some(format!(
2195 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2196 e.error_tag("required").unwrap_or("the latest version")
2197 )),
2198 _ => None,
2199 },
2200 );
2201 }
2202
2203 pub fn new_in_workspace(
2204 workspace: &mut Workspace,
2205 window: &mut Window,
2206 cx: &mut Context<Workspace>,
2207 ) -> Task<Result<Entity<Editor>>> {
2208 let project = workspace.project().clone();
2209 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2210
2211 cx.spawn_in(window, async move |workspace, cx| {
2212 let buffer = create.await?;
2213 workspace.update_in(cx, |workspace, window, cx| {
2214 let editor =
2215 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2216 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2217 editor
2218 })
2219 })
2220 }
2221
2222 fn new_file_vertical(
2223 workspace: &mut Workspace,
2224 _: &workspace::NewFileSplitVertical,
2225 window: &mut Window,
2226 cx: &mut Context<Workspace>,
2227 ) {
2228 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2229 }
2230
2231 fn new_file_horizontal(
2232 workspace: &mut Workspace,
2233 _: &workspace::NewFileSplitHorizontal,
2234 window: &mut Window,
2235 cx: &mut Context<Workspace>,
2236 ) {
2237 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2238 }
2239
2240 fn new_file_in_direction(
2241 workspace: &mut Workspace,
2242 direction: SplitDirection,
2243 window: &mut Window,
2244 cx: &mut Context<Workspace>,
2245 ) {
2246 let project = workspace.project().clone();
2247 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2248
2249 cx.spawn_in(window, async move |workspace, cx| {
2250 let buffer = create.await?;
2251 workspace.update_in(cx, move |workspace, window, cx| {
2252 workspace.split_item(
2253 direction,
2254 Box::new(
2255 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2256 ),
2257 window,
2258 cx,
2259 )
2260 })?;
2261 anyhow::Ok(())
2262 })
2263 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2264 match e.error_code() {
2265 ErrorCode::RemoteUpgradeRequired => Some(format!(
2266 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2267 e.error_tag("required").unwrap_or("the latest version")
2268 )),
2269 _ => None,
2270 }
2271 });
2272 }
2273
2274 pub fn leader_id(&self) -> Option<CollaboratorId> {
2275 self.leader_id
2276 }
2277
2278 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2279 &self.buffer
2280 }
2281
2282 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2283 self.workspace.as_ref()?.0.upgrade()
2284 }
2285
2286 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2287 self.buffer().read(cx).title(cx)
2288 }
2289
2290 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2291 let git_blame_gutter_max_author_length = self
2292 .render_git_blame_gutter(cx)
2293 .then(|| {
2294 if let Some(blame) = self.blame.as_ref() {
2295 let max_author_length =
2296 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2297 Some(max_author_length)
2298 } else {
2299 None
2300 }
2301 })
2302 .flatten();
2303
2304 EditorSnapshot {
2305 mode: self.mode.clone(),
2306 show_gutter: self.show_gutter,
2307 show_line_numbers: self.show_line_numbers,
2308 show_git_diff_gutter: self.show_git_diff_gutter,
2309 show_runnables: self.show_runnables,
2310 show_breakpoints: self.show_breakpoints,
2311 git_blame_gutter_max_author_length,
2312 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2313 scroll_anchor: self.scroll_manager.anchor(),
2314 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2315 placeholder_text: self.placeholder_text.clone(),
2316 is_focused: self.focus_handle.is_focused(window),
2317 current_line_highlight: self
2318 .current_line_highlight
2319 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2320 gutter_hovered: self.gutter_hovered,
2321 }
2322 }
2323
2324 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2325 self.buffer.read(cx).language_at(point, cx)
2326 }
2327
2328 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2329 self.buffer.read(cx).read(cx).file_at(point).cloned()
2330 }
2331
2332 pub fn active_excerpt(
2333 &self,
2334 cx: &App,
2335 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2336 self.buffer
2337 .read(cx)
2338 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2339 }
2340
2341 pub fn mode(&self) -> &EditorMode {
2342 &self.mode
2343 }
2344
2345 pub fn set_mode(&mut self, mode: EditorMode) {
2346 self.mode = mode;
2347 }
2348
2349 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2350 self.collaboration_hub.as_deref()
2351 }
2352
2353 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2354 self.collaboration_hub = Some(hub);
2355 }
2356
2357 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2358 self.in_project_search = in_project_search;
2359 }
2360
2361 pub fn set_custom_context_menu(
2362 &mut self,
2363 f: impl 'static
2364 + Fn(
2365 &mut Self,
2366 DisplayPoint,
2367 &mut Window,
2368 &mut Context<Self>,
2369 ) -> Option<Entity<ui::ContextMenu>>,
2370 ) {
2371 self.custom_context_menu = Some(Box::new(f))
2372 }
2373
2374 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2375 self.completion_provider = provider;
2376 }
2377
2378 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2379 self.semantics_provider.clone()
2380 }
2381
2382 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2383 self.semantics_provider = provider;
2384 }
2385
2386 pub fn set_edit_prediction_provider<T>(
2387 &mut self,
2388 provider: Option<Entity<T>>,
2389 window: &mut Window,
2390 cx: &mut Context<Self>,
2391 ) where
2392 T: EditPredictionProvider,
2393 {
2394 self.edit_prediction_provider =
2395 provider.map(|provider| RegisteredInlineCompletionProvider {
2396 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2397 if this.focus_handle.is_focused(window) {
2398 this.update_visible_inline_completion(window, cx);
2399 }
2400 }),
2401 provider: Arc::new(provider),
2402 });
2403 self.update_edit_prediction_settings(cx);
2404 self.refresh_inline_completion(false, false, window, cx);
2405 }
2406
2407 pub fn placeholder_text(&self) -> Option<&str> {
2408 self.placeholder_text.as_deref()
2409 }
2410
2411 pub fn set_placeholder_text(
2412 &mut self,
2413 placeholder_text: impl Into<Arc<str>>,
2414 cx: &mut Context<Self>,
2415 ) {
2416 let placeholder_text = Some(placeholder_text.into());
2417 if self.placeholder_text != placeholder_text {
2418 self.placeholder_text = placeholder_text;
2419 cx.notify();
2420 }
2421 }
2422
2423 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2424 self.cursor_shape = cursor_shape;
2425
2426 // Disrupt blink for immediate user feedback that the cursor shape has changed
2427 self.blink_manager.update(cx, BlinkManager::show_cursor);
2428
2429 cx.notify();
2430 }
2431
2432 pub fn set_current_line_highlight(
2433 &mut self,
2434 current_line_highlight: Option<CurrentLineHighlight>,
2435 ) {
2436 self.current_line_highlight = current_line_highlight;
2437 }
2438
2439 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2440 self.collapse_matches = collapse_matches;
2441 }
2442
2443 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2444 let buffers = self.buffer.read(cx).all_buffers();
2445 let Some(project) = self.project.as_ref() else {
2446 return;
2447 };
2448 project.update(cx, |project, cx| {
2449 for buffer in buffers {
2450 self.registered_buffers
2451 .entry(buffer.read(cx).remote_id())
2452 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2453 }
2454 })
2455 }
2456
2457 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2458 if self.collapse_matches {
2459 return range.start..range.start;
2460 }
2461 range.clone()
2462 }
2463
2464 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2465 if self.display_map.read(cx).clip_at_line_ends != clip {
2466 self.display_map
2467 .update(cx, |map, _| map.clip_at_line_ends = clip);
2468 }
2469 }
2470
2471 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2472 self.input_enabled = input_enabled;
2473 }
2474
2475 pub fn set_inline_completions_hidden_for_vim_mode(
2476 &mut self,
2477 hidden: bool,
2478 window: &mut Window,
2479 cx: &mut Context<Self>,
2480 ) {
2481 if hidden != self.inline_completions_hidden_for_vim_mode {
2482 self.inline_completions_hidden_for_vim_mode = hidden;
2483 if hidden {
2484 self.update_visible_inline_completion(window, cx);
2485 } else {
2486 self.refresh_inline_completion(true, false, window, cx);
2487 }
2488 }
2489 }
2490
2491 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2492 self.menu_inline_completions_policy = value;
2493 }
2494
2495 pub fn set_autoindent(&mut self, autoindent: bool) {
2496 if autoindent {
2497 self.autoindent_mode = Some(AutoindentMode::EachLine);
2498 } else {
2499 self.autoindent_mode = None;
2500 }
2501 }
2502
2503 pub fn read_only(&self, cx: &App) -> bool {
2504 self.read_only || self.buffer.read(cx).read_only()
2505 }
2506
2507 pub fn set_read_only(&mut self, read_only: bool) {
2508 self.read_only = read_only;
2509 }
2510
2511 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2512 self.use_autoclose = autoclose;
2513 }
2514
2515 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2516 self.use_auto_surround = auto_surround;
2517 }
2518
2519 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2520 self.auto_replace_emoji_shortcode = auto_replace;
2521 }
2522
2523 pub fn toggle_edit_predictions(
2524 &mut self,
2525 _: &ToggleEditPrediction,
2526 window: &mut Window,
2527 cx: &mut Context<Self>,
2528 ) {
2529 if self.show_inline_completions_override.is_some() {
2530 self.set_show_edit_predictions(None, window, cx);
2531 } else {
2532 let show_edit_predictions = !self.edit_predictions_enabled();
2533 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2534 }
2535 }
2536
2537 pub fn set_show_edit_predictions(
2538 &mut self,
2539 show_edit_predictions: Option<bool>,
2540 window: &mut Window,
2541 cx: &mut Context<Self>,
2542 ) {
2543 self.show_inline_completions_override = show_edit_predictions;
2544 self.update_edit_prediction_settings(cx);
2545
2546 if let Some(false) = show_edit_predictions {
2547 self.discard_inline_completion(false, cx);
2548 } else {
2549 self.refresh_inline_completion(false, true, window, cx);
2550 }
2551 }
2552
2553 fn inline_completions_disabled_in_scope(
2554 &self,
2555 buffer: &Entity<Buffer>,
2556 buffer_position: language::Anchor,
2557 cx: &App,
2558 ) -> bool {
2559 let snapshot = buffer.read(cx).snapshot();
2560 let settings = snapshot.settings_at(buffer_position, cx);
2561
2562 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2563 return false;
2564 };
2565
2566 scope.override_name().map_or(false, |scope_name| {
2567 settings
2568 .edit_predictions_disabled_in
2569 .iter()
2570 .any(|s| s == scope_name)
2571 })
2572 }
2573
2574 pub fn set_use_modal_editing(&mut self, to: bool) {
2575 self.use_modal_editing = to;
2576 }
2577
2578 pub fn use_modal_editing(&self) -> bool {
2579 self.use_modal_editing
2580 }
2581
2582 fn selections_did_change(
2583 &mut self,
2584 local: bool,
2585 old_cursor_position: &Anchor,
2586 show_completions: bool,
2587 window: &mut Window,
2588 cx: &mut Context<Self>,
2589 ) {
2590 window.invalidate_character_coordinates();
2591
2592 // Copy selections to primary selection buffer
2593 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2594 if local {
2595 let selections = self.selections.all::<usize>(cx);
2596 let buffer_handle = self.buffer.read(cx).read(cx);
2597
2598 let mut text = String::new();
2599 for (index, selection) in selections.iter().enumerate() {
2600 let text_for_selection = buffer_handle
2601 .text_for_range(selection.start..selection.end)
2602 .collect::<String>();
2603
2604 text.push_str(&text_for_selection);
2605 if index != selections.len() - 1 {
2606 text.push('\n');
2607 }
2608 }
2609
2610 if !text.is_empty() {
2611 cx.write_to_primary(ClipboardItem::new_string(text));
2612 }
2613 }
2614
2615 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2616 self.buffer.update(cx, |buffer, cx| {
2617 buffer.set_active_selections(
2618 &self.selections.disjoint_anchors(),
2619 self.selections.line_mode,
2620 self.cursor_shape,
2621 cx,
2622 )
2623 });
2624 }
2625 let display_map = self
2626 .display_map
2627 .update(cx, |display_map, cx| display_map.snapshot(cx));
2628 let buffer = &display_map.buffer_snapshot;
2629 self.add_selections_state = None;
2630 self.select_next_state = None;
2631 self.select_prev_state = None;
2632 self.select_syntax_node_history.try_clear();
2633 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2634 self.snippet_stack
2635 .invalidate(&self.selections.disjoint_anchors(), buffer);
2636 self.take_rename(false, window, cx);
2637
2638 let new_cursor_position = self.selections.newest_anchor().head();
2639
2640 self.push_to_nav_history(
2641 *old_cursor_position,
2642 Some(new_cursor_position.to_point(buffer)),
2643 false,
2644 cx,
2645 );
2646
2647 if local {
2648 let new_cursor_position = self.selections.newest_anchor().head();
2649 let mut context_menu = self.context_menu.borrow_mut();
2650 let completion_menu = match context_menu.as_ref() {
2651 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2652 _ => {
2653 *context_menu = None;
2654 None
2655 }
2656 };
2657 if let Some(buffer_id) = new_cursor_position.buffer_id {
2658 if !self.registered_buffers.contains_key(&buffer_id) {
2659 if let Some(project) = self.project.as_ref() {
2660 project.update(cx, |project, cx| {
2661 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2662 return;
2663 };
2664 self.registered_buffers.insert(
2665 buffer_id,
2666 project.register_buffer_with_language_servers(&buffer, cx),
2667 );
2668 })
2669 }
2670 }
2671 }
2672
2673 if let Some(completion_menu) = completion_menu {
2674 let cursor_position = new_cursor_position.to_offset(buffer);
2675 let (word_range, kind) =
2676 buffer.surrounding_word(completion_menu.initial_position, true);
2677 if kind == Some(CharKind::Word)
2678 && word_range.to_inclusive().contains(&cursor_position)
2679 {
2680 let mut completion_menu = completion_menu.clone();
2681 drop(context_menu);
2682
2683 let query = Self::completion_query(buffer, cursor_position);
2684 cx.spawn(async move |this, cx| {
2685 completion_menu
2686 .filter(query.as_deref(), cx.background_executor().clone())
2687 .await;
2688
2689 this.update(cx, |this, cx| {
2690 let mut context_menu = this.context_menu.borrow_mut();
2691 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2692 else {
2693 return;
2694 };
2695
2696 if menu.id > completion_menu.id {
2697 return;
2698 }
2699
2700 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2701 drop(context_menu);
2702 cx.notify();
2703 })
2704 })
2705 .detach();
2706
2707 if show_completions {
2708 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2709 }
2710 } else {
2711 drop(context_menu);
2712 self.hide_context_menu(window, cx);
2713 }
2714 } else {
2715 drop(context_menu);
2716 }
2717
2718 hide_hover(self, cx);
2719
2720 if old_cursor_position.to_display_point(&display_map).row()
2721 != new_cursor_position.to_display_point(&display_map).row()
2722 {
2723 self.available_code_actions.take();
2724 }
2725 self.refresh_code_actions(window, cx);
2726 self.refresh_document_highlights(cx);
2727 self.refresh_selected_text_highlights(false, window, cx);
2728 refresh_matching_bracket_highlights(self, window, cx);
2729 self.update_visible_inline_completion(window, cx);
2730 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2731 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2732 self.inline_blame_popover.take();
2733 if self.git_blame_inline_enabled {
2734 self.start_inline_blame_timer(window, cx);
2735 }
2736 }
2737
2738 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2739 cx.emit(EditorEvent::SelectionsChanged { local });
2740
2741 let selections = &self.selections.disjoint;
2742 if selections.len() == 1 {
2743 cx.emit(SearchEvent::ActiveMatchChanged)
2744 }
2745 if local {
2746 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2747 let inmemory_selections = selections
2748 .iter()
2749 .map(|s| {
2750 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2751 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2752 })
2753 .collect();
2754 self.update_restoration_data(cx, |data| {
2755 data.selections = inmemory_selections;
2756 });
2757
2758 if WorkspaceSettings::get(None, cx).restore_on_startup
2759 != RestoreOnStartupBehavior::None
2760 {
2761 if let Some(workspace_id) =
2762 self.workspace.as_ref().and_then(|workspace| workspace.1)
2763 {
2764 let snapshot = self.buffer().read(cx).snapshot(cx);
2765 let selections = selections.clone();
2766 let background_executor = cx.background_executor().clone();
2767 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2768 self.serialize_selections = cx.background_spawn(async move {
2769 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2770 let db_selections = selections
2771 .iter()
2772 .map(|selection| {
2773 (
2774 selection.start.to_offset(&snapshot),
2775 selection.end.to_offset(&snapshot),
2776 )
2777 })
2778 .collect();
2779
2780 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2781 .await
2782 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2783 .log_err();
2784 });
2785 }
2786 }
2787 }
2788 }
2789
2790 cx.notify();
2791 }
2792
2793 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2794 use text::ToOffset as _;
2795 use text::ToPoint as _;
2796
2797 if self.mode.is_minimap()
2798 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2799 {
2800 return;
2801 }
2802
2803 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2804 return;
2805 };
2806
2807 let snapshot = singleton.read(cx).snapshot();
2808 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2809 let display_snapshot = display_map.snapshot(cx);
2810
2811 display_snapshot
2812 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2813 .map(|fold| {
2814 fold.range.start.text_anchor.to_point(&snapshot)
2815 ..fold.range.end.text_anchor.to_point(&snapshot)
2816 })
2817 .collect()
2818 });
2819 self.update_restoration_data(cx, |data| {
2820 data.folds = inmemory_folds;
2821 });
2822
2823 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2824 return;
2825 };
2826 let background_executor = cx.background_executor().clone();
2827 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2828 let db_folds = self.display_map.update(cx, |display_map, cx| {
2829 display_map
2830 .snapshot(cx)
2831 .folds_in_range(0..snapshot.len())
2832 .map(|fold| {
2833 (
2834 fold.range.start.text_anchor.to_offset(&snapshot),
2835 fold.range.end.text_anchor.to_offset(&snapshot),
2836 )
2837 })
2838 .collect()
2839 });
2840 self.serialize_folds = cx.background_spawn(async move {
2841 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2842 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2843 .await
2844 .with_context(|| {
2845 format!(
2846 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2847 )
2848 })
2849 .log_err();
2850 });
2851 }
2852
2853 pub fn sync_selections(
2854 &mut self,
2855 other: Entity<Editor>,
2856 cx: &mut Context<Self>,
2857 ) -> gpui::Subscription {
2858 let other_selections = other.read(cx).selections.disjoint.to_vec();
2859 self.selections.change_with(cx, |selections| {
2860 selections.select_anchors(other_selections);
2861 });
2862
2863 let other_subscription =
2864 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2865 EditorEvent::SelectionsChanged { local: true } => {
2866 let other_selections = other.read(cx).selections.disjoint.to_vec();
2867 if other_selections.is_empty() {
2868 return;
2869 }
2870 this.selections.change_with(cx, |selections| {
2871 selections.select_anchors(other_selections);
2872 });
2873 }
2874 _ => {}
2875 });
2876
2877 let this_subscription =
2878 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2879 EditorEvent::SelectionsChanged { local: true } => {
2880 let these_selections = this.selections.disjoint.to_vec();
2881 if these_selections.is_empty() {
2882 return;
2883 }
2884 other.update(cx, |other_editor, cx| {
2885 other_editor.selections.change_with(cx, |selections| {
2886 selections.select_anchors(these_selections);
2887 })
2888 });
2889 }
2890 _ => {}
2891 });
2892
2893 Subscription::join(other_subscription, this_subscription)
2894 }
2895
2896 pub fn change_selections<R>(
2897 &mut self,
2898 autoscroll: Option<Autoscroll>,
2899 window: &mut Window,
2900 cx: &mut Context<Self>,
2901 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2902 ) -> R {
2903 self.change_selections_inner(autoscroll, true, window, cx, change)
2904 }
2905
2906 fn change_selections_inner<R>(
2907 &mut self,
2908 autoscroll: Option<Autoscroll>,
2909 request_completions: bool,
2910 window: &mut Window,
2911 cx: &mut Context<Self>,
2912 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2913 ) -> R {
2914 let old_cursor_position = self.selections.newest_anchor().head();
2915 self.push_to_selection_history();
2916
2917 let (changed, result) = self.selections.change_with(cx, change);
2918
2919 if changed {
2920 if let Some(autoscroll) = autoscroll {
2921 self.request_autoscroll(autoscroll, cx);
2922 }
2923 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2924
2925 if self.should_open_signature_help_automatically(
2926 &old_cursor_position,
2927 self.signature_help_state.backspace_pressed(),
2928 cx,
2929 ) {
2930 self.show_signature_help(&ShowSignatureHelp, window, cx);
2931 }
2932 self.signature_help_state.set_backspace_pressed(false);
2933 }
2934
2935 result
2936 }
2937
2938 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2939 where
2940 I: IntoIterator<Item = (Range<S>, T)>,
2941 S: ToOffset,
2942 T: Into<Arc<str>>,
2943 {
2944 if self.read_only(cx) {
2945 return;
2946 }
2947
2948 self.buffer
2949 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2950 }
2951
2952 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2953 where
2954 I: IntoIterator<Item = (Range<S>, T)>,
2955 S: ToOffset,
2956 T: Into<Arc<str>>,
2957 {
2958 if self.read_only(cx) {
2959 return;
2960 }
2961
2962 self.buffer.update(cx, |buffer, cx| {
2963 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2964 });
2965 }
2966
2967 pub fn edit_with_block_indent<I, S, T>(
2968 &mut self,
2969 edits: I,
2970 original_indent_columns: Vec<Option<u32>>,
2971 cx: &mut Context<Self>,
2972 ) where
2973 I: IntoIterator<Item = (Range<S>, T)>,
2974 S: ToOffset,
2975 T: Into<Arc<str>>,
2976 {
2977 if self.read_only(cx) {
2978 return;
2979 }
2980
2981 self.buffer.update(cx, |buffer, cx| {
2982 buffer.edit(
2983 edits,
2984 Some(AutoindentMode::Block {
2985 original_indent_columns,
2986 }),
2987 cx,
2988 )
2989 });
2990 }
2991
2992 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2993 self.hide_context_menu(window, cx);
2994
2995 match phase {
2996 SelectPhase::Begin {
2997 position,
2998 add,
2999 click_count,
3000 } => self.begin_selection(position, add, click_count, window, cx),
3001 SelectPhase::BeginColumnar {
3002 position,
3003 goal_column,
3004 reset,
3005 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3006 SelectPhase::Extend {
3007 position,
3008 click_count,
3009 } => self.extend_selection(position, click_count, window, cx),
3010 SelectPhase::Update {
3011 position,
3012 goal_column,
3013 scroll_delta,
3014 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3015 SelectPhase::End => self.end_selection(window, cx),
3016 }
3017 }
3018
3019 fn extend_selection(
3020 &mut self,
3021 position: DisplayPoint,
3022 click_count: usize,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3027 let tail = self.selections.newest::<usize>(cx).tail();
3028 self.begin_selection(position, false, click_count, window, cx);
3029
3030 let position = position.to_offset(&display_map, Bias::Left);
3031 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3032
3033 let mut pending_selection = self
3034 .selections
3035 .pending_anchor()
3036 .expect("extend_selection not called with pending selection");
3037 if position >= tail {
3038 pending_selection.start = tail_anchor;
3039 } else {
3040 pending_selection.end = tail_anchor;
3041 pending_selection.reversed = true;
3042 }
3043
3044 let mut pending_mode = self.selections.pending_mode().unwrap();
3045 match &mut pending_mode {
3046 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3047 _ => {}
3048 }
3049
3050 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3051
3052 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3053 s.set_pending(pending_selection, pending_mode)
3054 });
3055 }
3056
3057 fn begin_selection(
3058 &mut self,
3059 position: DisplayPoint,
3060 add: bool,
3061 click_count: usize,
3062 window: &mut Window,
3063 cx: &mut Context<Self>,
3064 ) {
3065 if !self.focus_handle.is_focused(window) {
3066 self.last_focused_descendant = None;
3067 window.focus(&self.focus_handle);
3068 }
3069
3070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3071 let buffer = &display_map.buffer_snapshot;
3072 let position = display_map.clip_point(position, Bias::Left);
3073
3074 let start;
3075 let end;
3076 let mode;
3077 let mut auto_scroll;
3078 match click_count {
3079 1 => {
3080 start = buffer.anchor_before(position.to_point(&display_map));
3081 end = start;
3082 mode = SelectMode::Character;
3083 auto_scroll = true;
3084 }
3085 2 => {
3086 let range = movement::surrounding_word(&display_map, position);
3087 start = buffer.anchor_before(range.start.to_point(&display_map));
3088 end = buffer.anchor_before(range.end.to_point(&display_map));
3089 mode = SelectMode::Word(start..end);
3090 auto_scroll = true;
3091 }
3092 3 => {
3093 let position = display_map
3094 .clip_point(position, Bias::Left)
3095 .to_point(&display_map);
3096 let line_start = display_map.prev_line_boundary(position).0;
3097 let next_line_start = buffer.clip_point(
3098 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3099 Bias::Left,
3100 );
3101 start = buffer.anchor_before(line_start);
3102 end = buffer.anchor_before(next_line_start);
3103 mode = SelectMode::Line(start..end);
3104 auto_scroll = true;
3105 }
3106 _ => {
3107 start = buffer.anchor_before(0);
3108 end = buffer.anchor_before(buffer.len());
3109 mode = SelectMode::All;
3110 auto_scroll = false;
3111 }
3112 }
3113 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3114
3115 let point_to_delete: Option<usize> = {
3116 let selected_points: Vec<Selection<Point>> =
3117 self.selections.disjoint_in_range(start..end, cx);
3118
3119 if !add || click_count > 1 {
3120 None
3121 } else if !selected_points.is_empty() {
3122 Some(selected_points[0].id)
3123 } else {
3124 let clicked_point_already_selected =
3125 self.selections.disjoint.iter().find(|selection| {
3126 selection.start.to_point(buffer) == start.to_point(buffer)
3127 || selection.end.to_point(buffer) == end.to_point(buffer)
3128 });
3129
3130 clicked_point_already_selected.map(|selection| selection.id)
3131 }
3132 };
3133
3134 let selections_count = self.selections.count();
3135
3136 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3137 if let Some(point_to_delete) = point_to_delete {
3138 s.delete(point_to_delete);
3139
3140 if selections_count == 1 {
3141 s.set_pending_anchor_range(start..end, mode);
3142 }
3143 } else {
3144 if !add {
3145 s.clear_disjoint();
3146 }
3147
3148 s.set_pending_anchor_range(start..end, mode);
3149 }
3150 });
3151 }
3152
3153 fn begin_columnar_selection(
3154 &mut self,
3155 position: DisplayPoint,
3156 goal_column: u32,
3157 reset: bool,
3158 window: &mut Window,
3159 cx: &mut Context<Self>,
3160 ) {
3161 if !self.focus_handle.is_focused(window) {
3162 self.last_focused_descendant = None;
3163 window.focus(&self.focus_handle);
3164 }
3165
3166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3167
3168 if reset {
3169 let pointer_position = display_map
3170 .buffer_snapshot
3171 .anchor_before(position.to_point(&display_map));
3172
3173 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3174 s.clear_disjoint();
3175 s.set_pending_anchor_range(
3176 pointer_position..pointer_position,
3177 SelectMode::Character,
3178 );
3179 });
3180 }
3181
3182 let tail = self.selections.newest::<Point>(cx).tail();
3183 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3184
3185 if !reset {
3186 self.select_columns(
3187 tail.to_display_point(&display_map),
3188 position,
3189 goal_column,
3190 &display_map,
3191 window,
3192 cx,
3193 );
3194 }
3195 }
3196
3197 fn update_selection(
3198 &mut self,
3199 position: DisplayPoint,
3200 goal_column: u32,
3201 scroll_delta: gpui::Point<f32>,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3206
3207 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3208 let tail = tail.to_display_point(&display_map);
3209 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3210 } else if let Some(mut pending) = self.selections.pending_anchor() {
3211 let buffer = self.buffer.read(cx).snapshot(cx);
3212 let head;
3213 let tail;
3214 let mode = self.selections.pending_mode().unwrap();
3215 match &mode {
3216 SelectMode::Character => {
3217 head = position.to_point(&display_map);
3218 tail = pending.tail().to_point(&buffer);
3219 }
3220 SelectMode::Word(original_range) => {
3221 let original_display_range = original_range.start.to_display_point(&display_map)
3222 ..original_range.end.to_display_point(&display_map);
3223 let original_buffer_range = original_display_range.start.to_point(&display_map)
3224 ..original_display_range.end.to_point(&display_map);
3225 if movement::is_inside_word(&display_map, position)
3226 || original_display_range.contains(&position)
3227 {
3228 let word_range = movement::surrounding_word(&display_map, position);
3229 if word_range.start < original_display_range.start {
3230 head = word_range.start.to_point(&display_map);
3231 } else {
3232 head = word_range.end.to_point(&display_map);
3233 }
3234 } else {
3235 head = position.to_point(&display_map);
3236 }
3237
3238 if head <= original_buffer_range.start {
3239 tail = original_buffer_range.end;
3240 } else {
3241 tail = original_buffer_range.start;
3242 }
3243 }
3244 SelectMode::Line(original_range) => {
3245 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3246
3247 let position = display_map
3248 .clip_point(position, Bias::Left)
3249 .to_point(&display_map);
3250 let line_start = display_map.prev_line_boundary(position).0;
3251 let next_line_start = buffer.clip_point(
3252 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3253 Bias::Left,
3254 );
3255
3256 if line_start < original_range.start {
3257 head = line_start
3258 } else {
3259 head = next_line_start
3260 }
3261
3262 if head <= original_range.start {
3263 tail = original_range.end;
3264 } else {
3265 tail = original_range.start;
3266 }
3267 }
3268 SelectMode::All => {
3269 return;
3270 }
3271 };
3272
3273 if head < tail {
3274 pending.start = buffer.anchor_before(head);
3275 pending.end = buffer.anchor_before(tail);
3276 pending.reversed = true;
3277 } else {
3278 pending.start = buffer.anchor_before(tail);
3279 pending.end = buffer.anchor_before(head);
3280 pending.reversed = false;
3281 }
3282
3283 self.change_selections(None, window, cx, |s| {
3284 s.set_pending(pending, mode);
3285 });
3286 } else {
3287 log::error!("update_selection dispatched with no pending selection");
3288 return;
3289 }
3290
3291 self.apply_scroll_delta(scroll_delta, window, cx);
3292 cx.notify();
3293 }
3294
3295 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3296 self.columnar_selection_tail.take();
3297 if self.selections.pending_anchor().is_some() {
3298 let selections = self.selections.all::<usize>(cx);
3299 self.change_selections(None, window, cx, |s| {
3300 s.select(selections);
3301 s.clear_pending();
3302 });
3303 }
3304 }
3305
3306 fn select_columns(
3307 &mut self,
3308 tail: DisplayPoint,
3309 head: DisplayPoint,
3310 goal_column: u32,
3311 display_map: &DisplaySnapshot,
3312 window: &mut Window,
3313 cx: &mut Context<Self>,
3314 ) {
3315 let start_row = cmp::min(tail.row(), head.row());
3316 let end_row = cmp::max(tail.row(), head.row());
3317 let start_column = cmp::min(tail.column(), goal_column);
3318 let end_column = cmp::max(tail.column(), goal_column);
3319 let reversed = start_column < tail.column();
3320
3321 let selection_ranges = (start_row.0..=end_row.0)
3322 .map(DisplayRow)
3323 .filter_map(|row| {
3324 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3325 let start = display_map
3326 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3327 .to_point(display_map);
3328 let end = display_map
3329 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3330 .to_point(display_map);
3331 if reversed {
3332 Some(end..start)
3333 } else {
3334 Some(start..end)
3335 }
3336 } else {
3337 None
3338 }
3339 })
3340 .collect::<Vec<_>>();
3341
3342 self.change_selections(None, window, cx, |s| {
3343 s.select_ranges(selection_ranges);
3344 });
3345 cx.notify();
3346 }
3347
3348 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3349 self.selections
3350 .all_adjusted(cx)
3351 .iter()
3352 .any(|selection| !selection.is_empty())
3353 }
3354
3355 pub fn has_pending_nonempty_selection(&self) -> bool {
3356 let pending_nonempty_selection = match self.selections.pending_anchor() {
3357 Some(Selection { start, end, .. }) => start != end,
3358 None => false,
3359 };
3360
3361 pending_nonempty_selection
3362 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3363 }
3364
3365 pub fn has_pending_selection(&self) -> bool {
3366 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3367 }
3368
3369 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3370 self.selection_mark_mode = false;
3371
3372 if self.clear_expanded_diff_hunks(cx) {
3373 cx.notify();
3374 return;
3375 }
3376 if self.dismiss_menus_and_popups(true, window, cx) {
3377 return;
3378 }
3379
3380 if self.mode.is_full()
3381 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3382 {
3383 return;
3384 }
3385
3386 cx.propagate();
3387 }
3388
3389 pub fn dismiss_menus_and_popups(
3390 &mut self,
3391 is_user_requested: bool,
3392 window: &mut Window,
3393 cx: &mut Context<Self>,
3394 ) -> bool {
3395 if self.take_rename(false, window, cx).is_some() {
3396 return true;
3397 }
3398
3399 if hide_hover(self, cx) {
3400 return true;
3401 }
3402
3403 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3404 return true;
3405 }
3406
3407 if self.hide_context_menu(window, cx).is_some() {
3408 return true;
3409 }
3410
3411 if self.mouse_context_menu.take().is_some() {
3412 return true;
3413 }
3414
3415 if is_user_requested && self.discard_inline_completion(true, cx) {
3416 return true;
3417 }
3418
3419 if self.snippet_stack.pop().is_some() {
3420 return true;
3421 }
3422
3423 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3424 self.dismiss_diagnostics(cx);
3425 return true;
3426 }
3427
3428 false
3429 }
3430
3431 fn linked_editing_ranges_for(
3432 &self,
3433 selection: Range<text::Anchor>,
3434 cx: &App,
3435 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3436 if self.linked_edit_ranges.is_empty() {
3437 return None;
3438 }
3439 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3440 selection.end.buffer_id.and_then(|end_buffer_id| {
3441 if selection.start.buffer_id != Some(end_buffer_id) {
3442 return None;
3443 }
3444 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3445 let snapshot = buffer.read(cx).snapshot();
3446 self.linked_edit_ranges
3447 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3448 .map(|ranges| (ranges, snapshot, buffer))
3449 })?;
3450 use text::ToOffset as TO;
3451 // find offset from the start of current range to current cursor position
3452 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3453
3454 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3455 let start_difference = start_offset - start_byte_offset;
3456 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3457 let end_difference = end_offset - start_byte_offset;
3458 // Current range has associated linked ranges.
3459 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3460 for range in linked_ranges.iter() {
3461 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3462 let end_offset = start_offset + end_difference;
3463 let start_offset = start_offset + start_difference;
3464 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3465 continue;
3466 }
3467 if self.selections.disjoint_anchor_ranges().any(|s| {
3468 if s.start.buffer_id != selection.start.buffer_id
3469 || s.end.buffer_id != selection.end.buffer_id
3470 {
3471 return false;
3472 }
3473 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3474 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3475 }) {
3476 continue;
3477 }
3478 let start = buffer_snapshot.anchor_after(start_offset);
3479 let end = buffer_snapshot.anchor_after(end_offset);
3480 linked_edits
3481 .entry(buffer.clone())
3482 .or_default()
3483 .push(start..end);
3484 }
3485 Some(linked_edits)
3486 }
3487
3488 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3489 let text: Arc<str> = text.into();
3490
3491 if self.read_only(cx) {
3492 return;
3493 }
3494
3495 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3496
3497 let selections = self.selections.all_adjusted(cx);
3498 let mut bracket_inserted = false;
3499 let mut edits = Vec::new();
3500 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3501 let mut new_selections = Vec::with_capacity(selections.len());
3502 let mut new_autoclose_regions = Vec::new();
3503 let snapshot = self.buffer.read(cx).read(cx);
3504 let mut clear_linked_edit_ranges = false;
3505
3506 for (selection, autoclose_region) in
3507 self.selections_with_autoclose_regions(selections, &snapshot)
3508 {
3509 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3510 // Determine if the inserted text matches the opening or closing
3511 // bracket of any of this language's bracket pairs.
3512 let mut bracket_pair = None;
3513 let mut is_bracket_pair_start = false;
3514 let mut is_bracket_pair_end = false;
3515 if !text.is_empty() {
3516 let mut bracket_pair_matching_end = None;
3517 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3518 // and they are removing the character that triggered IME popup.
3519 for (pair, enabled) in scope.brackets() {
3520 if !pair.close && !pair.surround {
3521 continue;
3522 }
3523
3524 if enabled && pair.start.ends_with(text.as_ref()) {
3525 let prefix_len = pair.start.len() - text.len();
3526 let preceding_text_matches_prefix = prefix_len == 0
3527 || (selection.start.column >= (prefix_len as u32)
3528 && snapshot.contains_str_at(
3529 Point::new(
3530 selection.start.row,
3531 selection.start.column - (prefix_len as u32),
3532 ),
3533 &pair.start[..prefix_len],
3534 ));
3535 if preceding_text_matches_prefix {
3536 bracket_pair = Some(pair.clone());
3537 is_bracket_pair_start = true;
3538 break;
3539 }
3540 }
3541 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3542 {
3543 // take first bracket pair matching end, but don't break in case a later bracket
3544 // pair matches start
3545 bracket_pair_matching_end = Some(pair.clone());
3546 }
3547 }
3548 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3549 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3550 is_bracket_pair_end = true;
3551 }
3552 }
3553
3554 if let Some(bracket_pair) = bracket_pair {
3555 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3556 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3557 let auto_surround =
3558 self.use_auto_surround && snapshot_settings.use_auto_surround;
3559 if selection.is_empty() {
3560 if is_bracket_pair_start {
3561 // If the inserted text is a suffix of an opening bracket and the
3562 // selection is preceded by the rest of the opening bracket, then
3563 // insert the closing bracket.
3564 let following_text_allows_autoclose = snapshot
3565 .chars_at(selection.start)
3566 .next()
3567 .map_or(true, |c| scope.should_autoclose_before(c));
3568
3569 let preceding_text_allows_autoclose = selection.start.column == 0
3570 || snapshot.reversed_chars_at(selection.start).next().map_or(
3571 true,
3572 |c| {
3573 bracket_pair.start != bracket_pair.end
3574 || !snapshot
3575 .char_classifier_at(selection.start)
3576 .is_word(c)
3577 },
3578 );
3579
3580 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3581 && bracket_pair.start.len() == 1
3582 {
3583 let target = bracket_pair.start.chars().next().unwrap();
3584 let current_line_count = snapshot
3585 .reversed_chars_at(selection.start)
3586 .take_while(|&c| c != '\n')
3587 .filter(|&c| c == target)
3588 .count();
3589 current_line_count % 2 == 1
3590 } else {
3591 false
3592 };
3593
3594 if autoclose
3595 && bracket_pair.close
3596 && following_text_allows_autoclose
3597 && preceding_text_allows_autoclose
3598 && !is_closing_quote
3599 {
3600 let anchor = snapshot.anchor_before(selection.end);
3601 new_selections.push((selection.map(|_| anchor), text.len()));
3602 new_autoclose_regions.push((
3603 anchor,
3604 text.len(),
3605 selection.id,
3606 bracket_pair.clone(),
3607 ));
3608 edits.push((
3609 selection.range(),
3610 format!("{}{}", text, bracket_pair.end).into(),
3611 ));
3612 bracket_inserted = true;
3613 continue;
3614 }
3615 }
3616
3617 if let Some(region) = autoclose_region {
3618 // If the selection is followed by an auto-inserted closing bracket,
3619 // then don't insert that closing bracket again; just move the selection
3620 // past the closing bracket.
3621 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3622 && text.as_ref() == region.pair.end.as_str();
3623 if should_skip {
3624 let anchor = snapshot.anchor_after(selection.end);
3625 new_selections
3626 .push((selection.map(|_| anchor), region.pair.end.len()));
3627 continue;
3628 }
3629 }
3630
3631 let always_treat_brackets_as_autoclosed = snapshot
3632 .language_settings_at(selection.start, cx)
3633 .always_treat_brackets_as_autoclosed;
3634 if always_treat_brackets_as_autoclosed
3635 && is_bracket_pair_end
3636 && snapshot.contains_str_at(selection.end, text.as_ref())
3637 {
3638 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3639 // and the inserted text is a closing bracket and the selection is followed
3640 // by the closing bracket then move the selection past the closing bracket.
3641 let anchor = snapshot.anchor_after(selection.end);
3642 new_selections.push((selection.map(|_| anchor), text.len()));
3643 continue;
3644 }
3645 }
3646 // If an opening bracket is 1 character long and is typed while
3647 // text is selected, then surround that text with the bracket pair.
3648 else if auto_surround
3649 && bracket_pair.surround
3650 && is_bracket_pair_start
3651 && bracket_pair.start.chars().count() == 1
3652 {
3653 edits.push((selection.start..selection.start, text.clone()));
3654 edits.push((
3655 selection.end..selection.end,
3656 bracket_pair.end.as_str().into(),
3657 ));
3658 bracket_inserted = true;
3659 new_selections.push((
3660 Selection {
3661 id: selection.id,
3662 start: snapshot.anchor_after(selection.start),
3663 end: snapshot.anchor_before(selection.end),
3664 reversed: selection.reversed,
3665 goal: selection.goal,
3666 },
3667 0,
3668 ));
3669 continue;
3670 }
3671 }
3672 }
3673
3674 if self.auto_replace_emoji_shortcode
3675 && selection.is_empty()
3676 && text.as_ref().ends_with(':')
3677 {
3678 if let Some(possible_emoji_short_code) =
3679 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3680 {
3681 if !possible_emoji_short_code.is_empty() {
3682 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3683 let emoji_shortcode_start = Point::new(
3684 selection.start.row,
3685 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3686 );
3687
3688 // Remove shortcode from buffer
3689 edits.push((
3690 emoji_shortcode_start..selection.start,
3691 "".to_string().into(),
3692 ));
3693 new_selections.push((
3694 Selection {
3695 id: selection.id,
3696 start: snapshot.anchor_after(emoji_shortcode_start),
3697 end: snapshot.anchor_before(selection.start),
3698 reversed: selection.reversed,
3699 goal: selection.goal,
3700 },
3701 0,
3702 ));
3703
3704 // Insert emoji
3705 let selection_start_anchor = snapshot.anchor_after(selection.start);
3706 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3707 edits.push((selection.start..selection.end, emoji.to_string().into()));
3708
3709 continue;
3710 }
3711 }
3712 }
3713 }
3714
3715 // If not handling any auto-close operation, then just replace the selected
3716 // text with the given input and move the selection to the end of the
3717 // newly inserted text.
3718 let anchor = snapshot.anchor_after(selection.end);
3719 if !self.linked_edit_ranges.is_empty() {
3720 let start_anchor = snapshot.anchor_before(selection.start);
3721
3722 let is_word_char = text.chars().next().map_or(true, |char| {
3723 let classifier = snapshot
3724 .char_classifier_at(start_anchor.to_offset(&snapshot))
3725 .ignore_punctuation(true);
3726 classifier.is_word(char)
3727 });
3728
3729 if is_word_char {
3730 if let Some(ranges) = self
3731 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3732 {
3733 for (buffer, edits) in ranges {
3734 linked_edits
3735 .entry(buffer.clone())
3736 .or_default()
3737 .extend(edits.into_iter().map(|range| (range, text.clone())));
3738 }
3739 }
3740 } else {
3741 clear_linked_edit_ranges = true;
3742 }
3743 }
3744
3745 new_selections.push((selection.map(|_| anchor), 0));
3746 edits.push((selection.start..selection.end, text.clone()));
3747 }
3748
3749 drop(snapshot);
3750
3751 self.transact(window, cx, |this, window, cx| {
3752 if clear_linked_edit_ranges {
3753 this.linked_edit_ranges.clear();
3754 }
3755 let initial_buffer_versions =
3756 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3757
3758 this.buffer.update(cx, |buffer, cx| {
3759 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3760 });
3761 for (buffer, edits) in linked_edits {
3762 buffer.update(cx, |buffer, cx| {
3763 let snapshot = buffer.snapshot();
3764 let edits = edits
3765 .into_iter()
3766 .map(|(range, text)| {
3767 use text::ToPoint as TP;
3768 let end_point = TP::to_point(&range.end, &snapshot);
3769 let start_point = TP::to_point(&range.start, &snapshot);
3770 (start_point..end_point, text)
3771 })
3772 .sorted_by_key(|(range, _)| range.start);
3773 buffer.edit(edits, None, cx);
3774 })
3775 }
3776 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3777 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3778 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3779 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3780 .zip(new_selection_deltas)
3781 .map(|(selection, delta)| Selection {
3782 id: selection.id,
3783 start: selection.start + delta,
3784 end: selection.end + delta,
3785 reversed: selection.reversed,
3786 goal: SelectionGoal::None,
3787 })
3788 .collect::<Vec<_>>();
3789
3790 let mut i = 0;
3791 for (position, delta, selection_id, pair) in new_autoclose_regions {
3792 let position = position.to_offset(&map.buffer_snapshot) + delta;
3793 let start = map.buffer_snapshot.anchor_before(position);
3794 let end = map.buffer_snapshot.anchor_after(position);
3795 while let Some(existing_state) = this.autoclose_regions.get(i) {
3796 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3797 Ordering::Less => i += 1,
3798 Ordering::Greater => break,
3799 Ordering::Equal => {
3800 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3801 Ordering::Less => i += 1,
3802 Ordering::Equal => break,
3803 Ordering::Greater => break,
3804 }
3805 }
3806 }
3807 }
3808 this.autoclose_regions.insert(
3809 i,
3810 AutocloseRegion {
3811 selection_id,
3812 range: start..end,
3813 pair,
3814 },
3815 );
3816 }
3817
3818 let had_active_inline_completion = this.has_active_inline_completion();
3819 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3820 s.select(new_selections)
3821 });
3822
3823 if !bracket_inserted {
3824 if let Some(on_type_format_task) =
3825 this.trigger_on_type_formatting(text.to_string(), window, cx)
3826 {
3827 on_type_format_task.detach_and_log_err(cx);
3828 }
3829 }
3830
3831 let editor_settings = EditorSettings::get_global(cx);
3832 if bracket_inserted
3833 && (editor_settings.auto_signature_help
3834 || editor_settings.show_signature_help_after_edits)
3835 {
3836 this.show_signature_help(&ShowSignatureHelp, window, cx);
3837 }
3838
3839 let trigger_in_words =
3840 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3841 if this.hard_wrap.is_some() {
3842 let latest: Range<Point> = this.selections.newest(cx).range();
3843 if latest.is_empty()
3844 && this
3845 .buffer()
3846 .read(cx)
3847 .snapshot(cx)
3848 .line_len(MultiBufferRow(latest.start.row))
3849 == latest.start.column
3850 {
3851 this.rewrap_impl(
3852 RewrapOptions {
3853 override_language_settings: true,
3854 preserve_existing_whitespace: true,
3855 },
3856 cx,
3857 )
3858 }
3859 }
3860 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3861 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3862 this.refresh_inline_completion(true, false, window, cx);
3863 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3864 });
3865 }
3866
3867 fn find_possible_emoji_shortcode_at_position(
3868 snapshot: &MultiBufferSnapshot,
3869 position: Point,
3870 ) -> Option<String> {
3871 let mut chars = Vec::new();
3872 let mut found_colon = false;
3873 for char in snapshot.reversed_chars_at(position).take(100) {
3874 // Found a possible emoji shortcode in the middle of the buffer
3875 if found_colon {
3876 if char.is_whitespace() {
3877 chars.reverse();
3878 return Some(chars.iter().collect());
3879 }
3880 // If the previous character is not a whitespace, we are in the middle of a word
3881 // and we only want to complete the shortcode if the word is made up of other emojis
3882 let mut containing_word = String::new();
3883 for ch in snapshot
3884 .reversed_chars_at(position)
3885 .skip(chars.len() + 1)
3886 .take(100)
3887 {
3888 if ch.is_whitespace() {
3889 break;
3890 }
3891 containing_word.push(ch);
3892 }
3893 let containing_word = containing_word.chars().rev().collect::<String>();
3894 if util::word_consists_of_emojis(containing_word.as_str()) {
3895 chars.reverse();
3896 return Some(chars.iter().collect());
3897 }
3898 }
3899
3900 if char.is_whitespace() || !char.is_ascii() {
3901 return None;
3902 }
3903 if char == ':' {
3904 found_colon = true;
3905 } else {
3906 chars.push(char);
3907 }
3908 }
3909 // Found a possible emoji shortcode at the beginning of the buffer
3910 chars.reverse();
3911 Some(chars.iter().collect())
3912 }
3913
3914 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3916 self.transact(window, cx, |this, window, cx| {
3917 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3918 let selections = this.selections.all::<usize>(cx);
3919 let multi_buffer = this.buffer.read(cx);
3920 let buffer = multi_buffer.snapshot(cx);
3921 selections
3922 .iter()
3923 .map(|selection| {
3924 let start_point = selection.start.to_point(&buffer);
3925 let mut existing_indent =
3926 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3927 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3928 let start = selection.start;
3929 let end = selection.end;
3930 let selection_is_empty = start == end;
3931 let language_scope = buffer.language_scope_at(start);
3932 let (
3933 comment_delimiter,
3934 doc_delimiter,
3935 insert_extra_newline,
3936 indent_on_newline,
3937 indent_on_extra_newline,
3938 ) = if let Some(language) = &language_scope {
3939 let mut insert_extra_newline =
3940 insert_extra_newline_brackets(&buffer, start..end, language)
3941 || insert_extra_newline_tree_sitter(&buffer, start..end);
3942
3943 // Comment extension on newline is allowed only for cursor selections
3944 let comment_delimiter = maybe!({
3945 if !selection_is_empty {
3946 return None;
3947 }
3948
3949 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3950 return None;
3951 }
3952
3953 let delimiters = language.line_comment_prefixes();
3954 let max_len_of_delimiter =
3955 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3956 let (snapshot, range) =
3957 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3958
3959 let num_of_whitespaces = snapshot
3960 .chars_for_range(range.clone())
3961 .take_while(|c| c.is_whitespace())
3962 .count();
3963 let comment_candidate = snapshot
3964 .chars_for_range(range)
3965 .skip(num_of_whitespaces)
3966 .take(max_len_of_delimiter)
3967 .collect::<String>();
3968 let (delimiter, trimmed_len) = delimiters
3969 .iter()
3970 .filter_map(|delimiter| {
3971 let prefix = delimiter.trim_end();
3972 if comment_candidate.starts_with(prefix) {
3973 Some((delimiter, prefix.len()))
3974 } else {
3975 None
3976 }
3977 })
3978 .max_by_key(|(_, len)| *len)?;
3979
3980 let cursor_is_placed_after_comment_marker =
3981 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3982 if cursor_is_placed_after_comment_marker {
3983 Some(delimiter.clone())
3984 } else {
3985 None
3986 }
3987 });
3988
3989 let mut indent_on_newline = IndentSize::spaces(0);
3990 let mut indent_on_extra_newline = IndentSize::spaces(0);
3991
3992 let doc_delimiter = maybe!({
3993 if !selection_is_empty {
3994 return None;
3995 }
3996
3997 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3998 return None;
3999 }
4000
4001 let DocumentationConfig {
4002 start: start_tag,
4003 end: end_tag,
4004 prefix: delimiter,
4005 tab_size: len,
4006 } = language.documentation()?;
4007
4008 let (snapshot, range) =
4009 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4010
4011 let num_of_whitespaces = snapshot
4012 .chars_for_range(range.clone())
4013 .take_while(|c| c.is_whitespace())
4014 .count();
4015
4016 let cursor_is_after_start_tag = {
4017 let start_tag_len = start_tag.len();
4018 let start_tag_line = snapshot
4019 .chars_for_range(range.clone())
4020 .skip(num_of_whitespaces)
4021 .take(start_tag_len)
4022 .collect::<String>();
4023 if start_tag_line.starts_with(start_tag.as_ref()) {
4024 num_of_whitespaces + start_tag_len
4025 <= start_point.column as usize
4026 } else {
4027 false
4028 }
4029 };
4030
4031 let cursor_is_after_delimiter = {
4032 let delimiter_trim = delimiter.trim_end();
4033 let delimiter_line = snapshot
4034 .chars_for_range(range.clone())
4035 .skip(num_of_whitespaces)
4036 .take(delimiter_trim.len())
4037 .collect::<String>();
4038 if delimiter_line.starts_with(delimiter_trim) {
4039 num_of_whitespaces + delimiter_trim.len()
4040 <= start_point.column as usize
4041 } else {
4042 false
4043 }
4044 };
4045
4046 let cursor_is_before_end_tag_if_exists = {
4047 let mut char_position = 0u32;
4048 let mut end_tag_offset = None;
4049
4050 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4051 if let Some(byte_pos) = chunk.find(&**end_tag) {
4052 let chars_before_match =
4053 chunk[..byte_pos].chars().count() as u32;
4054 end_tag_offset =
4055 Some(char_position + chars_before_match);
4056 break 'outer;
4057 }
4058 char_position += chunk.chars().count() as u32;
4059 }
4060
4061 if let Some(end_tag_offset) = end_tag_offset {
4062 let cursor_is_before_end_tag =
4063 start_point.column <= end_tag_offset;
4064 if cursor_is_after_start_tag {
4065 if cursor_is_before_end_tag {
4066 insert_extra_newline = true;
4067 }
4068 let cursor_is_at_start_of_end_tag =
4069 start_point.column == end_tag_offset;
4070 if cursor_is_at_start_of_end_tag {
4071 indent_on_extra_newline.len = (*len).into();
4072 }
4073 }
4074 cursor_is_before_end_tag
4075 } else {
4076 true
4077 }
4078 };
4079
4080 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4081 && cursor_is_before_end_tag_if_exists
4082 {
4083 if cursor_is_after_start_tag {
4084 indent_on_newline.len = (*len).into();
4085 }
4086 Some(delimiter.clone())
4087 } else {
4088 None
4089 }
4090 });
4091
4092 (
4093 comment_delimiter,
4094 doc_delimiter,
4095 insert_extra_newline,
4096 indent_on_newline,
4097 indent_on_extra_newline,
4098 )
4099 } else {
4100 (
4101 None,
4102 None,
4103 false,
4104 IndentSize::default(),
4105 IndentSize::default(),
4106 )
4107 };
4108
4109 let prevent_auto_indent = doc_delimiter.is_some();
4110 let delimiter = comment_delimiter.or(doc_delimiter);
4111
4112 let capacity_for_delimiter =
4113 delimiter.as_deref().map(str::len).unwrap_or_default();
4114 let mut new_text = String::with_capacity(
4115 1 + capacity_for_delimiter
4116 + existing_indent.len as usize
4117 + indent_on_newline.len as usize
4118 + indent_on_extra_newline.len as usize,
4119 );
4120 new_text.push('\n');
4121 new_text.extend(existing_indent.chars());
4122 new_text.extend(indent_on_newline.chars());
4123
4124 if let Some(delimiter) = &delimiter {
4125 new_text.push_str(delimiter);
4126 }
4127
4128 if insert_extra_newline {
4129 new_text.push('\n');
4130 new_text.extend(existing_indent.chars());
4131 new_text.extend(indent_on_extra_newline.chars());
4132 }
4133
4134 let anchor = buffer.anchor_after(end);
4135 let new_selection = selection.map(|_| anchor);
4136 (
4137 ((start..end, new_text), prevent_auto_indent),
4138 (insert_extra_newline, new_selection),
4139 )
4140 })
4141 .unzip()
4142 };
4143
4144 let mut auto_indent_edits = Vec::new();
4145 let mut edits = Vec::new();
4146 for (edit, prevent_auto_indent) in edits_with_flags {
4147 if prevent_auto_indent {
4148 edits.push(edit);
4149 } else {
4150 auto_indent_edits.push(edit);
4151 }
4152 }
4153 if !edits.is_empty() {
4154 this.edit(edits, cx);
4155 }
4156 if !auto_indent_edits.is_empty() {
4157 this.edit_with_autoindent(auto_indent_edits, cx);
4158 }
4159
4160 let buffer = this.buffer.read(cx).snapshot(cx);
4161 let new_selections = selection_info
4162 .into_iter()
4163 .map(|(extra_newline_inserted, new_selection)| {
4164 let mut cursor = new_selection.end.to_point(&buffer);
4165 if extra_newline_inserted {
4166 cursor.row -= 1;
4167 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4168 }
4169 new_selection.map(|_| cursor)
4170 })
4171 .collect();
4172
4173 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4174 s.select(new_selections)
4175 });
4176 this.refresh_inline_completion(true, false, window, cx);
4177 });
4178 }
4179
4180 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4181 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4182
4183 let buffer = self.buffer.read(cx);
4184 let snapshot = buffer.snapshot(cx);
4185
4186 let mut edits = Vec::new();
4187 let mut rows = Vec::new();
4188
4189 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4190 let cursor = selection.head();
4191 let row = cursor.row;
4192
4193 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4194
4195 let newline = "\n".to_string();
4196 edits.push((start_of_line..start_of_line, newline));
4197
4198 rows.push(row + rows_inserted as u32);
4199 }
4200
4201 self.transact(window, cx, |editor, window, cx| {
4202 editor.edit(edits, cx);
4203
4204 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4205 let mut index = 0;
4206 s.move_cursors_with(|map, _, _| {
4207 let row = rows[index];
4208 index += 1;
4209
4210 let point = Point::new(row, 0);
4211 let boundary = map.next_line_boundary(point).1;
4212 let clipped = map.clip_point(boundary, Bias::Left);
4213
4214 (clipped, SelectionGoal::None)
4215 });
4216 });
4217
4218 let mut indent_edits = Vec::new();
4219 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4220 for row in rows {
4221 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4222 for (row, indent) in indents {
4223 if indent.len == 0 {
4224 continue;
4225 }
4226
4227 let text = match indent.kind {
4228 IndentKind::Space => " ".repeat(indent.len as usize),
4229 IndentKind::Tab => "\t".repeat(indent.len as usize),
4230 };
4231 let point = Point::new(row.0, 0);
4232 indent_edits.push((point..point, text));
4233 }
4234 }
4235 editor.edit(indent_edits, cx);
4236 });
4237 }
4238
4239 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4240 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4241
4242 let buffer = self.buffer.read(cx);
4243 let snapshot = buffer.snapshot(cx);
4244
4245 let mut edits = Vec::new();
4246 let mut rows = Vec::new();
4247 let mut rows_inserted = 0;
4248
4249 for selection in self.selections.all_adjusted(cx) {
4250 let cursor = selection.head();
4251 let row = cursor.row;
4252
4253 let point = Point::new(row + 1, 0);
4254 let start_of_line = snapshot.clip_point(point, Bias::Left);
4255
4256 let newline = "\n".to_string();
4257 edits.push((start_of_line..start_of_line, newline));
4258
4259 rows_inserted += 1;
4260 rows.push(row + rows_inserted);
4261 }
4262
4263 self.transact(window, cx, |editor, window, cx| {
4264 editor.edit(edits, cx);
4265
4266 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4267 let mut index = 0;
4268 s.move_cursors_with(|map, _, _| {
4269 let row = rows[index];
4270 index += 1;
4271
4272 let point = Point::new(row, 0);
4273 let boundary = map.next_line_boundary(point).1;
4274 let clipped = map.clip_point(boundary, Bias::Left);
4275
4276 (clipped, SelectionGoal::None)
4277 });
4278 });
4279
4280 let mut indent_edits = Vec::new();
4281 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4282 for row in rows {
4283 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4284 for (row, indent) in indents {
4285 if indent.len == 0 {
4286 continue;
4287 }
4288
4289 let text = match indent.kind {
4290 IndentKind::Space => " ".repeat(indent.len as usize),
4291 IndentKind::Tab => "\t".repeat(indent.len as usize),
4292 };
4293 let point = Point::new(row.0, 0);
4294 indent_edits.push((point..point, text));
4295 }
4296 }
4297 editor.edit(indent_edits, cx);
4298 });
4299 }
4300
4301 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4302 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4303 original_indent_columns: Vec::new(),
4304 });
4305 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4306 }
4307
4308 fn insert_with_autoindent_mode(
4309 &mut self,
4310 text: &str,
4311 autoindent_mode: Option<AutoindentMode>,
4312 window: &mut Window,
4313 cx: &mut Context<Self>,
4314 ) {
4315 if self.read_only(cx) {
4316 return;
4317 }
4318
4319 let text: Arc<str> = text.into();
4320 self.transact(window, cx, |this, window, cx| {
4321 let old_selections = this.selections.all_adjusted(cx);
4322 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4323 let anchors = {
4324 let snapshot = buffer.read(cx);
4325 old_selections
4326 .iter()
4327 .map(|s| {
4328 let anchor = snapshot.anchor_after(s.head());
4329 s.map(|_| anchor)
4330 })
4331 .collect::<Vec<_>>()
4332 };
4333 buffer.edit(
4334 old_selections
4335 .iter()
4336 .map(|s| (s.start..s.end, text.clone())),
4337 autoindent_mode,
4338 cx,
4339 );
4340 anchors
4341 });
4342
4343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4344 s.select_anchors(selection_anchors);
4345 });
4346
4347 cx.notify();
4348 });
4349 }
4350
4351 fn trigger_completion_on_input(
4352 &mut self,
4353 text: &str,
4354 trigger_in_words: bool,
4355 window: &mut Window,
4356 cx: &mut Context<Self>,
4357 ) {
4358 let ignore_completion_provider = self
4359 .context_menu
4360 .borrow()
4361 .as_ref()
4362 .map(|menu| match menu {
4363 CodeContextMenu::Completions(completions_menu) => {
4364 completions_menu.ignore_completion_provider
4365 }
4366 CodeContextMenu::CodeActions(_) => false,
4367 })
4368 .unwrap_or(false);
4369
4370 if ignore_completion_provider {
4371 self.show_word_completions(&ShowWordCompletions, window, cx);
4372 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4373 self.show_completions(
4374 &ShowCompletions {
4375 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4376 },
4377 window,
4378 cx,
4379 );
4380 } else {
4381 self.hide_context_menu(window, cx);
4382 }
4383 }
4384
4385 fn is_completion_trigger(
4386 &self,
4387 text: &str,
4388 trigger_in_words: bool,
4389 cx: &mut Context<Self>,
4390 ) -> bool {
4391 let position = self.selections.newest_anchor().head();
4392 let multibuffer = self.buffer.read(cx);
4393 let Some(buffer) = position
4394 .buffer_id
4395 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4396 else {
4397 return false;
4398 };
4399
4400 if let Some(completion_provider) = &self.completion_provider {
4401 completion_provider.is_completion_trigger(
4402 &buffer,
4403 position.text_anchor,
4404 text,
4405 trigger_in_words,
4406 cx,
4407 )
4408 } else {
4409 false
4410 }
4411 }
4412
4413 /// If any empty selections is touching the start of its innermost containing autoclose
4414 /// region, expand it to select the brackets.
4415 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4416 let selections = self.selections.all::<usize>(cx);
4417 let buffer = self.buffer.read(cx).read(cx);
4418 let new_selections = self
4419 .selections_with_autoclose_regions(selections, &buffer)
4420 .map(|(mut selection, region)| {
4421 if !selection.is_empty() {
4422 return selection;
4423 }
4424
4425 if let Some(region) = region {
4426 let mut range = region.range.to_offset(&buffer);
4427 if selection.start == range.start && range.start >= region.pair.start.len() {
4428 range.start -= region.pair.start.len();
4429 if buffer.contains_str_at(range.start, ®ion.pair.start)
4430 && buffer.contains_str_at(range.end, ®ion.pair.end)
4431 {
4432 range.end += region.pair.end.len();
4433 selection.start = range.start;
4434 selection.end = range.end;
4435
4436 return selection;
4437 }
4438 }
4439 }
4440
4441 let always_treat_brackets_as_autoclosed = buffer
4442 .language_settings_at(selection.start, cx)
4443 .always_treat_brackets_as_autoclosed;
4444
4445 if !always_treat_brackets_as_autoclosed {
4446 return selection;
4447 }
4448
4449 if let Some(scope) = buffer.language_scope_at(selection.start) {
4450 for (pair, enabled) in scope.brackets() {
4451 if !enabled || !pair.close {
4452 continue;
4453 }
4454
4455 if buffer.contains_str_at(selection.start, &pair.end) {
4456 let pair_start_len = pair.start.len();
4457 if buffer.contains_str_at(
4458 selection.start.saturating_sub(pair_start_len),
4459 &pair.start,
4460 ) {
4461 selection.start -= pair_start_len;
4462 selection.end += pair.end.len();
4463
4464 return selection;
4465 }
4466 }
4467 }
4468 }
4469
4470 selection
4471 })
4472 .collect();
4473
4474 drop(buffer);
4475 self.change_selections(None, window, cx, |selections| {
4476 selections.select(new_selections)
4477 });
4478 }
4479
4480 /// Iterate the given selections, and for each one, find the smallest surrounding
4481 /// autoclose region. This uses the ordering of the selections and the autoclose
4482 /// regions to avoid repeated comparisons.
4483 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4484 &'a self,
4485 selections: impl IntoIterator<Item = Selection<D>>,
4486 buffer: &'a MultiBufferSnapshot,
4487 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4488 let mut i = 0;
4489 let mut regions = self.autoclose_regions.as_slice();
4490 selections.into_iter().map(move |selection| {
4491 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4492
4493 let mut enclosing = None;
4494 while let Some(pair_state) = regions.get(i) {
4495 if pair_state.range.end.to_offset(buffer) < range.start {
4496 regions = ®ions[i + 1..];
4497 i = 0;
4498 } else if pair_state.range.start.to_offset(buffer) > range.end {
4499 break;
4500 } else {
4501 if pair_state.selection_id == selection.id {
4502 enclosing = Some(pair_state);
4503 }
4504 i += 1;
4505 }
4506 }
4507
4508 (selection, enclosing)
4509 })
4510 }
4511
4512 /// Remove any autoclose regions that no longer contain their selection.
4513 fn invalidate_autoclose_regions(
4514 &mut self,
4515 mut selections: &[Selection<Anchor>],
4516 buffer: &MultiBufferSnapshot,
4517 ) {
4518 self.autoclose_regions.retain(|state| {
4519 let mut i = 0;
4520 while let Some(selection) = selections.get(i) {
4521 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4522 selections = &selections[1..];
4523 continue;
4524 }
4525 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4526 break;
4527 }
4528 if selection.id == state.selection_id {
4529 return true;
4530 } else {
4531 i += 1;
4532 }
4533 }
4534 false
4535 });
4536 }
4537
4538 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4539 let offset = position.to_offset(buffer);
4540 let (word_range, kind) = buffer.surrounding_word(offset, true);
4541 if offset > word_range.start && kind == Some(CharKind::Word) {
4542 Some(
4543 buffer
4544 .text_for_range(word_range.start..offset)
4545 .collect::<String>(),
4546 )
4547 } else {
4548 None
4549 }
4550 }
4551
4552 pub fn toggle_inline_values(
4553 &mut self,
4554 _: &ToggleInlineValues,
4555 _: &mut Window,
4556 cx: &mut Context<Self>,
4557 ) {
4558 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4559
4560 self.refresh_inline_values(cx);
4561 }
4562
4563 pub fn toggle_inlay_hints(
4564 &mut self,
4565 _: &ToggleInlayHints,
4566 _: &mut Window,
4567 cx: &mut Context<Self>,
4568 ) {
4569 self.refresh_inlay_hints(
4570 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4571 cx,
4572 );
4573 }
4574
4575 pub fn inlay_hints_enabled(&self) -> bool {
4576 self.inlay_hint_cache.enabled
4577 }
4578
4579 pub fn inline_values_enabled(&self) -> bool {
4580 self.inline_value_cache.enabled
4581 }
4582
4583 #[cfg(any(test, feature = "test-support"))]
4584 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4585 self.display_map
4586 .read(cx)
4587 .current_inlays()
4588 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4589 .cloned()
4590 .collect()
4591 }
4592
4593 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4594 if self.semantics_provider.is_none() || !self.mode.is_full() {
4595 return;
4596 }
4597
4598 let reason_description = reason.description();
4599 let ignore_debounce = matches!(
4600 reason,
4601 InlayHintRefreshReason::SettingsChange(_)
4602 | InlayHintRefreshReason::Toggle(_)
4603 | InlayHintRefreshReason::ExcerptsRemoved(_)
4604 | InlayHintRefreshReason::ModifiersChanged(_)
4605 );
4606 let (invalidate_cache, required_languages) = match reason {
4607 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4608 match self.inlay_hint_cache.modifiers_override(enabled) {
4609 Some(enabled) => {
4610 if enabled {
4611 (InvalidationStrategy::RefreshRequested, None)
4612 } else {
4613 self.splice_inlays(
4614 &self
4615 .visible_inlay_hints(cx)
4616 .iter()
4617 .map(|inlay| inlay.id)
4618 .collect::<Vec<InlayId>>(),
4619 Vec::new(),
4620 cx,
4621 );
4622 return;
4623 }
4624 }
4625 None => return,
4626 }
4627 }
4628 InlayHintRefreshReason::Toggle(enabled) => {
4629 if self.inlay_hint_cache.toggle(enabled) {
4630 if enabled {
4631 (InvalidationStrategy::RefreshRequested, None)
4632 } else {
4633 self.splice_inlays(
4634 &self
4635 .visible_inlay_hints(cx)
4636 .iter()
4637 .map(|inlay| inlay.id)
4638 .collect::<Vec<InlayId>>(),
4639 Vec::new(),
4640 cx,
4641 );
4642 return;
4643 }
4644 } else {
4645 return;
4646 }
4647 }
4648 InlayHintRefreshReason::SettingsChange(new_settings) => {
4649 match self.inlay_hint_cache.update_settings(
4650 &self.buffer,
4651 new_settings,
4652 self.visible_inlay_hints(cx),
4653 cx,
4654 ) {
4655 ControlFlow::Break(Some(InlaySplice {
4656 to_remove,
4657 to_insert,
4658 })) => {
4659 self.splice_inlays(&to_remove, to_insert, cx);
4660 return;
4661 }
4662 ControlFlow::Break(None) => return,
4663 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4664 }
4665 }
4666 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4667 if let Some(InlaySplice {
4668 to_remove,
4669 to_insert,
4670 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4671 {
4672 self.splice_inlays(&to_remove, to_insert, cx);
4673 }
4674 self.display_map.update(cx, |display_map, _| {
4675 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4676 });
4677 return;
4678 }
4679 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4680 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4681 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4682 }
4683 InlayHintRefreshReason::RefreshRequested => {
4684 (InvalidationStrategy::RefreshRequested, None)
4685 }
4686 };
4687
4688 if let Some(InlaySplice {
4689 to_remove,
4690 to_insert,
4691 }) = self.inlay_hint_cache.spawn_hint_refresh(
4692 reason_description,
4693 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4694 invalidate_cache,
4695 ignore_debounce,
4696 cx,
4697 ) {
4698 self.splice_inlays(&to_remove, to_insert, cx);
4699 }
4700 }
4701
4702 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4703 self.display_map
4704 .read(cx)
4705 .current_inlays()
4706 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4707 .cloned()
4708 .collect()
4709 }
4710
4711 pub fn excerpts_for_inlay_hints_query(
4712 &self,
4713 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4714 cx: &mut Context<Editor>,
4715 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4716 let Some(project) = self.project.as_ref() else {
4717 return HashMap::default();
4718 };
4719 let project = project.read(cx);
4720 let multi_buffer = self.buffer().read(cx);
4721 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4722 let multi_buffer_visible_start = self
4723 .scroll_manager
4724 .anchor()
4725 .anchor
4726 .to_point(&multi_buffer_snapshot);
4727 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4728 multi_buffer_visible_start
4729 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4730 Bias::Left,
4731 );
4732 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4733 multi_buffer_snapshot
4734 .range_to_buffer_ranges(multi_buffer_visible_range)
4735 .into_iter()
4736 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4737 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4738 let buffer_file = project::File::from_dyn(buffer.file())?;
4739 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4740 let worktree_entry = buffer_worktree
4741 .read(cx)
4742 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4743 if worktree_entry.is_ignored {
4744 return None;
4745 }
4746
4747 let language = buffer.language()?;
4748 if let Some(restrict_to_languages) = restrict_to_languages {
4749 if !restrict_to_languages.contains(language) {
4750 return None;
4751 }
4752 }
4753 Some((
4754 excerpt_id,
4755 (
4756 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4757 buffer.version().clone(),
4758 excerpt_visible_range,
4759 ),
4760 ))
4761 })
4762 .collect()
4763 }
4764
4765 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4766 TextLayoutDetails {
4767 text_system: window.text_system().clone(),
4768 editor_style: self.style.clone().unwrap(),
4769 rem_size: window.rem_size(),
4770 scroll_anchor: self.scroll_manager.anchor(),
4771 visible_rows: self.visible_line_count(),
4772 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4773 }
4774 }
4775
4776 pub fn splice_inlays(
4777 &self,
4778 to_remove: &[InlayId],
4779 to_insert: Vec<Inlay>,
4780 cx: &mut Context<Self>,
4781 ) {
4782 self.display_map.update(cx, |display_map, cx| {
4783 display_map.splice_inlays(to_remove, to_insert, cx)
4784 });
4785 cx.notify();
4786 }
4787
4788 fn trigger_on_type_formatting(
4789 &self,
4790 input: String,
4791 window: &mut Window,
4792 cx: &mut Context<Self>,
4793 ) -> Option<Task<Result<()>>> {
4794 if input.len() != 1 {
4795 return None;
4796 }
4797
4798 let project = self.project.as_ref()?;
4799 let position = self.selections.newest_anchor().head();
4800 let (buffer, buffer_position) = self
4801 .buffer
4802 .read(cx)
4803 .text_anchor_for_position(position, cx)?;
4804
4805 let settings = language_settings::language_settings(
4806 buffer
4807 .read(cx)
4808 .language_at(buffer_position)
4809 .map(|l| l.name()),
4810 buffer.read(cx).file(),
4811 cx,
4812 );
4813 if !settings.use_on_type_format {
4814 return None;
4815 }
4816
4817 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4818 // hence we do LSP request & edit on host side only — add formats to host's history.
4819 let push_to_lsp_host_history = true;
4820 // If this is not the host, append its history with new edits.
4821 let push_to_client_history = project.read(cx).is_via_collab();
4822
4823 let on_type_formatting = project.update(cx, |project, cx| {
4824 project.on_type_format(
4825 buffer.clone(),
4826 buffer_position,
4827 input,
4828 push_to_lsp_host_history,
4829 cx,
4830 )
4831 });
4832 Some(cx.spawn_in(window, async move |editor, cx| {
4833 if let Some(transaction) = on_type_formatting.await? {
4834 if push_to_client_history {
4835 buffer
4836 .update(cx, |buffer, _| {
4837 buffer.push_transaction(transaction, Instant::now());
4838 buffer.finalize_last_transaction();
4839 })
4840 .ok();
4841 }
4842 editor.update(cx, |editor, cx| {
4843 editor.refresh_document_highlights(cx);
4844 })?;
4845 }
4846 Ok(())
4847 }))
4848 }
4849
4850 pub fn show_word_completions(
4851 &mut self,
4852 _: &ShowWordCompletions,
4853 window: &mut Window,
4854 cx: &mut Context<Self>,
4855 ) {
4856 self.open_completions_menu(true, None, window, cx);
4857 }
4858
4859 pub fn show_completions(
4860 &mut self,
4861 options: &ShowCompletions,
4862 window: &mut Window,
4863 cx: &mut Context<Self>,
4864 ) {
4865 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4866 }
4867
4868 fn open_completions_menu(
4869 &mut self,
4870 ignore_completion_provider: bool,
4871 trigger: Option<&str>,
4872 window: &mut Window,
4873 cx: &mut Context<Self>,
4874 ) {
4875 if self.pending_rename.is_some() {
4876 return;
4877 }
4878 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4879 return;
4880 }
4881
4882 let position = self.selections.newest_anchor().head();
4883 if position.diff_base_anchor.is_some() {
4884 return;
4885 }
4886 let (buffer, buffer_position) =
4887 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4888 output
4889 } else {
4890 return;
4891 };
4892 let buffer_snapshot = buffer.read(cx).snapshot();
4893 let show_completion_documentation = buffer_snapshot
4894 .settings_at(buffer_position, cx)
4895 .show_completion_documentation;
4896
4897 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4898
4899 let trigger_kind = match trigger {
4900 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4901 CompletionTriggerKind::TRIGGER_CHARACTER
4902 }
4903 _ => CompletionTriggerKind::INVOKED,
4904 };
4905 let completion_context = CompletionContext {
4906 trigger_character: trigger.and_then(|trigger| {
4907 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4908 Some(String::from(trigger))
4909 } else {
4910 None
4911 }
4912 }),
4913 trigger_kind,
4914 };
4915
4916 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4917 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4918 let word_to_exclude = buffer_snapshot
4919 .text_for_range(old_range.clone())
4920 .collect::<String>();
4921 (
4922 buffer_snapshot.anchor_before(old_range.start)
4923 ..buffer_snapshot.anchor_after(old_range.end),
4924 Some(word_to_exclude),
4925 )
4926 } else {
4927 (buffer_position..buffer_position, None)
4928 };
4929
4930 let completion_settings = language_settings(
4931 buffer_snapshot
4932 .language_at(buffer_position)
4933 .map(|language| language.name()),
4934 buffer_snapshot.file(),
4935 cx,
4936 )
4937 .completions;
4938
4939 // The document can be large, so stay in reasonable bounds when searching for words,
4940 // otherwise completion pop-up might be slow to appear.
4941 const WORD_LOOKUP_ROWS: u32 = 5_000;
4942 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4943 let min_word_search = buffer_snapshot.clip_point(
4944 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4945 Bias::Left,
4946 );
4947 let max_word_search = buffer_snapshot.clip_point(
4948 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4949 Bias::Right,
4950 );
4951 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4952 ..buffer_snapshot.point_to_offset(max_word_search);
4953
4954 let provider = self
4955 .completion_provider
4956 .as_ref()
4957 .filter(|_| !ignore_completion_provider);
4958 let skip_digits = query
4959 .as_ref()
4960 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4961
4962 let (mut words, provided_completions) = match provider {
4963 Some(provider) => {
4964 let completions = provider.completions(
4965 position.excerpt_id,
4966 &buffer,
4967 buffer_position,
4968 completion_context,
4969 window,
4970 cx,
4971 );
4972
4973 let words = match completion_settings.words {
4974 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4975 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4976 .background_spawn(async move {
4977 buffer_snapshot.words_in_range(WordsQuery {
4978 fuzzy_contents: None,
4979 range: word_search_range,
4980 skip_digits,
4981 })
4982 }),
4983 };
4984
4985 (words, completions)
4986 }
4987 None => (
4988 cx.background_spawn(async move {
4989 buffer_snapshot.words_in_range(WordsQuery {
4990 fuzzy_contents: None,
4991 range: word_search_range,
4992 skip_digits,
4993 })
4994 }),
4995 Task::ready(Ok(None)),
4996 ),
4997 };
4998
4999 let sort_completions = provider
5000 .as_ref()
5001 .map_or(false, |provider| provider.sort_completions());
5002
5003 let filter_completions = provider
5004 .as_ref()
5005 .map_or(true, |provider| provider.filter_completions());
5006
5007 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5008
5009 let id = post_inc(&mut self.next_completion_id);
5010 let task = cx.spawn_in(window, async move |editor, cx| {
5011 async move {
5012 editor.update(cx, |this, _| {
5013 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5014 })?;
5015
5016 let mut completions = Vec::new();
5017 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5018 completions.extend(provided_completions);
5019 if completion_settings.words == WordsCompletionMode::Fallback {
5020 words = Task::ready(BTreeMap::default());
5021 }
5022 }
5023
5024 let mut words = words.await;
5025 if let Some(word_to_exclude) = &word_to_exclude {
5026 words.remove(word_to_exclude);
5027 }
5028 for lsp_completion in &completions {
5029 words.remove(&lsp_completion.new_text);
5030 }
5031 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5032 replace_range: old_range.clone(),
5033 new_text: word.clone(),
5034 label: CodeLabel::plain(word, None),
5035 icon_path: None,
5036 documentation: None,
5037 source: CompletionSource::BufferWord {
5038 word_range,
5039 resolved: false,
5040 },
5041 insert_text_mode: Some(InsertTextMode::AS_IS),
5042 confirm: None,
5043 }));
5044
5045 let menu = if completions.is_empty() {
5046 None
5047 } else {
5048 let mut menu = CompletionsMenu::new(
5049 id,
5050 sort_completions,
5051 show_completion_documentation,
5052 ignore_completion_provider,
5053 position,
5054 buffer.clone(),
5055 completions.into(),
5056 snippet_sort_order,
5057 );
5058
5059 menu.filter(
5060 if filter_completions {
5061 query.as_deref()
5062 } else {
5063 None
5064 },
5065 cx.background_executor().clone(),
5066 )
5067 .await;
5068
5069 menu.visible().then_some(menu)
5070 };
5071
5072 editor.update_in(cx, |editor, window, cx| {
5073 match editor.context_menu.borrow().as_ref() {
5074 None => {}
5075 Some(CodeContextMenu::Completions(prev_menu)) => {
5076 if prev_menu.id > id {
5077 return;
5078 }
5079 }
5080 _ => return,
5081 }
5082
5083 if editor.focus_handle.is_focused(window) && menu.is_some() {
5084 let mut menu = menu.unwrap();
5085 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5086 crate::hover_popover::hide_hover(editor, cx);
5087 *editor.context_menu.borrow_mut() =
5088 Some(CodeContextMenu::Completions(menu));
5089
5090 if editor.show_edit_predictions_in_menu() {
5091 editor.update_visible_inline_completion(window, cx);
5092 } else {
5093 editor.discard_inline_completion(false, cx);
5094 }
5095
5096 cx.notify();
5097 } else if editor.completion_tasks.len() <= 1 {
5098 // If there are no more completion tasks and the last menu was
5099 // empty, we should hide it.
5100 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5101 // If it was already hidden and we don't show inline
5102 // completions in the menu, we should also show the
5103 // inline-completion when available.
5104 if was_hidden && editor.show_edit_predictions_in_menu() {
5105 editor.update_visible_inline_completion(window, cx);
5106 }
5107 }
5108 })?;
5109
5110 anyhow::Ok(())
5111 }
5112 .log_err()
5113 .await
5114 });
5115
5116 self.completion_tasks.push((id, task));
5117 }
5118
5119 #[cfg(feature = "test-support")]
5120 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5121 let menu = self.context_menu.borrow();
5122 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5123 let completions = menu.completions.borrow();
5124 Some(completions.to_vec())
5125 } else {
5126 None
5127 }
5128 }
5129
5130 pub fn confirm_completion(
5131 &mut self,
5132 action: &ConfirmCompletion,
5133 window: &mut Window,
5134 cx: &mut Context<Self>,
5135 ) -> Option<Task<Result<()>>> {
5136 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5137 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5138 }
5139
5140 pub fn confirm_completion_insert(
5141 &mut self,
5142 _: &ConfirmCompletionInsert,
5143 window: &mut Window,
5144 cx: &mut Context<Self>,
5145 ) -> Option<Task<Result<()>>> {
5146 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5147 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5148 }
5149
5150 pub fn confirm_completion_replace(
5151 &mut self,
5152 _: &ConfirmCompletionReplace,
5153 window: &mut Window,
5154 cx: &mut Context<Self>,
5155 ) -> Option<Task<Result<()>>> {
5156 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5157 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5158 }
5159
5160 pub fn compose_completion(
5161 &mut self,
5162 action: &ComposeCompletion,
5163 window: &mut Window,
5164 cx: &mut Context<Self>,
5165 ) -> Option<Task<Result<()>>> {
5166 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5167 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5168 }
5169
5170 fn do_completion(
5171 &mut self,
5172 item_ix: Option<usize>,
5173 intent: CompletionIntent,
5174 window: &mut Window,
5175 cx: &mut Context<Editor>,
5176 ) -> Option<Task<Result<()>>> {
5177 use language::ToOffset as _;
5178
5179 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5180 else {
5181 return None;
5182 };
5183
5184 let candidate_id = {
5185 let entries = completions_menu.entries.borrow();
5186 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5187 if self.show_edit_predictions_in_menu() {
5188 self.discard_inline_completion(true, cx);
5189 }
5190 mat.candidate_id
5191 };
5192
5193 let buffer_handle = completions_menu.buffer;
5194 let completion = completions_menu
5195 .completions
5196 .borrow()
5197 .get(candidate_id)?
5198 .clone();
5199 cx.stop_propagation();
5200
5201 let snapshot = self.buffer.read(cx).snapshot(cx);
5202 let newest_anchor = self.selections.newest_anchor();
5203
5204 let snippet;
5205 let new_text;
5206 if completion.is_snippet() {
5207 let mut snippet_source = completion.new_text.clone();
5208 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5209 if scope.prefers_label_for_snippet_in_completion() {
5210 if let Some(label) = completion.label() {
5211 if matches!(
5212 completion.kind(),
5213 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5214 ) {
5215 snippet_source = label;
5216 }
5217 }
5218 }
5219 }
5220 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5221 new_text = snippet.as_ref().unwrap().text.clone();
5222 } else {
5223 snippet = None;
5224 new_text = completion.new_text.clone();
5225 };
5226
5227 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5228 let buffer = buffer_handle.read(cx);
5229 let replace_range_multibuffer = {
5230 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5231 let multibuffer_anchor = snapshot
5232 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5233 .unwrap()
5234 ..snapshot
5235 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5236 .unwrap();
5237 multibuffer_anchor.start.to_offset(&snapshot)
5238 ..multibuffer_anchor.end.to_offset(&snapshot)
5239 };
5240 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5241 return None;
5242 }
5243
5244 let old_text = buffer
5245 .text_for_range(replace_range.clone())
5246 .collect::<String>();
5247 let lookbehind = newest_anchor
5248 .start
5249 .text_anchor
5250 .to_offset(buffer)
5251 .saturating_sub(replace_range.start);
5252 let lookahead = replace_range
5253 .end
5254 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5255 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5256 let suffix = &old_text[lookbehind.min(old_text.len())..];
5257
5258 let selections = self.selections.all::<usize>(cx);
5259 let mut ranges = Vec::new();
5260 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5261
5262 for selection in &selections {
5263 let range = if selection.id == newest_anchor.id {
5264 replace_range_multibuffer.clone()
5265 } else {
5266 let mut range = selection.range();
5267
5268 // if prefix is present, don't duplicate it
5269 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5270 range.start = range.start.saturating_sub(lookbehind);
5271
5272 // if suffix is also present, mimic the newest cursor and replace it
5273 if selection.id != newest_anchor.id
5274 && snapshot.contains_str_at(range.end, suffix)
5275 {
5276 range.end += lookahead;
5277 }
5278 }
5279 range
5280 };
5281
5282 ranges.push(range.clone());
5283
5284 if !self.linked_edit_ranges.is_empty() {
5285 let start_anchor = snapshot.anchor_before(range.start);
5286 let end_anchor = snapshot.anchor_after(range.end);
5287 if let Some(ranges) = self
5288 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5289 {
5290 for (buffer, edits) in ranges {
5291 linked_edits
5292 .entry(buffer.clone())
5293 .or_default()
5294 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5295 }
5296 }
5297 }
5298 }
5299
5300 cx.emit(EditorEvent::InputHandled {
5301 utf16_range_to_replace: None,
5302 text: new_text.clone().into(),
5303 });
5304
5305 self.transact(window, cx, |this, window, cx| {
5306 if let Some(mut snippet) = snippet {
5307 snippet.text = new_text.to_string();
5308 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5309 } else {
5310 this.buffer.update(cx, |buffer, cx| {
5311 let auto_indent = match completion.insert_text_mode {
5312 Some(InsertTextMode::AS_IS) => None,
5313 _ => this.autoindent_mode.clone(),
5314 };
5315 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5316 buffer.edit(edits, auto_indent, cx);
5317 });
5318 }
5319 for (buffer, edits) in linked_edits {
5320 buffer.update(cx, |buffer, cx| {
5321 let snapshot = buffer.snapshot();
5322 let edits = edits
5323 .into_iter()
5324 .map(|(range, text)| {
5325 use text::ToPoint as TP;
5326 let end_point = TP::to_point(&range.end, &snapshot);
5327 let start_point = TP::to_point(&range.start, &snapshot);
5328 (start_point..end_point, text)
5329 })
5330 .sorted_by_key(|(range, _)| range.start);
5331 buffer.edit(edits, None, cx);
5332 })
5333 }
5334
5335 this.refresh_inline_completion(true, false, window, cx);
5336 });
5337
5338 let show_new_completions_on_confirm = completion
5339 .confirm
5340 .as_ref()
5341 .map_or(false, |confirm| confirm(intent, window, cx));
5342 if show_new_completions_on_confirm {
5343 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5344 }
5345
5346 let provider = self.completion_provider.as_ref()?;
5347 drop(completion);
5348 let apply_edits = provider.apply_additional_edits_for_completion(
5349 buffer_handle,
5350 completions_menu.completions.clone(),
5351 candidate_id,
5352 true,
5353 cx,
5354 );
5355
5356 let editor_settings = EditorSettings::get_global(cx);
5357 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5358 // After the code completion is finished, users often want to know what signatures are needed.
5359 // so we should automatically call signature_help
5360 self.show_signature_help(&ShowSignatureHelp, window, cx);
5361 }
5362
5363 Some(cx.foreground_executor().spawn(async move {
5364 apply_edits.await?;
5365 Ok(())
5366 }))
5367 }
5368
5369 pub fn toggle_code_actions(
5370 &mut self,
5371 action: &ToggleCodeActions,
5372 window: &mut Window,
5373 cx: &mut Context<Self>,
5374 ) {
5375 let quick_launch = action.quick_launch;
5376 let mut context_menu = self.context_menu.borrow_mut();
5377 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5378 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5379 // Toggle if we're selecting the same one
5380 *context_menu = None;
5381 cx.notify();
5382 return;
5383 } else {
5384 // Otherwise, clear it and start a new one
5385 *context_menu = None;
5386 cx.notify();
5387 }
5388 }
5389 drop(context_menu);
5390 let snapshot = self.snapshot(window, cx);
5391 let deployed_from_indicator = action.deployed_from_indicator;
5392 let mut task = self.code_actions_task.take();
5393 let action = action.clone();
5394 cx.spawn_in(window, async move |editor, cx| {
5395 while let Some(prev_task) = task {
5396 prev_task.await.log_err();
5397 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5398 }
5399
5400 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5401 if editor.focus_handle.is_focused(window) {
5402 let multibuffer_point = action
5403 .deployed_from_indicator
5404 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5405 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5406 let (buffer, buffer_row) = snapshot
5407 .buffer_snapshot
5408 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5409 .and_then(|(buffer_snapshot, range)| {
5410 editor
5411 .buffer
5412 .read(cx)
5413 .buffer(buffer_snapshot.remote_id())
5414 .map(|buffer| (buffer, range.start.row))
5415 })?;
5416 let (_, code_actions) = editor
5417 .available_code_actions
5418 .clone()
5419 .and_then(|(location, code_actions)| {
5420 let snapshot = location.buffer.read(cx).snapshot();
5421 let point_range = location.range.to_point(&snapshot);
5422 let point_range = point_range.start.row..=point_range.end.row;
5423 if point_range.contains(&buffer_row) {
5424 Some((location, code_actions))
5425 } else {
5426 None
5427 }
5428 })
5429 .unzip();
5430 let buffer_id = buffer.read(cx).remote_id();
5431 let tasks = editor
5432 .tasks
5433 .get(&(buffer_id, buffer_row))
5434 .map(|t| Arc::new(t.to_owned()));
5435 if tasks.is_none() && code_actions.is_none() {
5436 return None;
5437 }
5438
5439 editor.completion_tasks.clear();
5440 editor.discard_inline_completion(false, cx);
5441 let task_context =
5442 tasks
5443 .as_ref()
5444 .zip(editor.project.clone())
5445 .map(|(tasks, project)| {
5446 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5447 });
5448
5449 Some(cx.spawn_in(window, async move |editor, cx| {
5450 let task_context = match task_context {
5451 Some(task_context) => task_context.await,
5452 None => None,
5453 };
5454 let resolved_tasks =
5455 tasks
5456 .zip(task_context.clone())
5457 .map(|(tasks, task_context)| ResolvedTasks {
5458 templates: tasks.resolve(&task_context).collect(),
5459 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5460 multibuffer_point.row,
5461 tasks.column,
5462 )),
5463 });
5464 let debug_scenarios = editor.update(cx, |editor, cx| {
5465 if cx.has_flag::<DebuggerFeatureFlag>() {
5466 maybe!({
5467 let project = editor.project.as_ref()?;
5468 let dap_store = project.read(cx).dap_store();
5469 let mut scenarios = vec![];
5470 let resolved_tasks = resolved_tasks.as_ref()?;
5471 let buffer = buffer.read(cx);
5472 let language = buffer.language()?;
5473 let file = buffer.file();
5474 let debug_adapter =
5475 language_settings(language.name().into(), file, cx)
5476 .debuggers
5477 .first()
5478 .map(SharedString::from)
5479 .or_else(|| {
5480 language
5481 .config()
5482 .debuggers
5483 .first()
5484 .map(SharedString::from)
5485 })?;
5486
5487 dap_store.update(cx, |dap_store, cx| {
5488 for (_, task) in &resolved_tasks.templates {
5489 if let Some(scenario) = dap_store
5490 .debug_scenario_for_build_task(
5491 task.original_task().clone(),
5492 debug_adapter.clone().into(),
5493 task.display_label().to_owned().into(),
5494 cx,
5495 )
5496 {
5497 scenarios.push(scenario);
5498 }
5499 }
5500 });
5501 Some(scenarios)
5502 })
5503 .unwrap_or_default()
5504 } else {
5505 vec![]
5506 }
5507 })?;
5508 let spawn_straight_away = quick_launch
5509 && resolved_tasks
5510 .as_ref()
5511 .map_or(false, |tasks| tasks.templates.len() == 1)
5512 && code_actions
5513 .as_ref()
5514 .map_or(true, |actions| actions.is_empty())
5515 && debug_scenarios.is_empty();
5516 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5517 crate::hover_popover::hide_hover(editor, cx);
5518 *editor.context_menu.borrow_mut() =
5519 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5520 buffer,
5521 actions: CodeActionContents::new(
5522 resolved_tasks,
5523 code_actions,
5524 debug_scenarios,
5525 task_context.unwrap_or_default(),
5526 ),
5527 selected_item: Default::default(),
5528 scroll_handle: UniformListScrollHandle::default(),
5529 deployed_from_indicator,
5530 }));
5531 if spawn_straight_away {
5532 if let Some(task) = editor.confirm_code_action(
5533 &ConfirmCodeAction { item_ix: Some(0) },
5534 window,
5535 cx,
5536 ) {
5537 cx.notify();
5538 return task;
5539 }
5540 }
5541 cx.notify();
5542 Task::ready(Ok(()))
5543 }) {
5544 task.await
5545 } else {
5546 Ok(())
5547 }
5548 }))
5549 } else {
5550 Some(Task::ready(Ok(())))
5551 }
5552 })?;
5553 if let Some(task) = spawned_test_task {
5554 task.await?;
5555 }
5556
5557 anyhow::Ok(())
5558 })
5559 .detach_and_log_err(cx);
5560 }
5561
5562 pub fn confirm_code_action(
5563 &mut self,
5564 action: &ConfirmCodeAction,
5565 window: &mut Window,
5566 cx: &mut Context<Self>,
5567 ) -> Option<Task<Result<()>>> {
5568 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5569
5570 let actions_menu =
5571 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5572 menu
5573 } else {
5574 return None;
5575 };
5576
5577 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5578 let action = actions_menu.actions.get(action_ix)?;
5579 let title = action.label();
5580 let buffer = actions_menu.buffer;
5581 let workspace = self.workspace()?;
5582
5583 match action {
5584 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5585 workspace.update(cx, |workspace, cx| {
5586 workspace.schedule_resolved_task(
5587 task_source_kind,
5588 resolved_task,
5589 false,
5590 window,
5591 cx,
5592 );
5593
5594 Some(Task::ready(Ok(())))
5595 })
5596 }
5597 CodeActionsItem::CodeAction {
5598 excerpt_id,
5599 action,
5600 provider,
5601 } => {
5602 let apply_code_action =
5603 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5604 let workspace = workspace.downgrade();
5605 Some(cx.spawn_in(window, async move |editor, cx| {
5606 let project_transaction = apply_code_action.await?;
5607 Self::open_project_transaction(
5608 &editor,
5609 workspace,
5610 project_transaction,
5611 title,
5612 cx,
5613 )
5614 .await
5615 }))
5616 }
5617 CodeActionsItem::DebugScenario(scenario) => {
5618 let context = actions_menu.actions.context.clone();
5619
5620 workspace.update(cx, |workspace, cx| {
5621 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5622 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5623 });
5624 Some(Task::ready(Ok(())))
5625 }
5626 }
5627 }
5628
5629 pub async fn open_project_transaction(
5630 this: &WeakEntity<Editor>,
5631 workspace: WeakEntity<Workspace>,
5632 transaction: ProjectTransaction,
5633 title: String,
5634 cx: &mut AsyncWindowContext,
5635 ) -> Result<()> {
5636 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5637 cx.update(|_, cx| {
5638 entries.sort_unstable_by_key(|(buffer, _)| {
5639 buffer.read(cx).file().map(|f| f.path().clone())
5640 });
5641 })?;
5642
5643 // If the project transaction's edits are all contained within this editor, then
5644 // avoid opening a new editor to display them.
5645
5646 if let Some((buffer, transaction)) = entries.first() {
5647 if entries.len() == 1 {
5648 let excerpt = this.update(cx, |editor, cx| {
5649 editor
5650 .buffer()
5651 .read(cx)
5652 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5653 })?;
5654 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5655 if excerpted_buffer == *buffer {
5656 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5657 let excerpt_range = excerpt_range.to_offset(buffer);
5658 buffer
5659 .edited_ranges_for_transaction::<usize>(transaction)
5660 .all(|range| {
5661 excerpt_range.start <= range.start
5662 && excerpt_range.end >= range.end
5663 })
5664 })?;
5665
5666 if all_edits_within_excerpt {
5667 return Ok(());
5668 }
5669 }
5670 }
5671 }
5672 } else {
5673 return Ok(());
5674 }
5675
5676 let mut ranges_to_highlight = Vec::new();
5677 let excerpt_buffer = cx.new(|cx| {
5678 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5679 for (buffer_handle, transaction) in &entries {
5680 let edited_ranges = buffer_handle
5681 .read(cx)
5682 .edited_ranges_for_transaction::<Point>(transaction)
5683 .collect::<Vec<_>>();
5684 let (ranges, _) = multibuffer.set_excerpts_for_path(
5685 PathKey::for_buffer(buffer_handle, cx),
5686 buffer_handle.clone(),
5687 edited_ranges,
5688 DEFAULT_MULTIBUFFER_CONTEXT,
5689 cx,
5690 );
5691
5692 ranges_to_highlight.extend(ranges);
5693 }
5694 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5695 multibuffer
5696 })?;
5697
5698 workspace.update_in(cx, |workspace, window, cx| {
5699 let project = workspace.project().clone();
5700 let editor =
5701 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5702 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5703 editor.update(cx, |editor, cx| {
5704 editor.highlight_background::<Self>(
5705 &ranges_to_highlight,
5706 |theme| theme.editor_highlighted_line_background,
5707 cx,
5708 );
5709 });
5710 })?;
5711
5712 Ok(())
5713 }
5714
5715 pub fn clear_code_action_providers(&mut self) {
5716 self.code_action_providers.clear();
5717 self.available_code_actions.take();
5718 }
5719
5720 pub fn add_code_action_provider(
5721 &mut self,
5722 provider: Rc<dyn CodeActionProvider>,
5723 window: &mut Window,
5724 cx: &mut Context<Self>,
5725 ) {
5726 if self
5727 .code_action_providers
5728 .iter()
5729 .any(|existing_provider| existing_provider.id() == provider.id())
5730 {
5731 return;
5732 }
5733
5734 self.code_action_providers.push(provider);
5735 self.refresh_code_actions(window, cx);
5736 }
5737
5738 pub fn remove_code_action_provider(
5739 &mut self,
5740 id: Arc<str>,
5741 window: &mut Window,
5742 cx: &mut Context<Self>,
5743 ) {
5744 self.code_action_providers
5745 .retain(|provider| provider.id() != id);
5746 self.refresh_code_actions(window, cx);
5747 }
5748
5749 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5750 let newest_selection = self.selections.newest_anchor().clone();
5751 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5752 let buffer = self.buffer.read(cx);
5753 if newest_selection.head().diff_base_anchor.is_some() {
5754 return None;
5755 }
5756 let (start_buffer, start) =
5757 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5758 let (end_buffer, end) =
5759 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5760 if start_buffer != end_buffer {
5761 return None;
5762 }
5763
5764 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5765 cx.background_executor()
5766 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5767 .await;
5768
5769 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5770 let providers = this.code_action_providers.clone();
5771 let tasks = this
5772 .code_action_providers
5773 .iter()
5774 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5775 .collect::<Vec<_>>();
5776 (providers, tasks)
5777 })?;
5778
5779 let mut actions = Vec::new();
5780 for (provider, provider_actions) in
5781 providers.into_iter().zip(future::join_all(tasks).await)
5782 {
5783 if let Some(provider_actions) = provider_actions.log_err() {
5784 actions.extend(provider_actions.into_iter().map(|action| {
5785 AvailableCodeAction {
5786 excerpt_id: newest_selection.start.excerpt_id,
5787 action,
5788 provider: provider.clone(),
5789 }
5790 }));
5791 }
5792 }
5793
5794 this.update(cx, |this, cx| {
5795 this.available_code_actions = if actions.is_empty() {
5796 None
5797 } else {
5798 Some((
5799 Location {
5800 buffer: start_buffer,
5801 range: start..end,
5802 },
5803 actions.into(),
5804 ))
5805 };
5806 cx.notify();
5807 })
5808 }));
5809 None
5810 }
5811
5812 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5813 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5814 self.show_git_blame_inline = false;
5815
5816 self.show_git_blame_inline_delay_task =
5817 Some(cx.spawn_in(window, async move |this, cx| {
5818 cx.background_executor().timer(delay).await;
5819
5820 this.update(cx, |this, cx| {
5821 this.show_git_blame_inline = true;
5822 cx.notify();
5823 })
5824 .log_err();
5825 }));
5826 }
5827 }
5828
5829 fn show_blame_popover(
5830 &mut self,
5831 blame_entry: &BlameEntry,
5832 position: gpui::Point<Pixels>,
5833 cx: &mut Context<Self>,
5834 ) {
5835 if let Some(state) = &mut self.inline_blame_popover {
5836 state.hide_task.take();
5837 cx.notify();
5838 } else {
5839 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5840 let show_task = cx.spawn(async move |editor, cx| {
5841 cx.background_executor()
5842 .timer(std::time::Duration::from_millis(delay))
5843 .await;
5844 editor
5845 .update(cx, |editor, cx| {
5846 if let Some(state) = &mut editor.inline_blame_popover {
5847 state.show_task = None;
5848 cx.notify();
5849 }
5850 })
5851 .ok();
5852 });
5853 let Some(blame) = self.blame.as_ref() else {
5854 return;
5855 };
5856 let blame = blame.read(cx);
5857 let details = blame.details_for_entry(&blame_entry);
5858 let markdown = cx.new(|cx| {
5859 Markdown::new(
5860 details
5861 .as_ref()
5862 .map(|message| message.message.clone())
5863 .unwrap_or_default(),
5864 None,
5865 None,
5866 cx,
5867 )
5868 });
5869 self.inline_blame_popover = Some(InlineBlamePopover {
5870 position,
5871 show_task: Some(show_task),
5872 hide_task: None,
5873 popover_bounds: None,
5874 popover_state: InlineBlamePopoverState {
5875 scroll_handle: ScrollHandle::new(),
5876 commit_message: details,
5877 markdown,
5878 },
5879 });
5880 }
5881 }
5882
5883 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5884 if let Some(state) = &mut self.inline_blame_popover {
5885 if state.show_task.is_some() {
5886 self.inline_blame_popover.take();
5887 cx.notify();
5888 } else {
5889 let hide_task = cx.spawn(async move |editor, cx| {
5890 cx.background_executor()
5891 .timer(std::time::Duration::from_millis(100))
5892 .await;
5893 editor
5894 .update(cx, |editor, cx| {
5895 editor.inline_blame_popover.take();
5896 cx.notify();
5897 })
5898 .ok();
5899 });
5900 state.hide_task = Some(hide_task);
5901 }
5902 }
5903 }
5904
5905 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5906 if self.pending_rename.is_some() {
5907 return None;
5908 }
5909
5910 let provider = self.semantics_provider.clone()?;
5911 let buffer = self.buffer.read(cx);
5912 let newest_selection = self.selections.newest_anchor().clone();
5913 let cursor_position = newest_selection.head();
5914 let (cursor_buffer, cursor_buffer_position) =
5915 buffer.text_anchor_for_position(cursor_position, cx)?;
5916 let (tail_buffer, tail_buffer_position) =
5917 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5918 if cursor_buffer != tail_buffer {
5919 return None;
5920 }
5921
5922 let snapshot = cursor_buffer.read(cx).snapshot();
5923 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5924 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5925 if start_word_range != end_word_range {
5926 self.document_highlights_task.take();
5927 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5928 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5929 return None;
5930 }
5931
5932 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5933 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5934 cx.background_executor()
5935 .timer(Duration::from_millis(debounce))
5936 .await;
5937
5938 let highlights = if let Some(highlights) = cx
5939 .update(|cx| {
5940 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5941 })
5942 .ok()
5943 .flatten()
5944 {
5945 highlights.await.log_err()
5946 } else {
5947 None
5948 };
5949
5950 if let Some(highlights) = highlights {
5951 this.update(cx, |this, cx| {
5952 if this.pending_rename.is_some() {
5953 return;
5954 }
5955
5956 let buffer_id = cursor_position.buffer_id;
5957 let buffer = this.buffer.read(cx);
5958 if !buffer
5959 .text_anchor_for_position(cursor_position, cx)
5960 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5961 {
5962 return;
5963 }
5964
5965 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5966 let mut write_ranges = Vec::new();
5967 let mut read_ranges = Vec::new();
5968 for highlight in highlights {
5969 for (excerpt_id, excerpt_range) in
5970 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5971 {
5972 let start = highlight
5973 .range
5974 .start
5975 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5976 let end = highlight
5977 .range
5978 .end
5979 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5980 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5981 continue;
5982 }
5983
5984 let range = Anchor {
5985 buffer_id,
5986 excerpt_id,
5987 text_anchor: start,
5988 diff_base_anchor: None,
5989 }..Anchor {
5990 buffer_id,
5991 excerpt_id,
5992 text_anchor: end,
5993 diff_base_anchor: None,
5994 };
5995 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5996 write_ranges.push(range);
5997 } else {
5998 read_ranges.push(range);
5999 }
6000 }
6001 }
6002
6003 this.highlight_background::<DocumentHighlightRead>(
6004 &read_ranges,
6005 |theme| theme.editor_document_highlight_read_background,
6006 cx,
6007 );
6008 this.highlight_background::<DocumentHighlightWrite>(
6009 &write_ranges,
6010 |theme| theme.editor_document_highlight_write_background,
6011 cx,
6012 );
6013 cx.notify();
6014 })
6015 .log_err();
6016 }
6017 }));
6018 None
6019 }
6020
6021 fn prepare_highlight_query_from_selection(
6022 &mut self,
6023 cx: &mut Context<Editor>,
6024 ) -> Option<(String, Range<Anchor>)> {
6025 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6026 return None;
6027 }
6028 if !EditorSettings::get_global(cx).selection_highlight {
6029 return None;
6030 }
6031 if self.selections.count() != 1 || self.selections.line_mode {
6032 return None;
6033 }
6034 let selection = self.selections.newest::<Point>(cx);
6035 if selection.is_empty() || selection.start.row != selection.end.row {
6036 return None;
6037 }
6038 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6039 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6040 let query = multi_buffer_snapshot
6041 .text_for_range(selection_anchor_range.clone())
6042 .collect::<String>();
6043 if query.trim().is_empty() {
6044 return None;
6045 }
6046 Some((query, selection_anchor_range))
6047 }
6048
6049 fn update_selection_occurrence_highlights(
6050 &mut self,
6051 query_text: String,
6052 query_range: Range<Anchor>,
6053 multi_buffer_range_to_query: Range<Point>,
6054 use_debounce: bool,
6055 window: &mut Window,
6056 cx: &mut Context<Editor>,
6057 ) -> Task<()> {
6058 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6059 cx.spawn_in(window, async move |editor, cx| {
6060 if use_debounce {
6061 cx.background_executor()
6062 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6063 .await;
6064 }
6065 let match_task = cx.background_spawn(async move {
6066 let buffer_ranges = multi_buffer_snapshot
6067 .range_to_buffer_ranges(multi_buffer_range_to_query)
6068 .into_iter()
6069 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6070 let mut match_ranges = Vec::new();
6071 let Ok(regex) = project::search::SearchQuery::text(
6072 query_text.clone(),
6073 false,
6074 false,
6075 false,
6076 Default::default(),
6077 Default::default(),
6078 false,
6079 None,
6080 ) else {
6081 return Vec::default();
6082 };
6083 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6084 match_ranges.extend(
6085 regex
6086 .search(&buffer_snapshot, Some(search_range.clone()))
6087 .await
6088 .into_iter()
6089 .filter_map(|match_range| {
6090 let match_start = buffer_snapshot
6091 .anchor_after(search_range.start + match_range.start);
6092 let match_end = buffer_snapshot
6093 .anchor_before(search_range.start + match_range.end);
6094 let match_anchor_range = Anchor::range_in_buffer(
6095 excerpt_id,
6096 buffer_snapshot.remote_id(),
6097 match_start..match_end,
6098 );
6099 (match_anchor_range != query_range).then_some(match_anchor_range)
6100 }),
6101 );
6102 }
6103 match_ranges
6104 });
6105 let match_ranges = match_task.await;
6106 editor
6107 .update_in(cx, |editor, _, cx| {
6108 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6109 if !match_ranges.is_empty() {
6110 editor.highlight_background::<SelectedTextHighlight>(
6111 &match_ranges,
6112 |theme| theme.editor_document_highlight_bracket_background,
6113 cx,
6114 )
6115 }
6116 })
6117 .log_err();
6118 })
6119 }
6120
6121 fn refresh_selected_text_highlights(
6122 &mut self,
6123 on_buffer_edit: bool,
6124 window: &mut Window,
6125 cx: &mut Context<Editor>,
6126 ) {
6127 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6128 else {
6129 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6130 self.quick_selection_highlight_task.take();
6131 self.debounced_selection_highlight_task.take();
6132 return;
6133 };
6134 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6135 if on_buffer_edit
6136 || self
6137 .quick_selection_highlight_task
6138 .as_ref()
6139 .map_or(true, |(prev_anchor_range, _)| {
6140 prev_anchor_range != &query_range
6141 })
6142 {
6143 let multi_buffer_visible_start = self
6144 .scroll_manager
6145 .anchor()
6146 .anchor
6147 .to_point(&multi_buffer_snapshot);
6148 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6149 multi_buffer_visible_start
6150 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6151 Bias::Left,
6152 );
6153 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6154 self.quick_selection_highlight_task = Some((
6155 query_range.clone(),
6156 self.update_selection_occurrence_highlights(
6157 query_text.clone(),
6158 query_range.clone(),
6159 multi_buffer_visible_range,
6160 false,
6161 window,
6162 cx,
6163 ),
6164 ));
6165 }
6166 if on_buffer_edit
6167 || self
6168 .debounced_selection_highlight_task
6169 .as_ref()
6170 .map_or(true, |(prev_anchor_range, _)| {
6171 prev_anchor_range != &query_range
6172 })
6173 {
6174 let multi_buffer_start = multi_buffer_snapshot
6175 .anchor_before(0)
6176 .to_point(&multi_buffer_snapshot);
6177 let multi_buffer_end = multi_buffer_snapshot
6178 .anchor_after(multi_buffer_snapshot.len())
6179 .to_point(&multi_buffer_snapshot);
6180 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6181 self.debounced_selection_highlight_task = Some((
6182 query_range.clone(),
6183 self.update_selection_occurrence_highlights(
6184 query_text,
6185 query_range,
6186 multi_buffer_full_range,
6187 true,
6188 window,
6189 cx,
6190 ),
6191 ));
6192 }
6193 }
6194
6195 pub fn refresh_inline_completion(
6196 &mut self,
6197 debounce: bool,
6198 user_requested: bool,
6199 window: &mut Window,
6200 cx: &mut Context<Self>,
6201 ) -> Option<()> {
6202 let provider = self.edit_prediction_provider()?;
6203 let cursor = self.selections.newest_anchor().head();
6204 let (buffer, cursor_buffer_position) =
6205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6206
6207 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6208 self.discard_inline_completion(false, cx);
6209 return None;
6210 }
6211
6212 if !user_requested
6213 && (!self.should_show_edit_predictions()
6214 || !self.is_focused(window)
6215 || buffer.read(cx).is_empty())
6216 {
6217 self.discard_inline_completion(false, cx);
6218 return None;
6219 }
6220
6221 self.update_visible_inline_completion(window, cx);
6222 provider.refresh(
6223 self.project.clone(),
6224 buffer,
6225 cursor_buffer_position,
6226 debounce,
6227 cx,
6228 );
6229 Some(())
6230 }
6231
6232 fn show_edit_predictions_in_menu(&self) -> bool {
6233 match self.edit_prediction_settings {
6234 EditPredictionSettings::Disabled => false,
6235 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6236 }
6237 }
6238
6239 pub fn edit_predictions_enabled(&self) -> bool {
6240 match self.edit_prediction_settings {
6241 EditPredictionSettings::Disabled => false,
6242 EditPredictionSettings::Enabled { .. } => true,
6243 }
6244 }
6245
6246 fn edit_prediction_requires_modifier(&self) -> bool {
6247 match self.edit_prediction_settings {
6248 EditPredictionSettings::Disabled => false,
6249 EditPredictionSettings::Enabled {
6250 preview_requires_modifier,
6251 ..
6252 } => preview_requires_modifier,
6253 }
6254 }
6255
6256 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6257 if self.edit_prediction_provider.is_none() {
6258 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6259 } else {
6260 let selection = self.selections.newest_anchor();
6261 let cursor = selection.head();
6262
6263 if let Some((buffer, cursor_buffer_position)) =
6264 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6265 {
6266 self.edit_prediction_settings =
6267 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6268 }
6269 }
6270 }
6271
6272 fn edit_prediction_settings_at_position(
6273 &self,
6274 buffer: &Entity<Buffer>,
6275 buffer_position: language::Anchor,
6276 cx: &App,
6277 ) -> EditPredictionSettings {
6278 if !self.mode.is_full()
6279 || !self.show_inline_completions_override.unwrap_or(true)
6280 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6281 {
6282 return EditPredictionSettings::Disabled;
6283 }
6284
6285 let buffer = buffer.read(cx);
6286
6287 let file = buffer.file();
6288
6289 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6290 return EditPredictionSettings::Disabled;
6291 };
6292
6293 let by_provider = matches!(
6294 self.menu_inline_completions_policy,
6295 MenuInlineCompletionsPolicy::ByProvider
6296 );
6297
6298 let show_in_menu = by_provider
6299 && self
6300 .edit_prediction_provider
6301 .as_ref()
6302 .map_or(false, |provider| {
6303 provider.provider.show_completions_in_menu()
6304 });
6305
6306 let preview_requires_modifier =
6307 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6308
6309 EditPredictionSettings::Enabled {
6310 show_in_menu,
6311 preview_requires_modifier,
6312 }
6313 }
6314
6315 fn should_show_edit_predictions(&self) -> bool {
6316 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6317 }
6318
6319 pub fn edit_prediction_preview_is_active(&self) -> bool {
6320 matches!(
6321 self.edit_prediction_preview,
6322 EditPredictionPreview::Active { .. }
6323 )
6324 }
6325
6326 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6327 let cursor = self.selections.newest_anchor().head();
6328 if let Some((buffer, cursor_position)) =
6329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6330 {
6331 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6332 } else {
6333 false
6334 }
6335 }
6336
6337 pub fn supports_minimap(&self, cx: &App) -> bool {
6338 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6339 }
6340
6341 fn edit_predictions_enabled_in_buffer(
6342 &self,
6343 buffer: &Entity<Buffer>,
6344 buffer_position: language::Anchor,
6345 cx: &App,
6346 ) -> bool {
6347 maybe!({
6348 if self.read_only(cx) {
6349 return Some(false);
6350 }
6351 let provider = self.edit_prediction_provider()?;
6352 if !provider.is_enabled(&buffer, buffer_position, cx) {
6353 return Some(false);
6354 }
6355 let buffer = buffer.read(cx);
6356 let Some(file) = buffer.file() else {
6357 return Some(true);
6358 };
6359 let settings = all_language_settings(Some(file), cx);
6360 Some(settings.edit_predictions_enabled_for_file(file, cx))
6361 })
6362 .unwrap_or(false)
6363 }
6364
6365 fn cycle_inline_completion(
6366 &mut self,
6367 direction: Direction,
6368 window: &mut Window,
6369 cx: &mut Context<Self>,
6370 ) -> Option<()> {
6371 let provider = self.edit_prediction_provider()?;
6372 let cursor = self.selections.newest_anchor().head();
6373 let (buffer, cursor_buffer_position) =
6374 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6375 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6376 return None;
6377 }
6378
6379 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6380 self.update_visible_inline_completion(window, cx);
6381
6382 Some(())
6383 }
6384
6385 pub fn show_inline_completion(
6386 &mut self,
6387 _: &ShowEditPrediction,
6388 window: &mut Window,
6389 cx: &mut Context<Self>,
6390 ) {
6391 if !self.has_active_inline_completion() {
6392 self.refresh_inline_completion(false, true, window, cx);
6393 return;
6394 }
6395
6396 self.update_visible_inline_completion(window, cx);
6397 }
6398
6399 pub fn display_cursor_names(
6400 &mut self,
6401 _: &DisplayCursorNames,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 self.show_cursor_names(window, cx);
6406 }
6407
6408 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6409 self.show_cursor_names = true;
6410 cx.notify();
6411 cx.spawn_in(window, async move |this, cx| {
6412 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6413 this.update(cx, |this, cx| {
6414 this.show_cursor_names = false;
6415 cx.notify()
6416 })
6417 .ok()
6418 })
6419 .detach();
6420 }
6421
6422 pub fn next_edit_prediction(
6423 &mut self,
6424 _: &NextEditPrediction,
6425 window: &mut Window,
6426 cx: &mut Context<Self>,
6427 ) {
6428 if self.has_active_inline_completion() {
6429 self.cycle_inline_completion(Direction::Next, window, cx);
6430 } else {
6431 let is_copilot_disabled = self
6432 .refresh_inline_completion(false, true, window, cx)
6433 .is_none();
6434 if is_copilot_disabled {
6435 cx.propagate();
6436 }
6437 }
6438 }
6439
6440 pub fn previous_edit_prediction(
6441 &mut self,
6442 _: &PreviousEditPrediction,
6443 window: &mut Window,
6444 cx: &mut Context<Self>,
6445 ) {
6446 if self.has_active_inline_completion() {
6447 self.cycle_inline_completion(Direction::Prev, window, cx);
6448 } else {
6449 let is_copilot_disabled = self
6450 .refresh_inline_completion(false, true, window, cx)
6451 .is_none();
6452 if is_copilot_disabled {
6453 cx.propagate();
6454 }
6455 }
6456 }
6457
6458 pub fn accept_edit_prediction(
6459 &mut self,
6460 _: &AcceptEditPrediction,
6461 window: &mut Window,
6462 cx: &mut Context<Self>,
6463 ) {
6464 if self.show_edit_predictions_in_menu() {
6465 self.hide_context_menu(window, cx);
6466 }
6467
6468 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6469 return;
6470 };
6471
6472 self.report_inline_completion_event(
6473 active_inline_completion.completion_id.clone(),
6474 true,
6475 cx,
6476 );
6477
6478 match &active_inline_completion.completion {
6479 InlineCompletion::Move { target, .. } => {
6480 let target = *target;
6481
6482 if let Some(position_map) = &self.last_position_map {
6483 if position_map
6484 .visible_row_range
6485 .contains(&target.to_display_point(&position_map.snapshot).row())
6486 || !self.edit_prediction_requires_modifier()
6487 {
6488 self.unfold_ranges(&[target..target], true, false, cx);
6489 // Note that this is also done in vim's handler of the Tab action.
6490 self.change_selections(
6491 Some(Autoscroll::newest()),
6492 window,
6493 cx,
6494 |selections| {
6495 selections.select_anchor_ranges([target..target]);
6496 },
6497 );
6498 self.clear_row_highlights::<EditPredictionPreview>();
6499
6500 self.edit_prediction_preview
6501 .set_previous_scroll_position(None);
6502 } else {
6503 self.edit_prediction_preview
6504 .set_previous_scroll_position(Some(
6505 position_map.snapshot.scroll_anchor,
6506 ));
6507
6508 self.highlight_rows::<EditPredictionPreview>(
6509 target..target,
6510 cx.theme().colors().editor_highlighted_line_background,
6511 RowHighlightOptions {
6512 autoscroll: true,
6513 ..Default::default()
6514 },
6515 cx,
6516 );
6517 self.request_autoscroll(Autoscroll::fit(), cx);
6518 }
6519 }
6520 }
6521 InlineCompletion::Edit { edits, .. } => {
6522 if let Some(provider) = self.edit_prediction_provider() {
6523 provider.accept(cx);
6524 }
6525
6526 let snapshot = self.buffer.read(cx).snapshot(cx);
6527 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6528
6529 self.buffer.update(cx, |buffer, cx| {
6530 buffer.edit(edits.iter().cloned(), None, cx)
6531 });
6532
6533 self.change_selections(None, window, cx, |s| {
6534 s.select_anchor_ranges([last_edit_end..last_edit_end])
6535 });
6536
6537 self.update_visible_inline_completion(window, cx);
6538 if self.active_inline_completion.is_none() {
6539 self.refresh_inline_completion(true, true, window, cx);
6540 }
6541
6542 cx.notify();
6543 }
6544 }
6545
6546 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6547 }
6548
6549 pub fn accept_partial_inline_completion(
6550 &mut self,
6551 _: &AcceptPartialEditPrediction,
6552 window: &mut Window,
6553 cx: &mut Context<Self>,
6554 ) {
6555 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6556 return;
6557 };
6558 if self.selections.count() != 1 {
6559 return;
6560 }
6561
6562 self.report_inline_completion_event(
6563 active_inline_completion.completion_id.clone(),
6564 true,
6565 cx,
6566 );
6567
6568 match &active_inline_completion.completion {
6569 InlineCompletion::Move { target, .. } => {
6570 let target = *target;
6571 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6572 selections.select_anchor_ranges([target..target]);
6573 });
6574 }
6575 InlineCompletion::Edit { edits, .. } => {
6576 // Find an insertion that starts at the cursor position.
6577 let snapshot = self.buffer.read(cx).snapshot(cx);
6578 let cursor_offset = self.selections.newest::<usize>(cx).head();
6579 let insertion = edits.iter().find_map(|(range, text)| {
6580 let range = range.to_offset(&snapshot);
6581 if range.is_empty() && range.start == cursor_offset {
6582 Some(text)
6583 } else {
6584 None
6585 }
6586 });
6587
6588 if let Some(text) = insertion {
6589 let mut partial_completion = text
6590 .chars()
6591 .by_ref()
6592 .take_while(|c| c.is_alphabetic())
6593 .collect::<String>();
6594 if partial_completion.is_empty() {
6595 partial_completion = text
6596 .chars()
6597 .by_ref()
6598 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6599 .collect::<String>();
6600 }
6601
6602 cx.emit(EditorEvent::InputHandled {
6603 utf16_range_to_replace: None,
6604 text: partial_completion.clone().into(),
6605 });
6606
6607 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6608
6609 self.refresh_inline_completion(true, true, window, cx);
6610 cx.notify();
6611 } else {
6612 self.accept_edit_prediction(&Default::default(), window, cx);
6613 }
6614 }
6615 }
6616 }
6617
6618 fn discard_inline_completion(
6619 &mut self,
6620 should_report_inline_completion_event: bool,
6621 cx: &mut Context<Self>,
6622 ) -> bool {
6623 if should_report_inline_completion_event {
6624 let completion_id = self
6625 .active_inline_completion
6626 .as_ref()
6627 .and_then(|active_completion| active_completion.completion_id.clone());
6628
6629 self.report_inline_completion_event(completion_id, false, cx);
6630 }
6631
6632 if let Some(provider) = self.edit_prediction_provider() {
6633 provider.discard(cx);
6634 }
6635
6636 self.take_active_inline_completion(cx)
6637 }
6638
6639 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6640 let Some(provider) = self.edit_prediction_provider() else {
6641 return;
6642 };
6643
6644 let Some((_, buffer, _)) = self
6645 .buffer
6646 .read(cx)
6647 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6648 else {
6649 return;
6650 };
6651
6652 let extension = buffer
6653 .read(cx)
6654 .file()
6655 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6656
6657 let event_type = match accepted {
6658 true => "Edit Prediction Accepted",
6659 false => "Edit Prediction Discarded",
6660 };
6661 telemetry::event!(
6662 event_type,
6663 provider = provider.name(),
6664 prediction_id = id,
6665 suggestion_accepted = accepted,
6666 file_extension = extension,
6667 );
6668 }
6669
6670 pub fn has_active_inline_completion(&self) -> bool {
6671 self.active_inline_completion.is_some()
6672 }
6673
6674 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6675 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6676 return false;
6677 };
6678
6679 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6680 self.clear_highlights::<InlineCompletionHighlight>(cx);
6681 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6682 true
6683 }
6684
6685 /// Returns true when we're displaying the edit prediction popover below the cursor
6686 /// like we are not previewing and the LSP autocomplete menu is visible
6687 /// or we are in `when_holding_modifier` mode.
6688 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6689 if self.edit_prediction_preview_is_active()
6690 || !self.show_edit_predictions_in_menu()
6691 || !self.edit_predictions_enabled()
6692 {
6693 return false;
6694 }
6695
6696 if self.has_visible_completions_menu() {
6697 return true;
6698 }
6699
6700 has_completion && self.edit_prediction_requires_modifier()
6701 }
6702
6703 fn handle_modifiers_changed(
6704 &mut self,
6705 modifiers: Modifiers,
6706 position_map: &PositionMap,
6707 window: &mut Window,
6708 cx: &mut Context<Self>,
6709 ) {
6710 if self.show_edit_predictions_in_menu() {
6711 self.update_edit_prediction_preview(&modifiers, window, cx);
6712 }
6713
6714 self.update_selection_mode(&modifiers, position_map, window, cx);
6715
6716 let mouse_position = window.mouse_position();
6717 if !position_map.text_hitbox.is_hovered(window) {
6718 return;
6719 }
6720
6721 self.update_hovered_link(
6722 position_map.point_for_position(mouse_position),
6723 &position_map.snapshot,
6724 modifiers,
6725 window,
6726 cx,
6727 )
6728 }
6729
6730 fn update_selection_mode(
6731 &mut self,
6732 modifiers: &Modifiers,
6733 position_map: &PositionMap,
6734 window: &mut Window,
6735 cx: &mut Context<Self>,
6736 ) {
6737 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6738 return;
6739 }
6740
6741 let mouse_position = window.mouse_position();
6742 let point_for_position = position_map.point_for_position(mouse_position);
6743 let position = point_for_position.previous_valid;
6744
6745 self.select(
6746 SelectPhase::BeginColumnar {
6747 position,
6748 reset: false,
6749 goal_column: point_for_position.exact_unclipped.column(),
6750 },
6751 window,
6752 cx,
6753 );
6754 }
6755
6756 fn update_edit_prediction_preview(
6757 &mut self,
6758 modifiers: &Modifiers,
6759 window: &mut Window,
6760 cx: &mut Context<Self>,
6761 ) {
6762 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6763 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6764 return;
6765 };
6766
6767 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6768 if matches!(
6769 self.edit_prediction_preview,
6770 EditPredictionPreview::Inactive { .. }
6771 ) {
6772 self.edit_prediction_preview = EditPredictionPreview::Active {
6773 previous_scroll_position: None,
6774 since: Instant::now(),
6775 };
6776
6777 self.update_visible_inline_completion(window, cx);
6778 cx.notify();
6779 }
6780 } else if let EditPredictionPreview::Active {
6781 previous_scroll_position,
6782 since,
6783 } = self.edit_prediction_preview
6784 {
6785 if let (Some(previous_scroll_position), Some(position_map)) =
6786 (previous_scroll_position, self.last_position_map.as_ref())
6787 {
6788 self.set_scroll_position(
6789 previous_scroll_position
6790 .scroll_position(&position_map.snapshot.display_snapshot),
6791 window,
6792 cx,
6793 );
6794 }
6795
6796 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6797 released_too_fast: since.elapsed() < Duration::from_millis(200),
6798 };
6799 self.clear_row_highlights::<EditPredictionPreview>();
6800 self.update_visible_inline_completion(window, cx);
6801 cx.notify();
6802 }
6803 }
6804
6805 fn update_visible_inline_completion(
6806 &mut self,
6807 _window: &mut Window,
6808 cx: &mut Context<Self>,
6809 ) -> Option<()> {
6810 let selection = self.selections.newest_anchor();
6811 let cursor = selection.head();
6812 let multibuffer = self.buffer.read(cx).snapshot(cx);
6813 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6814 let excerpt_id = cursor.excerpt_id;
6815
6816 let show_in_menu = self.show_edit_predictions_in_menu();
6817 let completions_menu_has_precedence = !show_in_menu
6818 && (self.context_menu.borrow().is_some()
6819 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6820
6821 if completions_menu_has_precedence
6822 || !offset_selection.is_empty()
6823 || self
6824 .active_inline_completion
6825 .as_ref()
6826 .map_or(false, |completion| {
6827 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6828 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6829 !invalidation_range.contains(&offset_selection.head())
6830 })
6831 {
6832 self.discard_inline_completion(false, cx);
6833 return None;
6834 }
6835
6836 self.take_active_inline_completion(cx);
6837 let Some(provider) = self.edit_prediction_provider() else {
6838 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6839 return None;
6840 };
6841
6842 let (buffer, cursor_buffer_position) =
6843 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6844
6845 self.edit_prediction_settings =
6846 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6847
6848 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6849
6850 if self.edit_prediction_indent_conflict {
6851 let cursor_point = cursor.to_point(&multibuffer);
6852
6853 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6854
6855 if let Some((_, indent)) = indents.iter().next() {
6856 if indent.len == cursor_point.column {
6857 self.edit_prediction_indent_conflict = false;
6858 }
6859 }
6860 }
6861
6862 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6863 let edits = inline_completion
6864 .edits
6865 .into_iter()
6866 .flat_map(|(range, new_text)| {
6867 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6868 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6869 Some((start..end, new_text))
6870 })
6871 .collect::<Vec<_>>();
6872 if edits.is_empty() {
6873 return None;
6874 }
6875
6876 let first_edit_start = edits.first().unwrap().0.start;
6877 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6878 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6879
6880 let last_edit_end = edits.last().unwrap().0.end;
6881 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6882 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6883
6884 let cursor_row = cursor.to_point(&multibuffer).row;
6885
6886 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6887
6888 let mut inlay_ids = Vec::new();
6889 let invalidation_row_range;
6890 let move_invalidation_row_range = if cursor_row < edit_start_row {
6891 Some(cursor_row..edit_end_row)
6892 } else if cursor_row > edit_end_row {
6893 Some(edit_start_row..cursor_row)
6894 } else {
6895 None
6896 };
6897 let is_move =
6898 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6899 let completion = if is_move {
6900 invalidation_row_range =
6901 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6902 let target = first_edit_start;
6903 InlineCompletion::Move { target, snapshot }
6904 } else {
6905 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6906 && !self.inline_completions_hidden_for_vim_mode;
6907
6908 if show_completions_in_buffer {
6909 if edits
6910 .iter()
6911 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6912 {
6913 let mut inlays = Vec::new();
6914 for (range, new_text) in &edits {
6915 let inlay = Inlay::inline_completion(
6916 post_inc(&mut self.next_inlay_id),
6917 range.start,
6918 new_text.as_str(),
6919 );
6920 inlay_ids.push(inlay.id);
6921 inlays.push(inlay);
6922 }
6923
6924 self.splice_inlays(&[], inlays, cx);
6925 } else {
6926 let background_color = cx.theme().status().deleted_background;
6927 self.highlight_text::<InlineCompletionHighlight>(
6928 edits.iter().map(|(range, _)| range.clone()).collect(),
6929 HighlightStyle {
6930 background_color: Some(background_color),
6931 ..Default::default()
6932 },
6933 cx,
6934 );
6935 }
6936 }
6937
6938 invalidation_row_range = edit_start_row..edit_end_row;
6939
6940 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6941 if provider.show_tab_accept_marker() {
6942 EditDisplayMode::TabAccept
6943 } else {
6944 EditDisplayMode::Inline
6945 }
6946 } else {
6947 EditDisplayMode::DiffPopover
6948 };
6949
6950 InlineCompletion::Edit {
6951 edits,
6952 edit_preview: inline_completion.edit_preview,
6953 display_mode,
6954 snapshot,
6955 }
6956 };
6957
6958 let invalidation_range = multibuffer
6959 .anchor_before(Point::new(invalidation_row_range.start, 0))
6960 ..multibuffer.anchor_after(Point::new(
6961 invalidation_row_range.end,
6962 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6963 ));
6964
6965 self.stale_inline_completion_in_menu = None;
6966 self.active_inline_completion = Some(InlineCompletionState {
6967 inlay_ids,
6968 completion,
6969 completion_id: inline_completion.id,
6970 invalidation_range,
6971 });
6972
6973 cx.notify();
6974
6975 Some(())
6976 }
6977
6978 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6979 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6980 }
6981
6982 fn clear_tasks(&mut self) {
6983 self.tasks.clear()
6984 }
6985
6986 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6987 if self.tasks.insert(key, value).is_some() {
6988 // This case should hopefully be rare, but just in case...
6989 log::error!(
6990 "multiple different run targets found on a single line, only the last target will be rendered"
6991 )
6992 }
6993 }
6994
6995 /// Get all display points of breakpoints that will be rendered within editor
6996 ///
6997 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6998 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6999 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7000 fn active_breakpoints(
7001 &self,
7002 range: Range<DisplayRow>,
7003 window: &mut Window,
7004 cx: &mut Context<Self>,
7005 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7006 let mut breakpoint_display_points = HashMap::default();
7007
7008 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7009 return breakpoint_display_points;
7010 };
7011
7012 let snapshot = self.snapshot(window, cx);
7013
7014 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7015 let Some(project) = self.project.as_ref() else {
7016 return breakpoint_display_points;
7017 };
7018
7019 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7020 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7021
7022 for (buffer_snapshot, range, excerpt_id) in
7023 multi_buffer_snapshot.range_to_buffer_ranges(range)
7024 {
7025 let Some(buffer) = project.read_with(cx, |this, cx| {
7026 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7027 }) else {
7028 continue;
7029 };
7030 let breakpoints = breakpoint_store.read(cx).breakpoints(
7031 &buffer,
7032 Some(
7033 buffer_snapshot.anchor_before(range.start)
7034 ..buffer_snapshot.anchor_after(range.end),
7035 ),
7036 buffer_snapshot,
7037 cx,
7038 );
7039 for (breakpoint, state) in breakpoints {
7040 let multi_buffer_anchor =
7041 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7042 let position = multi_buffer_anchor
7043 .to_point(&multi_buffer_snapshot)
7044 .to_display_point(&snapshot);
7045
7046 breakpoint_display_points.insert(
7047 position.row(),
7048 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7049 );
7050 }
7051 }
7052
7053 breakpoint_display_points
7054 }
7055
7056 fn breakpoint_context_menu(
7057 &self,
7058 anchor: Anchor,
7059 window: &mut Window,
7060 cx: &mut Context<Self>,
7061 ) -> Entity<ui::ContextMenu> {
7062 let weak_editor = cx.weak_entity();
7063 let focus_handle = self.focus_handle(cx);
7064
7065 let row = self
7066 .buffer
7067 .read(cx)
7068 .snapshot(cx)
7069 .summary_for_anchor::<Point>(&anchor)
7070 .row;
7071
7072 let breakpoint = self
7073 .breakpoint_at_row(row, window, cx)
7074 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7075
7076 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7077 "Edit Log Breakpoint"
7078 } else {
7079 "Set Log Breakpoint"
7080 };
7081
7082 let condition_breakpoint_msg = if breakpoint
7083 .as_ref()
7084 .is_some_and(|bp| bp.1.condition.is_some())
7085 {
7086 "Edit Condition Breakpoint"
7087 } else {
7088 "Set Condition Breakpoint"
7089 };
7090
7091 let hit_condition_breakpoint_msg = if breakpoint
7092 .as_ref()
7093 .is_some_and(|bp| bp.1.hit_condition.is_some())
7094 {
7095 "Edit Hit Condition Breakpoint"
7096 } else {
7097 "Set Hit Condition Breakpoint"
7098 };
7099
7100 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7101 "Unset Breakpoint"
7102 } else {
7103 "Set Breakpoint"
7104 };
7105
7106 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7107 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7108
7109 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7110 BreakpointState::Enabled => Some("Disable"),
7111 BreakpointState::Disabled => Some("Enable"),
7112 });
7113
7114 let (anchor, breakpoint) =
7115 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7116
7117 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7118 menu.on_blur_subscription(Subscription::new(|| {}))
7119 .context(focus_handle)
7120 .when(run_to_cursor, |this| {
7121 let weak_editor = weak_editor.clone();
7122 this.entry("Run to cursor", None, move |window, cx| {
7123 weak_editor
7124 .update(cx, |editor, cx| {
7125 editor.change_selections(None, window, cx, |s| {
7126 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7127 });
7128 })
7129 .ok();
7130
7131 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7132 })
7133 .separator()
7134 })
7135 .when_some(toggle_state_msg, |this, msg| {
7136 this.entry(msg, None, {
7137 let weak_editor = weak_editor.clone();
7138 let breakpoint = breakpoint.clone();
7139 move |_window, cx| {
7140 weak_editor
7141 .update(cx, |this, cx| {
7142 this.edit_breakpoint_at_anchor(
7143 anchor,
7144 breakpoint.as_ref().clone(),
7145 BreakpointEditAction::InvertState,
7146 cx,
7147 );
7148 })
7149 .log_err();
7150 }
7151 })
7152 })
7153 .entry(set_breakpoint_msg, None, {
7154 let weak_editor = weak_editor.clone();
7155 let breakpoint = breakpoint.clone();
7156 move |_window, cx| {
7157 weak_editor
7158 .update(cx, |this, cx| {
7159 this.edit_breakpoint_at_anchor(
7160 anchor,
7161 breakpoint.as_ref().clone(),
7162 BreakpointEditAction::Toggle,
7163 cx,
7164 );
7165 })
7166 .log_err();
7167 }
7168 })
7169 .entry(log_breakpoint_msg, None, {
7170 let breakpoint = breakpoint.clone();
7171 let weak_editor = weak_editor.clone();
7172 move |window, cx| {
7173 weak_editor
7174 .update(cx, |this, cx| {
7175 this.add_edit_breakpoint_block(
7176 anchor,
7177 breakpoint.as_ref(),
7178 BreakpointPromptEditAction::Log,
7179 window,
7180 cx,
7181 );
7182 })
7183 .log_err();
7184 }
7185 })
7186 .entry(condition_breakpoint_msg, None, {
7187 let breakpoint = breakpoint.clone();
7188 let weak_editor = weak_editor.clone();
7189 move |window, cx| {
7190 weak_editor
7191 .update(cx, |this, cx| {
7192 this.add_edit_breakpoint_block(
7193 anchor,
7194 breakpoint.as_ref(),
7195 BreakpointPromptEditAction::Condition,
7196 window,
7197 cx,
7198 );
7199 })
7200 .log_err();
7201 }
7202 })
7203 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7204 weak_editor
7205 .update(cx, |this, cx| {
7206 this.add_edit_breakpoint_block(
7207 anchor,
7208 breakpoint.as_ref(),
7209 BreakpointPromptEditAction::HitCondition,
7210 window,
7211 cx,
7212 );
7213 })
7214 .log_err();
7215 })
7216 })
7217 }
7218
7219 fn render_breakpoint(
7220 &self,
7221 position: Anchor,
7222 row: DisplayRow,
7223 breakpoint: &Breakpoint,
7224 state: Option<BreakpointSessionState>,
7225 cx: &mut Context<Self>,
7226 ) -> IconButton {
7227 let is_rejected = state.is_some_and(|s| !s.verified);
7228 // Is it a breakpoint that shows up when hovering over gutter?
7229 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7230 (false, false),
7231 |PhantomBreakpointIndicator {
7232 is_active,
7233 display_row,
7234 collides_with_existing_breakpoint,
7235 }| {
7236 (
7237 is_active && display_row == row,
7238 collides_with_existing_breakpoint,
7239 )
7240 },
7241 );
7242
7243 let (color, icon) = {
7244 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7245 (false, false) => ui::IconName::DebugBreakpoint,
7246 (true, false) => ui::IconName::DebugLogBreakpoint,
7247 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7248 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7249 };
7250
7251 let color = if is_phantom {
7252 Color::Hint
7253 } else if is_rejected {
7254 Color::Disabled
7255 } else {
7256 Color::Debugger
7257 };
7258
7259 (color, icon)
7260 };
7261
7262 let breakpoint = Arc::from(breakpoint.clone());
7263
7264 let alt_as_text = gpui::Keystroke {
7265 modifiers: Modifiers::secondary_key(),
7266 ..Default::default()
7267 };
7268 let primary_action_text = if breakpoint.is_disabled() {
7269 "Enable breakpoint"
7270 } else if is_phantom && !collides_with_existing {
7271 "Set breakpoint"
7272 } else {
7273 "Unset breakpoint"
7274 };
7275 let focus_handle = self.focus_handle.clone();
7276
7277 let meta = if is_rejected {
7278 SharedString::from("No executable code is associated with this line.")
7279 } else if collides_with_existing && !breakpoint.is_disabled() {
7280 SharedString::from(format!(
7281 "{alt_as_text}-click to disable,\nright-click for more options."
7282 ))
7283 } else {
7284 SharedString::from("Right-click for more options.")
7285 };
7286 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7287 .icon_size(IconSize::XSmall)
7288 .size(ui::ButtonSize::None)
7289 .when(is_rejected, |this| {
7290 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7291 })
7292 .icon_color(color)
7293 .style(ButtonStyle::Transparent)
7294 .on_click(cx.listener({
7295 let breakpoint = breakpoint.clone();
7296
7297 move |editor, event: &ClickEvent, window, cx| {
7298 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7299 BreakpointEditAction::InvertState
7300 } else {
7301 BreakpointEditAction::Toggle
7302 };
7303
7304 window.focus(&editor.focus_handle(cx));
7305 editor.edit_breakpoint_at_anchor(
7306 position,
7307 breakpoint.as_ref().clone(),
7308 edit_action,
7309 cx,
7310 );
7311 }
7312 }))
7313 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7314 editor.set_breakpoint_context_menu(
7315 row,
7316 Some(position),
7317 event.down.position,
7318 window,
7319 cx,
7320 );
7321 }))
7322 .tooltip(move |window, cx| {
7323 Tooltip::with_meta_in(
7324 primary_action_text,
7325 Some(&ToggleBreakpoint),
7326 meta.clone(),
7327 &focus_handle,
7328 window,
7329 cx,
7330 )
7331 })
7332 }
7333
7334 fn build_tasks_context(
7335 project: &Entity<Project>,
7336 buffer: &Entity<Buffer>,
7337 buffer_row: u32,
7338 tasks: &Arc<RunnableTasks>,
7339 cx: &mut Context<Self>,
7340 ) -> Task<Option<task::TaskContext>> {
7341 let position = Point::new(buffer_row, tasks.column);
7342 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7343 let location = Location {
7344 buffer: buffer.clone(),
7345 range: range_start..range_start,
7346 };
7347 // Fill in the environmental variables from the tree-sitter captures
7348 let mut captured_task_variables = TaskVariables::default();
7349 for (capture_name, value) in tasks.extra_variables.clone() {
7350 captured_task_variables.insert(
7351 task::VariableName::Custom(capture_name.into()),
7352 value.clone(),
7353 );
7354 }
7355 project.update(cx, |project, cx| {
7356 project.task_store().update(cx, |task_store, cx| {
7357 task_store.task_context_for_location(captured_task_variables, location, cx)
7358 })
7359 })
7360 }
7361
7362 pub fn spawn_nearest_task(
7363 &mut self,
7364 action: &SpawnNearestTask,
7365 window: &mut Window,
7366 cx: &mut Context<Self>,
7367 ) {
7368 let Some((workspace, _)) = self.workspace.clone() else {
7369 return;
7370 };
7371 let Some(project) = self.project.clone() else {
7372 return;
7373 };
7374
7375 // Try to find a closest, enclosing node using tree-sitter that has a
7376 // task
7377 let Some((buffer, buffer_row, tasks)) = self
7378 .find_enclosing_node_task(cx)
7379 // Or find the task that's closest in row-distance.
7380 .or_else(|| self.find_closest_task(cx))
7381 else {
7382 return;
7383 };
7384
7385 let reveal_strategy = action.reveal;
7386 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7387 cx.spawn_in(window, async move |_, cx| {
7388 let context = task_context.await?;
7389 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7390
7391 let resolved = &mut resolved_task.resolved;
7392 resolved.reveal = reveal_strategy;
7393
7394 workspace
7395 .update_in(cx, |workspace, window, cx| {
7396 workspace.schedule_resolved_task(
7397 task_source_kind,
7398 resolved_task,
7399 false,
7400 window,
7401 cx,
7402 );
7403 })
7404 .ok()
7405 })
7406 .detach();
7407 }
7408
7409 fn find_closest_task(
7410 &mut self,
7411 cx: &mut Context<Self>,
7412 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7413 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7414
7415 let ((buffer_id, row), tasks) = self
7416 .tasks
7417 .iter()
7418 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7419
7420 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7421 let tasks = Arc::new(tasks.to_owned());
7422 Some((buffer, *row, tasks))
7423 }
7424
7425 fn find_enclosing_node_task(
7426 &mut self,
7427 cx: &mut Context<Self>,
7428 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7429 let snapshot = self.buffer.read(cx).snapshot(cx);
7430 let offset = self.selections.newest::<usize>(cx).head();
7431 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7432 let buffer_id = excerpt.buffer().remote_id();
7433
7434 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7435 let mut cursor = layer.node().walk();
7436
7437 while cursor.goto_first_child_for_byte(offset).is_some() {
7438 if cursor.node().end_byte() == offset {
7439 cursor.goto_next_sibling();
7440 }
7441 }
7442
7443 // Ascend to the smallest ancestor that contains the range and has a task.
7444 loop {
7445 let node = cursor.node();
7446 let node_range = node.byte_range();
7447 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7448
7449 // Check if this node contains our offset
7450 if node_range.start <= offset && node_range.end >= offset {
7451 // If it contains offset, check for task
7452 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7453 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7454 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7455 }
7456 }
7457
7458 if !cursor.goto_parent() {
7459 break;
7460 }
7461 }
7462 None
7463 }
7464
7465 fn render_run_indicator(
7466 &self,
7467 _style: &EditorStyle,
7468 is_active: bool,
7469 row: DisplayRow,
7470 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7471 cx: &mut Context<Self>,
7472 ) -> IconButton {
7473 let color = Color::Muted;
7474 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7475
7476 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7477 .shape(ui::IconButtonShape::Square)
7478 .icon_size(IconSize::XSmall)
7479 .icon_color(color)
7480 .toggle_state(is_active)
7481 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7482 let quick_launch = e.down.button == MouseButton::Left;
7483 window.focus(&editor.focus_handle(cx));
7484 editor.toggle_code_actions(
7485 &ToggleCodeActions {
7486 deployed_from_indicator: Some(row),
7487 quick_launch,
7488 },
7489 window,
7490 cx,
7491 );
7492 }))
7493 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7494 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7495 }))
7496 }
7497
7498 pub fn context_menu_visible(&self) -> bool {
7499 !self.edit_prediction_preview_is_active()
7500 && self
7501 .context_menu
7502 .borrow()
7503 .as_ref()
7504 .map_or(false, |menu| menu.visible())
7505 }
7506
7507 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7508 self.context_menu
7509 .borrow()
7510 .as_ref()
7511 .map(|menu| menu.origin())
7512 }
7513
7514 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7515 self.context_menu_options = Some(options);
7516 }
7517
7518 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7519 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7520
7521 fn render_edit_prediction_popover(
7522 &mut self,
7523 text_bounds: &Bounds<Pixels>,
7524 content_origin: gpui::Point<Pixels>,
7525 right_margin: Pixels,
7526 editor_snapshot: &EditorSnapshot,
7527 visible_row_range: Range<DisplayRow>,
7528 scroll_top: f32,
7529 scroll_bottom: f32,
7530 line_layouts: &[LineWithInvisibles],
7531 line_height: Pixels,
7532 scroll_pixel_position: gpui::Point<Pixels>,
7533 newest_selection_head: Option<DisplayPoint>,
7534 editor_width: Pixels,
7535 style: &EditorStyle,
7536 window: &mut Window,
7537 cx: &mut App,
7538 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7539 if self.mode().is_minimap() {
7540 return None;
7541 }
7542 let active_inline_completion = self.active_inline_completion.as_ref()?;
7543
7544 if self.edit_prediction_visible_in_cursor_popover(true) {
7545 return None;
7546 }
7547
7548 match &active_inline_completion.completion {
7549 InlineCompletion::Move { target, .. } => {
7550 let target_display_point = target.to_display_point(editor_snapshot);
7551
7552 if self.edit_prediction_requires_modifier() {
7553 if !self.edit_prediction_preview_is_active() {
7554 return None;
7555 }
7556
7557 self.render_edit_prediction_modifier_jump_popover(
7558 text_bounds,
7559 content_origin,
7560 visible_row_range,
7561 line_layouts,
7562 line_height,
7563 scroll_pixel_position,
7564 newest_selection_head,
7565 target_display_point,
7566 window,
7567 cx,
7568 )
7569 } else {
7570 self.render_edit_prediction_eager_jump_popover(
7571 text_bounds,
7572 content_origin,
7573 editor_snapshot,
7574 visible_row_range,
7575 scroll_top,
7576 scroll_bottom,
7577 line_height,
7578 scroll_pixel_position,
7579 target_display_point,
7580 editor_width,
7581 window,
7582 cx,
7583 )
7584 }
7585 }
7586 InlineCompletion::Edit {
7587 display_mode: EditDisplayMode::Inline,
7588 ..
7589 } => None,
7590 InlineCompletion::Edit {
7591 display_mode: EditDisplayMode::TabAccept,
7592 edits,
7593 ..
7594 } => {
7595 let range = &edits.first()?.0;
7596 let target_display_point = range.end.to_display_point(editor_snapshot);
7597
7598 self.render_edit_prediction_end_of_line_popover(
7599 "Accept",
7600 editor_snapshot,
7601 visible_row_range,
7602 target_display_point,
7603 line_height,
7604 scroll_pixel_position,
7605 content_origin,
7606 editor_width,
7607 window,
7608 cx,
7609 )
7610 }
7611 InlineCompletion::Edit {
7612 edits,
7613 edit_preview,
7614 display_mode: EditDisplayMode::DiffPopover,
7615 snapshot,
7616 } => self.render_edit_prediction_diff_popover(
7617 text_bounds,
7618 content_origin,
7619 right_margin,
7620 editor_snapshot,
7621 visible_row_range,
7622 line_layouts,
7623 line_height,
7624 scroll_pixel_position,
7625 newest_selection_head,
7626 editor_width,
7627 style,
7628 edits,
7629 edit_preview,
7630 snapshot,
7631 window,
7632 cx,
7633 ),
7634 }
7635 }
7636
7637 fn render_edit_prediction_modifier_jump_popover(
7638 &mut self,
7639 text_bounds: &Bounds<Pixels>,
7640 content_origin: gpui::Point<Pixels>,
7641 visible_row_range: Range<DisplayRow>,
7642 line_layouts: &[LineWithInvisibles],
7643 line_height: Pixels,
7644 scroll_pixel_position: gpui::Point<Pixels>,
7645 newest_selection_head: Option<DisplayPoint>,
7646 target_display_point: DisplayPoint,
7647 window: &mut Window,
7648 cx: &mut App,
7649 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7650 let scrolled_content_origin =
7651 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7652
7653 const SCROLL_PADDING_Y: Pixels = px(12.);
7654
7655 if target_display_point.row() < visible_row_range.start {
7656 return self.render_edit_prediction_scroll_popover(
7657 |_| SCROLL_PADDING_Y,
7658 IconName::ArrowUp,
7659 visible_row_range,
7660 line_layouts,
7661 newest_selection_head,
7662 scrolled_content_origin,
7663 window,
7664 cx,
7665 );
7666 } else if target_display_point.row() >= visible_row_range.end {
7667 return self.render_edit_prediction_scroll_popover(
7668 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7669 IconName::ArrowDown,
7670 visible_row_range,
7671 line_layouts,
7672 newest_selection_head,
7673 scrolled_content_origin,
7674 window,
7675 cx,
7676 );
7677 }
7678
7679 const POLE_WIDTH: Pixels = px(2.);
7680
7681 let line_layout =
7682 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7683 let target_column = target_display_point.column() as usize;
7684
7685 let target_x = line_layout.x_for_index(target_column);
7686 let target_y =
7687 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7688
7689 let flag_on_right = target_x < text_bounds.size.width / 2.;
7690
7691 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7692 border_color.l += 0.001;
7693
7694 let mut element = v_flex()
7695 .items_end()
7696 .when(flag_on_right, |el| el.items_start())
7697 .child(if flag_on_right {
7698 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7699 .rounded_bl(px(0.))
7700 .rounded_tl(px(0.))
7701 .border_l_2()
7702 .border_color(border_color)
7703 } else {
7704 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7705 .rounded_br(px(0.))
7706 .rounded_tr(px(0.))
7707 .border_r_2()
7708 .border_color(border_color)
7709 })
7710 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7711 .into_any();
7712
7713 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7714
7715 let mut origin = scrolled_content_origin + point(target_x, target_y)
7716 - point(
7717 if flag_on_right {
7718 POLE_WIDTH
7719 } else {
7720 size.width - POLE_WIDTH
7721 },
7722 size.height - line_height,
7723 );
7724
7725 origin.x = origin.x.max(content_origin.x);
7726
7727 element.prepaint_at(origin, window, cx);
7728
7729 Some((element, origin))
7730 }
7731
7732 fn render_edit_prediction_scroll_popover(
7733 &mut self,
7734 to_y: impl Fn(Size<Pixels>) -> Pixels,
7735 scroll_icon: IconName,
7736 visible_row_range: Range<DisplayRow>,
7737 line_layouts: &[LineWithInvisibles],
7738 newest_selection_head: Option<DisplayPoint>,
7739 scrolled_content_origin: gpui::Point<Pixels>,
7740 window: &mut Window,
7741 cx: &mut App,
7742 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7743 let mut element = self
7744 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7745 .into_any();
7746
7747 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7748
7749 let cursor = newest_selection_head?;
7750 let cursor_row_layout =
7751 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7752 let cursor_column = cursor.column() as usize;
7753
7754 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7755
7756 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7757
7758 element.prepaint_at(origin, window, cx);
7759 Some((element, origin))
7760 }
7761
7762 fn render_edit_prediction_eager_jump_popover(
7763 &mut self,
7764 text_bounds: &Bounds<Pixels>,
7765 content_origin: gpui::Point<Pixels>,
7766 editor_snapshot: &EditorSnapshot,
7767 visible_row_range: Range<DisplayRow>,
7768 scroll_top: f32,
7769 scroll_bottom: f32,
7770 line_height: Pixels,
7771 scroll_pixel_position: gpui::Point<Pixels>,
7772 target_display_point: DisplayPoint,
7773 editor_width: Pixels,
7774 window: &mut Window,
7775 cx: &mut App,
7776 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7777 if target_display_point.row().as_f32() < scroll_top {
7778 let mut element = self
7779 .render_edit_prediction_line_popover(
7780 "Jump to Edit",
7781 Some(IconName::ArrowUp),
7782 window,
7783 cx,
7784 )?
7785 .into_any();
7786
7787 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7788 let offset = point(
7789 (text_bounds.size.width - size.width) / 2.,
7790 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7791 );
7792
7793 let origin = text_bounds.origin + offset;
7794 element.prepaint_at(origin, window, cx);
7795 Some((element, origin))
7796 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7797 let mut element = self
7798 .render_edit_prediction_line_popover(
7799 "Jump to Edit",
7800 Some(IconName::ArrowDown),
7801 window,
7802 cx,
7803 )?
7804 .into_any();
7805
7806 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7807 let offset = point(
7808 (text_bounds.size.width - size.width) / 2.,
7809 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7810 );
7811
7812 let origin = text_bounds.origin + offset;
7813 element.prepaint_at(origin, window, cx);
7814 Some((element, origin))
7815 } else {
7816 self.render_edit_prediction_end_of_line_popover(
7817 "Jump to Edit",
7818 editor_snapshot,
7819 visible_row_range,
7820 target_display_point,
7821 line_height,
7822 scroll_pixel_position,
7823 content_origin,
7824 editor_width,
7825 window,
7826 cx,
7827 )
7828 }
7829 }
7830
7831 fn render_edit_prediction_end_of_line_popover(
7832 self: &mut Editor,
7833 label: &'static str,
7834 editor_snapshot: &EditorSnapshot,
7835 visible_row_range: Range<DisplayRow>,
7836 target_display_point: DisplayPoint,
7837 line_height: Pixels,
7838 scroll_pixel_position: gpui::Point<Pixels>,
7839 content_origin: gpui::Point<Pixels>,
7840 editor_width: Pixels,
7841 window: &mut Window,
7842 cx: &mut App,
7843 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7844 let target_line_end = DisplayPoint::new(
7845 target_display_point.row(),
7846 editor_snapshot.line_len(target_display_point.row()),
7847 );
7848
7849 let mut element = self
7850 .render_edit_prediction_line_popover(label, None, window, cx)?
7851 .into_any();
7852
7853 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7854
7855 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7856
7857 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7858 let mut origin = start_point
7859 + line_origin
7860 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7861 origin.x = origin.x.max(content_origin.x);
7862
7863 let max_x = content_origin.x + editor_width - size.width;
7864
7865 if origin.x > max_x {
7866 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7867
7868 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7869 origin.y += offset;
7870 IconName::ArrowUp
7871 } else {
7872 origin.y -= offset;
7873 IconName::ArrowDown
7874 };
7875
7876 element = self
7877 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7878 .into_any();
7879
7880 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7881
7882 origin.x = content_origin.x + editor_width - size.width - px(2.);
7883 }
7884
7885 element.prepaint_at(origin, window, cx);
7886 Some((element, origin))
7887 }
7888
7889 fn render_edit_prediction_diff_popover(
7890 self: &Editor,
7891 text_bounds: &Bounds<Pixels>,
7892 content_origin: gpui::Point<Pixels>,
7893 right_margin: Pixels,
7894 editor_snapshot: &EditorSnapshot,
7895 visible_row_range: Range<DisplayRow>,
7896 line_layouts: &[LineWithInvisibles],
7897 line_height: Pixels,
7898 scroll_pixel_position: gpui::Point<Pixels>,
7899 newest_selection_head: Option<DisplayPoint>,
7900 editor_width: Pixels,
7901 style: &EditorStyle,
7902 edits: &Vec<(Range<Anchor>, String)>,
7903 edit_preview: &Option<language::EditPreview>,
7904 snapshot: &language::BufferSnapshot,
7905 window: &mut Window,
7906 cx: &mut App,
7907 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7908 let edit_start = edits
7909 .first()
7910 .unwrap()
7911 .0
7912 .start
7913 .to_display_point(editor_snapshot);
7914 let edit_end = edits
7915 .last()
7916 .unwrap()
7917 .0
7918 .end
7919 .to_display_point(editor_snapshot);
7920
7921 let is_visible = visible_row_range.contains(&edit_start.row())
7922 || visible_row_range.contains(&edit_end.row());
7923 if !is_visible {
7924 return None;
7925 }
7926
7927 let highlighted_edits =
7928 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7929
7930 let styled_text = highlighted_edits.to_styled_text(&style.text);
7931 let line_count = highlighted_edits.text.lines().count();
7932
7933 const BORDER_WIDTH: Pixels = px(1.);
7934
7935 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7936 let has_keybind = keybind.is_some();
7937
7938 let mut element = h_flex()
7939 .items_start()
7940 .child(
7941 h_flex()
7942 .bg(cx.theme().colors().editor_background)
7943 .border(BORDER_WIDTH)
7944 .shadow_sm()
7945 .border_color(cx.theme().colors().border)
7946 .rounded_l_lg()
7947 .when(line_count > 1, |el| el.rounded_br_lg())
7948 .pr_1()
7949 .child(styled_text),
7950 )
7951 .child(
7952 h_flex()
7953 .h(line_height + BORDER_WIDTH * 2.)
7954 .px_1p5()
7955 .gap_1()
7956 // Workaround: For some reason, there's a gap if we don't do this
7957 .ml(-BORDER_WIDTH)
7958 .shadow(smallvec![gpui::BoxShadow {
7959 color: gpui::black().opacity(0.05),
7960 offset: point(px(1.), px(1.)),
7961 blur_radius: px(2.),
7962 spread_radius: px(0.),
7963 }])
7964 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7965 .border(BORDER_WIDTH)
7966 .border_color(cx.theme().colors().border)
7967 .rounded_r_lg()
7968 .id("edit_prediction_diff_popover_keybind")
7969 .when(!has_keybind, |el| {
7970 let status_colors = cx.theme().status();
7971
7972 el.bg(status_colors.error_background)
7973 .border_color(status_colors.error.opacity(0.6))
7974 .child(Icon::new(IconName::Info).color(Color::Error))
7975 .cursor_default()
7976 .hoverable_tooltip(move |_window, cx| {
7977 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7978 })
7979 })
7980 .children(keybind),
7981 )
7982 .into_any();
7983
7984 let longest_row =
7985 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7986 let longest_line_width = if visible_row_range.contains(&longest_row) {
7987 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7988 } else {
7989 layout_line(
7990 longest_row,
7991 editor_snapshot,
7992 style,
7993 editor_width,
7994 |_| false,
7995 window,
7996 cx,
7997 )
7998 .width
7999 };
8000
8001 let viewport_bounds =
8002 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8003 right: -right_margin,
8004 ..Default::default()
8005 });
8006
8007 let x_after_longest =
8008 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8009 - scroll_pixel_position.x;
8010
8011 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8012
8013 // Fully visible if it can be displayed within the window (allow overlapping other
8014 // panes). However, this is only allowed if the popover starts within text_bounds.
8015 let can_position_to_the_right = x_after_longest < text_bounds.right()
8016 && x_after_longest + element_bounds.width < viewport_bounds.right();
8017
8018 let mut origin = if can_position_to_the_right {
8019 point(
8020 x_after_longest,
8021 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8022 - scroll_pixel_position.y,
8023 )
8024 } else {
8025 let cursor_row = newest_selection_head.map(|head| head.row());
8026 let above_edit = edit_start
8027 .row()
8028 .0
8029 .checked_sub(line_count as u32)
8030 .map(DisplayRow);
8031 let below_edit = Some(edit_end.row() + 1);
8032 let above_cursor =
8033 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8034 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8035
8036 // Place the edit popover adjacent to the edit if there is a location
8037 // available that is onscreen and does not obscure the cursor. Otherwise,
8038 // place it adjacent to the cursor.
8039 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8040 .into_iter()
8041 .flatten()
8042 .find(|&start_row| {
8043 let end_row = start_row + line_count as u32;
8044 visible_row_range.contains(&start_row)
8045 && visible_row_range.contains(&end_row)
8046 && cursor_row.map_or(true, |cursor_row| {
8047 !((start_row..end_row).contains(&cursor_row))
8048 })
8049 })?;
8050
8051 content_origin
8052 + point(
8053 -scroll_pixel_position.x,
8054 row_target.as_f32() * line_height - scroll_pixel_position.y,
8055 )
8056 };
8057
8058 origin.x -= BORDER_WIDTH;
8059
8060 window.defer_draw(element, origin, 1);
8061
8062 // Do not return an element, since it will already be drawn due to defer_draw.
8063 None
8064 }
8065
8066 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8067 px(30.)
8068 }
8069
8070 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8071 if self.read_only(cx) {
8072 cx.theme().players().read_only()
8073 } else {
8074 self.style.as_ref().unwrap().local_player
8075 }
8076 }
8077
8078 fn render_edit_prediction_accept_keybind(
8079 &self,
8080 window: &mut Window,
8081 cx: &App,
8082 ) -> Option<AnyElement> {
8083 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8084 let accept_keystroke = accept_binding.keystroke()?;
8085
8086 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8087
8088 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8089 Color::Accent
8090 } else {
8091 Color::Muted
8092 };
8093
8094 h_flex()
8095 .px_0p5()
8096 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8097 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8098 .text_size(TextSize::XSmall.rems(cx))
8099 .child(h_flex().children(ui::render_modifiers(
8100 &accept_keystroke.modifiers,
8101 PlatformStyle::platform(),
8102 Some(modifiers_color),
8103 Some(IconSize::XSmall.rems().into()),
8104 true,
8105 )))
8106 .when(is_platform_style_mac, |parent| {
8107 parent.child(accept_keystroke.key.clone())
8108 })
8109 .when(!is_platform_style_mac, |parent| {
8110 parent.child(
8111 Key::new(
8112 util::capitalize(&accept_keystroke.key),
8113 Some(Color::Default),
8114 )
8115 .size(Some(IconSize::XSmall.rems().into())),
8116 )
8117 })
8118 .into_any()
8119 .into()
8120 }
8121
8122 fn render_edit_prediction_line_popover(
8123 &self,
8124 label: impl Into<SharedString>,
8125 icon: Option<IconName>,
8126 window: &mut Window,
8127 cx: &App,
8128 ) -> Option<Stateful<Div>> {
8129 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8130
8131 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8132 let has_keybind = keybind.is_some();
8133
8134 let result = h_flex()
8135 .id("ep-line-popover")
8136 .py_0p5()
8137 .pl_1()
8138 .pr(padding_right)
8139 .gap_1()
8140 .rounded_md()
8141 .border_1()
8142 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8143 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8144 .shadow_sm()
8145 .when(!has_keybind, |el| {
8146 let status_colors = cx.theme().status();
8147
8148 el.bg(status_colors.error_background)
8149 .border_color(status_colors.error.opacity(0.6))
8150 .pl_2()
8151 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8152 .cursor_default()
8153 .hoverable_tooltip(move |_window, cx| {
8154 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8155 })
8156 })
8157 .children(keybind)
8158 .child(
8159 Label::new(label)
8160 .size(LabelSize::Small)
8161 .when(!has_keybind, |el| {
8162 el.color(cx.theme().status().error.into()).strikethrough()
8163 }),
8164 )
8165 .when(!has_keybind, |el| {
8166 el.child(
8167 h_flex().ml_1().child(
8168 Icon::new(IconName::Info)
8169 .size(IconSize::Small)
8170 .color(cx.theme().status().error.into()),
8171 ),
8172 )
8173 })
8174 .when_some(icon, |element, icon| {
8175 element.child(
8176 div()
8177 .mt(px(1.5))
8178 .child(Icon::new(icon).size(IconSize::Small)),
8179 )
8180 });
8181
8182 Some(result)
8183 }
8184
8185 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8186 let accent_color = cx.theme().colors().text_accent;
8187 let editor_bg_color = cx.theme().colors().editor_background;
8188 editor_bg_color.blend(accent_color.opacity(0.1))
8189 }
8190
8191 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8192 let accent_color = cx.theme().colors().text_accent;
8193 let editor_bg_color = cx.theme().colors().editor_background;
8194 editor_bg_color.blend(accent_color.opacity(0.6))
8195 }
8196
8197 fn render_edit_prediction_cursor_popover(
8198 &self,
8199 min_width: Pixels,
8200 max_width: Pixels,
8201 cursor_point: Point,
8202 style: &EditorStyle,
8203 accept_keystroke: Option<&gpui::Keystroke>,
8204 _window: &Window,
8205 cx: &mut Context<Editor>,
8206 ) -> Option<AnyElement> {
8207 let provider = self.edit_prediction_provider.as_ref()?;
8208
8209 if provider.provider.needs_terms_acceptance(cx) {
8210 return Some(
8211 h_flex()
8212 .min_w(min_width)
8213 .flex_1()
8214 .px_2()
8215 .py_1()
8216 .gap_3()
8217 .elevation_2(cx)
8218 .hover(|style| style.bg(cx.theme().colors().element_hover))
8219 .id("accept-terms")
8220 .cursor_pointer()
8221 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8222 .on_click(cx.listener(|this, _event, window, cx| {
8223 cx.stop_propagation();
8224 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8225 window.dispatch_action(
8226 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8227 cx,
8228 );
8229 }))
8230 .child(
8231 h_flex()
8232 .flex_1()
8233 .gap_2()
8234 .child(Icon::new(IconName::ZedPredict))
8235 .child(Label::new("Accept Terms of Service"))
8236 .child(div().w_full())
8237 .child(
8238 Icon::new(IconName::ArrowUpRight)
8239 .color(Color::Muted)
8240 .size(IconSize::Small),
8241 )
8242 .into_any_element(),
8243 )
8244 .into_any(),
8245 );
8246 }
8247
8248 let is_refreshing = provider.provider.is_refreshing(cx);
8249
8250 fn pending_completion_container() -> Div {
8251 h_flex()
8252 .h_full()
8253 .flex_1()
8254 .gap_2()
8255 .child(Icon::new(IconName::ZedPredict))
8256 }
8257
8258 let completion = match &self.active_inline_completion {
8259 Some(prediction) => {
8260 if !self.has_visible_completions_menu() {
8261 const RADIUS: Pixels = px(6.);
8262 const BORDER_WIDTH: Pixels = px(1.);
8263
8264 return Some(
8265 h_flex()
8266 .elevation_2(cx)
8267 .border(BORDER_WIDTH)
8268 .border_color(cx.theme().colors().border)
8269 .when(accept_keystroke.is_none(), |el| {
8270 el.border_color(cx.theme().status().error)
8271 })
8272 .rounded(RADIUS)
8273 .rounded_tl(px(0.))
8274 .overflow_hidden()
8275 .child(div().px_1p5().child(match &prediction.completion {
8276 InlineCompletion::Move { target, snapshot } => {
8277 use text::ToPoint as _;
8278 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8279 {
8280 Icon::new(IconName::ZedPredictDown)
8281 } else {
8282 Icon::new(IconName::ZedPredictUp)
8283 }
8284 }
8285 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8286 }))
8287 .child(
8288 h_flex()
8289 .gap_1()
8290 .py_1()
8291 .px_2()
8292 .rounded_r(RADIUS - BORDER_WIDTH)
8293 .border_l_1()
8294 .border_color(cx.theme().colors().border)
8295 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8296 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8297 el.child(
8298 Label::new("Hold")
8299 .size(LabelSize::Small)
8300 .when(accept_keystroke.is_none(), |el| {
8301 el.strikethrough()
8302 })
8303 .line_height_style(LineHeightStyle::UiLabel),
8304 )
8305 })
8306 .id("edit_prediction_cursor_popover_keybind")
8307 .when(accept_keystroke.is_none(), |el| {
8308 let status_colors = cx.theme().status();
8309
8310 el.bg(status_colors.error_background)
8311 .border_color(status_colors.error.opacity(0.6))
8312 .child(Icon::new(IconName::Info).color(Color::Error))
8313 .cursor_default()
8314 .hoverable_tooltip(move |_window, cx| {
8315 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8316 .into()
8317 })
8318 })
8319 .when_some(
8320 accept_keystroke.as_ref(),
8321 |el, accept_keystroke| {
8322 el.child(h_flex().children(ui::render_modifiers(
8323 &accept_keystroke.modifiers,
8324 PlatformStyle::platform(),
8325 Some(Color::Default),
8326 Some(IconSize::XSmall.rems().into()),
8327 false,
8328 )))
8329 },
8330 ),
8331 )
8332 .into_any(),
8333 );
8334 }
8335
8336 self.render_edit_prediction_cursor_popover_preview(
8337 prediction,
8338 cursor_point,
8339 style,
8340 cx,
8341 )?
8342 }
8343
8344 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8345 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8346 stale_completion,
8347 cursor_point,
8348 style,
8349 cx,
8350 )?,
8351
8352 None => {
8353 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8354 }
8355 },
8356
8357 None => pending_completion_container().child(Label::new("No Prediction")),
8358 };
8359
8360 let completion = if is_refreshing {
8361 completion
8362 .with_animation(
8363 "loading-completion",
8364 Animation::new(Duration::from_secs(2))
8365 .repeat()
8366 .with_easing(pulsating_between(0.4, 0.8)),
8367 |label, delta| label.opacity(delta),
8368 )
8369 .into_any_element()
8370 } else {
8371 completion.into_any_element()
8372 };
8373
8374 let has_completion = self.active_inline_completion.is_some();
8375
8376 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8377 Some(
8378 h_flex()
8379 .min_w(min_width)
8380 .max_w(max_width)
8381 .flex_1()
8382 .elevation_2(cx)
8383 .border_color(cx.theme().colors().border)
8384 .child(
8385 div()
8386 .flex_1()
8387 .py_1()
8388 .px_2()
8389 .overflow_hidden()
8390 .child(completion),
8391 )
8392 .when_some(accept_keystroke, |el, accept_keystroke| {
8393 if !accept_keystroke.modifiers.modified() {
8394 return el;
8395 }
8396
8397 el.child(
8398 h_flex()
8399 .h_full()
8400 .border_l_1()
8401 .rounded_r_lg()
8402 .border_color(cx.theme().colors().border)
8403 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8404 .gap_1()
8405 .py_1()
8406 .px_2()
8407 .child(
8408 h_flex()
8409 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8410 .when(is_platform_style_mac, |parent| parent.gap_1())
8411 .child(h_flex().children(ui::render_modifiers(
8412 &accept_keystroke.modifiers,
8413 PlatformStyle::platform(),
8414 Some(if !has_completion {
8415 Color::Muted
8416 } else {
8417 Color::Default
8418 }),
8419 None,
8420 false,
8421 ))),
8422 )
8423 .child(Label::new("Preview").into_any_element())
8424 .opacity(if has_completion { 1.0 } else { 0.4 }),
8425 )
8426 })
8427 .into_any(),
8428 )
8429 }
8430
8431 fn render_edit_prediction_cursor_popover_preview(
8432 &self,
8433 completion: &InlineCompletionState,
8434 cursor_point: Point,
8435 style: &EditorStyle,
8436 cx: &mut Context<Editor>,
8437 ) -> Option<Div> {
8438 use text::ToPoint as _;
8439
8440 fn render_relative_row_jump(
8441 prefix: impl Into<String>,
8442 current_row: u32,
8443 target_row: u32,
8444 ) -> Div {
8445 let (row_diff, arrow) = if target_row < current_row {
8446 (current_row - target_row, IconName::ArrowUp)
8447 } else {
8448 (target_row - current_row, IconName::ArrowDown)
8449 };
8450
8451 h_flex()
8452 .child(
8453 Label::new(format!("{}{}", prefix.into(), row_diff))
8454 .color(Color::Muted)
8455 .size(LabelSize::Small),
8456 )
8457 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8458 }
8459
8460 match &completion.completion {
8461 InlineCompletion::Move {
8462 target, snapshot, ..
8463 } => Some(
8464 h_flex()
8465 .px_2()
8466 .gap_2()
8467 .flex_1()
8468 .child(
8469 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8470 Icon::new(IconName::ZedPredictDown)
8471 } else {
8472 Icon::new(IconName::ZedPredictUp)
8473 },
8474 )
8475 .child(Label::new("Jump to Edit")),
8476 ),
8477
8478 InlineCompletion::Edit {
8479 edits,
8480 edit_preview,
8481 snapshot,
8482 display_mode: _,
8483 } => {
8484 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8485
8486 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8487 &snapshot,
8488 &edits,
8489 edit_preview.as_ref()?,
8490 true,
8491 cx,
8492 )
8493 .first_line_preview();
8494
8495 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8496 .with_default_highlights(&style.text, highlighted_edits.highlights);
8497
8498 let preview = h_flex()
8499 .gap_1()
8500 .min_w_16()
8501 .child(styled_text)
8502 .when(has_more_lines, |parent| parent.child("…"));
8503
8504 let left = if first_edit_row != cursor_point.row {
8505 render_relative_row_jump("", cursor_point.row, first_edit_row)
8506 .into_any_element()
8507 } else {
8508 Icon::new(IconName::ZedPredict).into_any_element()
8509 };
8510
8511 Some(
8512 h_flex()
8513 .h_full()
8514 .flex_1()
8515 .gap_2()
8516 .pr_1()
8517 .overflow_x_hidden()
8518 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8519 .child(left)
8520 .child(preview),
8521 )
8522 }
8523 }
8524 }
8525
8526 fn render_context_menu(
8527 &self,
8528 style: &EditorStyle,
8529 max_height_in_lines: u32,
8530 window: &mut Window,
8531 cx: &mut Context<Editor>,
8532 ) -> Option<AnyElement> {
8533 let menu = self.context_menu.borrow();
8534 let menu = menu.as_ref()?;
8535 if !menu.visible() {
8536 return None;
8537 };
8538 Some(menu.render(style, max_height_in_lines, window, cx))
8539 }
8540
8541 fn render_context_menu_aside(
8542 &mut self,
8543 max_size: Size<Pixels>,
8544 window: &mut Window,
8545 cx: &mut Context<Editor>,
8546 ) -> Option<AnyElement> {
8547 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8548 if menu.visible() {
8549 menu.render_aside(self, max_size, window, cx)
8550 } else {
8551 None
8552 }
8553 })
8554 }
8555
8556 fn hide_context_menu(
8557 &mut self,
8558 window: &mut Window,
8559 cx: &mut Context<Self>,
8560 ) -> Option<CodeContextMenu> {
8561 cx.notify();
8562 self.completion_tasks.clear();
8563 let context_menu = self.context_menu.borrow_mut().take();
8564 self.stale_inline_completion_in_menu.take();
8565 self.update_visible_inline_completion(window, cx);
8566 context_menu
8567 }
8568
8569 fn show_snippet_choices(
8570 &mut self,
8571 choices: &Vec<String>,
8572 selection: Range<Anchor>,
8573 cx: &mut Context<Self>,
8574 ) {
8575 if selection.start.buffer_id.is_none() {
8576 return;
8577 }
8578 let buffer_id = selection.start.buffer_id.unwrap();
8579 let buffer = self.buffer().read(cx).buffer(buffer_id);
8580 let id = post_inc(&mut self.next_completion_id);
8581 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8582
8583 if let Some(buffer) = buffer {
8584 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8585 CompletionsMenu::new_snippet_choices(
8586 id,
8587 true,
8588 choices,
8589 selection,
8590 buffer,
8591 snippet_sort_order,
8592 ),
8593 ));
8594 }
8595 }
8596
8597 pub fn insert_snippet(
8598 &mut self,
8599 insertion_ranges: &[Range<usize>],
8600 snippet: Snippet,
8601 window: &mut Window,
8602 cx: &mut Context<Self>,
8603 ) -> Result<()> {
8604 struct Tabstop<T> {
8605 is_end_tabstop: bool,
8606 ranges: Vec<Range<T>>,
8607 choices: Option<Vec<String>>,
8608 }
8609
8610 let tabstops = self.buffer.update(cx, |buffer, cx| {
8611 let snippet_text: Arc<str> = snippet.text.clone().into();
8612 let edits = insertion_ranges
8613 .iter()
8614 .cloned()
8615 .map(|range| (range, snippet_text.clone()));
8616 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8617
8618 let snapshot = &*buffer.read(cx);
8619 let snippet = &snippet;
8620 snippet
8621 .tabstops
8622 .iter()
8623 .map(|tabstop| {
8624 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8625 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8626 });
8627 let mut tabstop_ranges = tabstop
8628 .ranges
8629 .iter()
8630 .flat_map(|tabstop_range| {
8631 let mut delta = 0_isize;
8632 insertion_ranges.iter().map(move |insertion_range| {
8633 let insertion_start = insertion_range.start as isize + delta;
8634 delta +=
8635 snippet.text.len() as isize - insertion_range.len() as isize;
8636
8637 let start = ((insertion_start + tabstop_range.start) as usize)
8638 .min(snapshot.len());
8639 let end = ((insertion_start + tabstop_range.end) as usize)
8640 .min(snapshot.len());
8641 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8642 })
8643 })
8644 .collect::<Vec<_>>();
8645 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8646
8647 Tabstop {
8648 is_end_tabstop,
8649 ranges: tabstop_ranges,
8650 choices: tabstop.choices.clone(),
8651 }
8652 })
8653 .collect::<Vec<_>>()
8654 });
8655 if let Some(tabstop) = tabstops.first() {
8656 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8657 s.select_ranges(tabstop.ranges.iter().cloned());
8658 });
8659
8660 if let Some(choices) = &tabstop.choices {
8661 if let Some(selection) = tabstop.ranges.first() {
8662 self.show_snippet_choices(choices, selection.clone(), cx)
8663 }
8664 }
8665
8666 // If we're already at the last tabstop and it's at the end of the snippet,
8667 // we're done, we don't need to keep the state around.
8668 if !tabstop.is_end_tabstop {
8669 let choices = tabstops
8670 .iter()
8671 .map(|tabstop| tabstop.choices.clone())
8672 .collect();
8673
8674 let ranges = tabstops
8675 .into_iter()
8676 .map(|tabstop| tabstop.ranges)
8677 .collect::<Vec<_>>();
8678
8679 self.snippet_stack.push(SnippetState {
8680 active_index: 0,
8681 ranges,
8682 choices,
8683 });
8684 }
8685
8686 // Check whether the just-entered snippet ends with an auto-closable bracket.
8687 if self.autoclose_regions.is_empty() {
8688 let snapshot = self.buffer.read(cx).snapshot(cx);
8689 for selection in &mut self.selections.all::<Point>(cx) {
8690 let selection_head = selection.head();
8691 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8692 continue;
8693 };
8694
8695 let mut bracket_pair = None;
8696 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8697 let prev_chars = snapshot
8698 .reversed_chars_at(selection_head)
8699 .collect::<String>();
8700 for (pair, enabled) in scope.brackets() {
8701 if enabled
8702 && pair.close
8703 && prev_chars.starts_with(pair.start.as_str())
8704 && next_chars.starts_with(pair.end.as_str())
8705 {
8706 bracket_pair = Some(pair.clone());
8707 break;
8708 }
8709 }
8710 if let Some(pair) = bracket_pair {
8711 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8712 let autoclose_enabled =
8713 self.use_autoclose && snapshot_settings.use_autoclose;
8714 if autoclose_enabled {
8715 let start = snapshot.anchor_after(selection_head);
8716 let end = snapshot.anchor_after(selection_head);
8717 self.autoclose_regions.push(AutocloseRegion {
8718 selection_id: selection.id,
8719 range: start..end,
8720 pair,
8721 });
8722 }
8723 }
8724 }
8725 }
8726 }
8727 Ok(())
8728 }
8729
8730 pub fn move_to_next_snippet_tabstop(
8731 &mut self,
8732 window: &mut Window,
8733 cx: &mut Context<Self>,
8734 ) -> bool {
8735 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8736 }
8737
8738 pub fn move_to_prev_snippet_tabstop(
8739 &mut self,
8740 window: &mut Window,
8741 cx: &mut Context<Self>,
8742 ) -> bool {
8743 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8744 }
8745
8746 pub fn move_to_snippet_tabstop(
8747 &mut self,
8748 bias: Bias,
8749 window: &mut Window,
8750 cx: &mut Context<Self>,
8751 ) -> bool {
8752 if let Some(mut snippet) = self.snippet_stack.pop() {
8753 match bias {
8754 Bias::Left => {
8755 if snippet.active_index > 0 {
8756 snippet.active_index -= 1;
8757 } else {
8758 self.snippet_stack.push(snippet);
8759 return false;
8760 }
8761 }
8762 Bias::Right => {
8763 if snippet.active_index + 1 < snippet.ranges.len() {
8764 snippet.active_index += 1;
8765 } else {
8766 self.snippet_stack.push(snippet);
8767 return false;
8768 }
8769 }
8770 }
8771 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8772 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8773 s.select_anchor_ranges(current_ranges.iter().cloned())
8774 });
8775
8776 if let Some(choices) = &snippet.choices[snippet.active_index] {
8777 if let Some(selection) = current_ranges.first() {
8778 self.show_snippet_choices(&choices, selection.clone(), cx);
8779 }
8780 }
8781
8782 // If snippet state is not at the last tabstop, push it back on the stack
8783 if snippet.active_index + 1 < snippet.ranges.len() {
8784 self.snippet_stack.push(snippet);
8785 }
8786 return true;
8787 }
8788 }
8789
8790 false
8791 }
8792
8793 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8794 self.transact(window, cx, |this, window, cx| {
8795 this.select_all(&SelectAll, window, cx);
8796 this.insert("", window, cx);
8797 });
8798 }
8799
8800 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8801 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8802 self.transact(window, cx, |this, window, cx| {
8803 this.select_autoclose_pair(window, cx);
8804 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8805 if !this.linked_edit_ranges.is_empty() {
8806 let selections = this.selections.all::<MultiBufferPoint>(cx);
8807 let snapshot = this.buffer.read(cx).snapshot(cx);
8808
8809 for selection in selections.iter() {
8810 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8811 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8812 if selection_start.buffer_id != selection_end.buffer_id {
8813 continue;
8814 }
8815 if let Some(ranges) =
8816 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8817 {
8818 for (buffer, entries) in ranges {
8819 linked_ranges.entry(buffer).or_default().extend(entries);
8820 }
8821 }
8822 }
8823 }
8824
8825 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8826 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8827 for selection in &mut selections {
8828 if selection.is_empty() {
8829 let old_head = selection.head();
8830 let mut new_head =
8831 movement::left(&display_map, old_head.to_display_point(&display_map))
8832 .to_point(&display_map);
8833 if let Some((buffer, line_buffer_range)) = display_map
8834 .buffer_snapshot
8835 .buffer_line_for_row(MultiBufferRow(old_head.row))
8836 {
8837 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8838 let indent_len = match indent_size.kind {
8839 IndentKind::Space => {
8840 buffer.settings_at(line_buffer_range.start, cx).tab_size
8841 }
8842 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8843 };
8844 if old_head.column <= indent_size.len && old_head.column > 0 {
8845 let indent_len = indent_len.get();
8846 new_head = cmp::min(
8847 new_head,
8848 MultiBufferPoint::new(
8849 old_head.row,
8850 ((old_head.column - 1) / indent_len) * indent_len,
8851 ),
8852 );
8853 }
8854 }
8855
8856 selection.set_head(new_head, SelectionGoal::None);
8857 }
8858 }
8859
8860 this.signature_help_state.set_backspace_pressed(true);
8861 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8862 s.select(selections)
8863 });
8864 this.insert("", window, cx);
8865 let empty_str: Arc<str> = Arc::from("");
8866 for (buffer, edits) in linked_ranges {
8867 let snapshot = buffer.read(cx).snapshot();
8868 use text::ToPoint as TP;
8869
8870 let edits = edits
8871 .into_iter()
8872 .map(|range| {
8873 let end_point = TP::to_point(&range.end, &snapshot);
8874 let mut start_point = TP::to_point(&range.start, &snapshot);
8875
8876 if end_point == start_point {
8877 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8878 .saturating_sub(1);
8879 start_point =
8880 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8881 };
8882
8883 (start_point..end_point, empty_str.clone())
8884 })
8885 .sorted_by_key(|(range, _)| range.start)
8886 .collect::<Vec<_>>();
8887 buffer.update(cx, |this, cx| {
8888 this.edit(edits, None, cx);
8889 })
8890 }
8891 this.refresh_inline_completion(true, false, window, cx);
8892 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8893 });
8894 }
8895
8896 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8897 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8898 self.transact(window, cx, |this, window, cx| {
8899 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8900 s.move_with(|map, selection| {
8901 if selection.is_empty() {
8902 let cursor = movement::right(map, selection.head());
8903 selection.end = cursor;
8904 selection.reversed = true;
8905 selection.goal = SelectionGoal::None;
8906 }
8907 })
8908 });
8909 this.insert("", window, cx);
8910 this.refresh_inline_completion(true, false, window, cx);
8911 });
8912 }
8913
8914 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8916 if self.move_to_prev_snippet_tabstop(window, cx) {
8917 return;
8918 }
8919 self.outdent(&Outdent, window, cx);
8920 }
8921
8922 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8923 if self.move_to_next_snippet_tabstop(window, cx) {
8924 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8925 return;
8926 }
8927 if self.read_only(cx) {
8928 return;
8929 }
8930 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8931 let mut selections = self.selections.all_adjusted(cx);
8932 let buffer = self.buffer.read(cx);
8933 let snapshot = buffer.snapshot(cx);
8934 let rows_iter = selections.iter().map(|s| s.head().row);
8935 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8936
8937 let has_some_cursor_in_whitespace = selections
8938 .iter()
8939 .filter(|selection| selection.is_empty())
8940 .any(|selection| {
8941 let cursor = selection.head();
8942 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8943 cursor.column < current_indent.len
8944 });
8945
8946 let mut edits = Vec::new();
8947 let mut prev_edited_row = 0;
8948 let mut row_delta = 0;
8949 for selection in &mut selections {
8950 if selection.start.row != prev_edited_row {
8951 row_delta = 0;
8952 }
8953 prev_edited_row = selection.end.row;
8954
8955 // If the selection is non-empty, then increase the indentation of the selected lines.
8956 if !selection.is_empty() {
8957 row_delta =
8958 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8959 continue;
8960 }
8961
8962 let cursor = selection.head();
8963 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8964 if let Some(suggested_indent) =
8965 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8966 {
8967 // Don't do anything if already at suggested indent
8968 // and there is any other cursor which is not
8969 if has_some_cursor_in_whitespace
8970 && cursor.column == current_indent.len
8971 && current_indent.len == suggested_indent.len
8972 {
8973 continue;
8974 }
8975
8976 // Adjust line and move cursor to suggested indent
8977 // if cursor is not at suggested indent
8978 if cursor.column < suggested_indent.len
8979 && cursor.column <= current_indent.len
8980 && current_indent.len <= suggested_indent.len
8981 {
8982 selection.start = Point::new(cursor.row, suggested_indent.len);
8983 selection.end = selection.start;
8984 if row_delta == 0 {
8985 edits.extend(Buffer::edit_for_indent_size_adjustment(
8986 cursor.row,
8987 current_indent,
8988 suggested_indent,
8989 ));
8990 row_delta = suggested_indent.len - current_indent.len;
8991 }
8992 continue;
8993 }
8994
8995 // If current indent is more than suggested indent
8996 // only move cursor to current indent and skip indent
8997 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8998 selection.start = Point::new(cursor.row, current_indent.len);
8999 selection.end = selection.start;
9000 continue;
9001 }
9002 }
9003
9004 // Otherwise, insert a hard or soft tab.
9005 let settings = buffer.language_settings_at(cursor, cx);
9006 let tab_size = if settings.hard_tabs {
9007 IndentSize::tab()
9008 } else {
9009 let tab_size = settings.tab_size.get();
9010 let indent_remainder = snapshot
9011 .text_for_range(Point::new(cursor.row, 0)..cursor)
9012 .flat_map(str::chars)
9013 .fold(row_delta % tab_size, |counter: u32, c| {
9014 if c == '\t' {
9015 0
9016 } else {
9017 (counter + 1) % tab_size
9018 }
9019 });
9020
9021 let chars_to_next_tab_stop = tab_size - indent_remainder;
9022 IndentSize::spaces(chars_to_next_tab_stop)
9023 };
9024 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9025 selection.end = selection.start;
9026 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9027 row_delta += tab_size.len;
9028 }
9029
9030 self.transact(window, cx, |this, window, cx| {
9031 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9032 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9033 s.select(selections)
9034 });
9035 this.refresh_inline_completion(true, false, window, cx);
9036 });
9037 }
9038
9039 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9040 if self.read_only(cx) {
9041 return;
9042 }
9043 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9044 let mut selections = self.selections.all::<Point>(cx);
9045 let mut prev_edited_row = 0;
9046 let mut row_delta = 0;
9047 let mut edits = Vec::new();
9048 let buffer = self.buffer.read(cx);
9049 let snapshot = buffer.snapshot(cx);
9050 for selection in &mut selections {
9051 if selection.start.row != prev_edited_row {
9052 row_delta = 0;
9053 }
9054 prev_edited_row = selection.end.row;
9055
9056 row_delta =
9057 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9058 }
9059
9060 self.transact(window, cx, |this, window, cx| {
9061 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9062 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9063 s.select(selections)
9064 });
9065 });
9066 }
9067
9068 fn indent_selection(
9069 buffer: &MultiBuffer,
9070 snapshot: &MultiBufferSnapshot,
9071 selection: &mut Selection<Point>,
9072 edits: &mut Vec<(Range<Point>, String)>,
9073 delta_for_start_row: u32,
9074 cx: &App,
9075 ) -> u32 {
9076 let settings = buffer.language_settings_at(selection.start, cx);
9077 let tab_size = settings.tab_size.get();
9078 let indent_kind = if settings.hard_tabs {
9079 IndentKind::Tab
9080 } else {
9081 IndentKind::Space
9082 };
9083 let mut start_row = selection.start.row;
9084 let mut end_row = selection.end.row + 1;
9085
9086 // If a selection ends at the beginning of a line, don't indent
9087 // that last line.
9088 if selection.end.column == 0 && selection.end.row > selection.start.row {
9089 end_row -= 1;
9090 }
9091
9092 // Avoid re-indenting a row that has already been indented by a
9093 // previous selection, but still update this selection's column
9094 // to reflect that indentation.
9095 if delta_for_start_row > 0 {
9096 start_row += 1;
9097 selection.start.column += delta_for_start_row;
9098 if selection.end.row == selection.start.row {
9099 selection.end.column += delta_for_start_row;
9100 }
9101 }
9102
9103 let mut delta_for_end_row = 0;
9104 let has_multiple_rows = start_row + 1 != end_row;
9105 for row in start_row..end_row {
9106 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9107 let indent_delta = match (current_indent.kind, indent_kind) {
9108 (IndentKind::Space, IndentKind::Space) => {
9109 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9110 IndentSize::spaces(columns_to_next_tab_stop)
9111 }
9112 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9113 (_, IndentKind::Tab) => IndentSize::tab(),
9114 };
9115
9116 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9117 0
9118 } else {
9119 selection.start.column
9120 };
9121 let row_start = Point::new(row, start);
9122 edits.push((
9123 row_start..row_start,
9124 indent_delta.chars().collect::<String>(),
9125 ));
9126
9127 // Update this selection's endpoints to reflect the indentation.
9128 if row == selection.start.row {
9129 selection.start.column += indent_delta.len;
9130 }
9131 if row == selection.end.row {
9132 selection.end.column += indent_delta.len;
9133 delta_for_end_row = indent_delta.len;
9134 }
9135 }
9136
9137 if selection.start.row == selection.end.row {
9138 delta_for_start_row + delta_for_end_row
9139 } else {
9140 delta_for_end_row
9141 }
9142 }
9143
9144 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9145 if self.read_only(cx) {
9146 return;
9147 }
9148 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9150 let selections = self.selections.all::<Point>(cx);
9151 let mut deletion_ranges = Vec::new();
9152 let mut last_outdent = None;
9153 {
9154 let buffer = self.buffer.read(cx);
9155 let snapshot = buffer.snapshot(cx);
9156 for selection in &selections {
9157 let settings = buffer.language_settings_at(selection.start, cx);
9158 let tab_size = settings.tab_size.get();
9159 let mut rows = selection.spanned_rows(false, &display_map);
9160
9161 // Avoid re-outdenting a row that has already been outdented by a
9162 // previous selection.
9163 if let Some(last_row) = last_outdent {
9164 if last_row == rows.start {
9165 rows.start = rows.start.next_row();
9166 }
9167 }
9168 let has_multiple_rows = rows.len() > 1;
9169 for row in rows.iter_rows() {
9170 let indent_size = snapshot.indent_size_for_line(row);
9171 if indent_size.len > 0 {
9172 let deletion_len = match indent_size.kind {
9173 IndentKind::Space => {
9174 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9175 if columns_to_prev_tab_stop == 0 {
9176 tab_size
9177 } else {
9178 columns_to_prev_tab_stop
9179 }
9180 }
9181 IndentKind::Tab => 1,
9182 };
9183 let start = if has_multiple_rows
9184 || deletion_len > selection.start.column
9185 || indent_size.len < selection.start.column
9186 {
9187 0
9188 } else {
9189 selection.start.column - deletion_len
9190 };
9191 deletion_ranges.push(
9192 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9193 );
9194 last_outdent = Some(row);
9195 }
9196 }
9197 }
9198 }
9199
9200 self.transact(window, cx, |this, window, cx| {
9201 this.buffer.update(cx, |buffer, cx| {
9202 let empty_str: Arc<str> = Arc::default();
9203 buffer.edit(
9204 deletion_ranges
9205 .into_iter()
9206 .map(|range| (range, empty_str.clone())),
9207 None,
9208 cx,
9209 );
9210 });
9211 let selections = this.selections.all::<usize>(cx);
9212 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9213 s.select(selections)
9214 });
9215 });
9216 }
9217
9218 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9219 if self.read_only(cx) {
9220 return;
9221 }
9222 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9223 let selections = self
9224 .selections
9225 .all::<usize>(cx)
9226 .into_iter()
9227 .map(|s| s.range());
9228
9229 self.transact(window, cx, |this, window, cx| {
9230 this.buffer.update(cx, |buffer, cx| {
9231 buffer.autoindent_ranges(selections, cx);
9232 });
9233 let selections = this.selections.all::<usize>(cx);
9234 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9235 s.select(selections)
9236 });
9237 });
9238 }
9239
9240 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9241 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9243 let selections = self.selections.all::<Point>(cx);
9244
9245 let mut new_cursors = Vec::new();
9246 let mut edit_ranges = Vec::new();
9247 let mut selections = selections.iter().peekable();
9248 while let Some(selection) = selections.next() {
9249 let mut rows = selection.spanned_rows(false, &display_map);
9250 let goal_display_column = selection.head().to_display_point(&display_map).column();
9251
9252 // Accumulate contiguous regions of rows that we want to delete.
9253 while let Some(next_selection) = selections.peek() {
9254 let next_rows = next_selection.spanned_rows(false, &display_map);
9255 if next_rows.start <= rows.end {
9256 rows.end = next_rows.end;
9257 selections.next().unwrap();
9258 } else {
9259 break;
9260 }
9261 }
9262
9263 let buffer = &display_map.buffer_snapshot;
9264 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9265 let edit_end;
9266 let cursor_buffer_row;
9267 if buffer.max_point().row >= rows.end.0 {
9268 // If there's a line after the range, delete the \n from the end of the row range
9269 // and position the cursor on the next line.
9270 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9271 cursor_buffer_row = rows.end;
9272 } else {
9273 // If there isn't a line after the range, delete the \n from the line before the
9274 // start of the row range and position the cursor there.
9275 edit_start = edit_start.saturating_sub(1);
9276 edit_end = buffer.len();
9277 cursor_buffer_row = rows.start.previous_row();
9278 }
9279
9280 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9281 *cursor.column_mut() =
9282 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9283
9284 new_cursors.push((
9285 selection.id,
9286 buffer.anchor_after(cursor.to_point(&display_map)),
9287 ));
9288 edit_ranges.push(edit_start..edit_end);
9289 }
9290
9291 self.transact(window, cx, |this, window, cx| {
9292 let buffer = this.buffer.update(cx, |buffer, cx| {
9293 let empty_str: Arc<str> = Arc::default();
9294 buffer.edit(
9295 edit_ranges
9296 .into_iter()
9297 .map(|range| (range, empty_str.clone())),
9298 None,
9299 cx,
9300 );
9301 buffer.snapshot(cx)
9302 });
9303 let new_selections = new_cursors
9304 .into_iter()
9305 .map(|(id, cursor)| {
9306 let cursor = cursor.to_point(&buffer);
9307 Selection {
9308 id,
9309 start: cursor,
9310 end: cursor,
9311 reversed: false,
9312 goal: SelectionGoal::None,
9313 }
9314 })
9315 .collect();
9316
9317 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9318 s.select(new_selections);
9319 });
9320 });
9321 }
9322
9323 pub fn join_lines_impl(
9324 &mut self,
9325 insert_whitespace: bool,
9326 window: &mut Window,
9327 cx: &mut Context<Self>,
9328 ) {
9329 if self.read_only(cx) {
9330 return;
9331 }
9332 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9333 for selection in self.selections.all::<Point>(cx) {
9334 let start = MultiBufferRow(selection.start.row);
9335 // Treat single line selections as if they include the next line. Otherwise this action
9336 // would do nothing for single line selections individual cursors.
9337 let end = if selection.start.row == selection.end.row {
9338 MultiBufferRow(selection.start.row + 1)
9339 } else {
9340 MultiBufferRow(selection.end.row)
9341 };
9342
9343 if let Some(last_row_range) = row_ranges.last_mut() {
9344 if start <= last_row_range.end {
9345 last_row_range.end = end;
9346 continue;
9347 }
9348 }
9349 row_ranges.push(start..end);
9350 }
9351
9352 let snapshot = self.buffer.read(cx).snapshot(cx);
9353 let mut cursor_positions = Vec::new();
9354 for row_range in &row_ranges {
9355 let anchor = snapshot.anchor_before(Point::new(
9356 row_range.end.previous_row().0,
9357 snapshot.line_len(row_range.end.previous_row()),
9358 ));
9359 cursor_positions.push(anchor..anchor);
9360 }
9361
9362 self.transact(window, cx, |this, window, cx| {
9363 for row_range in row_ranges.into_iter().rev() {
9364 for row in row_range.iter_rows().rev() {
9365 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9366 let next_line_row = row.next_row();
9367 let indent = snapshot.indent_size_for_line(next_line_row);
9368 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9369
9370 let replace =
9371 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9372 " "
9373 } else {
9374 ""
9375 };
9376
9377 this.buffer.update(cx, |buffer, cx| {
9378 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9379 });
9380 }
9381 }
9382
9383 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9384 s.select_anchor_ranges(cursor_positions)
9385 });
9386 });
9387 }
9388
9389 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9391 self.join_lines_impl(true, window, cx);
9392 }
9393
9394 pub fn sort_lines_case_sensitive(
9395 &mut self,
9396 _: &SortLinesCaseSensitive,
9397 window: &mut Window,
9398 cx: &mut Context<Self>,
9399 ) {
9400 self.manipulate_lines(window, cx, |lines| lines.sort())
9401 }
9402
9403 pub fn sort_lines_case_insensitive(
9404 &mut self,
9405 _: &SortLinesCaseInsensitive,
9406 window: &mut Window,
9407 cx: &mut Context<Self>,
9408 ) {
9409 self.manipulate_lines(window, cx, |lines| {
9410 lines.sort_by_key(|line| line.to_lowercase())
9411 })
9412 }
9413
9414 pub fn unique_lines_case_insensitive(
9415 &mut self,
9416 _: &UniqueLinesCaseInsensitive,
9417 window: &mut Window,
9418 cx: &mut Context<Self>,
9419 ) {
9420 self.manipulate_lines(window, cx, |lines| {
9421 let mut seen = HashSet::default();
9422 lines.retain(|line| seen.insert(line.to_lowercase()));
9423 })
9424 }
9425
9426 pub fn unique_lines_case_sensitive(
9427 &mut self,
9428 _: &UniqueLinesCaseSensitive,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) {
9432 self.manipulate_lines(window, cx, |lines| {
9433 let mut seen = HashSet::default();
9434 lines.retain(|line| seen.insert(*line));
9435 })
9436 }
9437
9438 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9439 let Some(project) = self.project.clone() else {
9440 return;
9441 };
9442 self.reload(project, window, cx)
9443 .detach_and_notify_err(window, cx);
9444 }
9445
9446 pub fn restore_file(
9447 &mut self,
9448 _: &::git::RestoreFile,
9449 window: &mut Window,
9450 cx: &mut Context<Self>,
9451 ) {
9452 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9453 let mut buffer_ids = HashSet::default();
9454 let snapshot = self.buffer().read(cx).snapshot(cx);
9455 for selection in self.selections.all::<usize>(cx) {
9456 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9457 }
9458
9459 let buffer = self.buffer().read(cx);
9460 let ranges = buffer_ids
9461 .into_iter()
9462 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9463 .collect::<Vec<_>>();
9464
9465 self.restore_hunks_in_ranges(ranges, window, cx);
9466 }
9467
9468 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9469 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9470 let selections = self
9471 .selections
9472 .all(cx)
9473 .into_iter()
9474 .map(|s| s.range())
9475 .collect();
9476 self.restore_hunks_in_ranges(selections, window, cx);
9477 }
9478
9479 pub fn restore_hunks_in_ranges(
9480 &mut self,
9481 ranges: Vec<Range<Point>>,
9482 window: &mut Window,
9483 cx: &mut Context<Editor>,
9484 ) {
9485 let mut revert_changes = HashMap::default();
9486 let chunk_by = self
9487 .snapshot(window, cx)
9488 .hunks_for_ranges(ranges)
9489 .into_iter()
9490 .chunk_by(|hunk| hunk.buffer_id);
9491 for (buffer_id, hunks) in &chunk_by {
9492 let hunks = hunks.collect::<Vec<_>>();
9493 for hunk in &hunks {
9494 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9495 }
9496 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9497 }
9498 drop(chunk_by);
9499 if !revert_changes.is_empty() {
9500 self.transact(window, cx, |editor, window, cx| {
9501 editor.restore(revert_changes, window, cx);
9502 });
9503 }
9504 }
9505
9506 pub fn open_active_item_in_terminal(
9507 &mut self,
9508 _: &OpenInTerminal,
9509 window: &mut Window,
9510 cx: &mut Context<Self>,
9511 ) {
9512 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9513 let project_path = buffer.read(cx).project_path(cx)?;
9514 let project = self.project.as_ref()?.read(cx);
9515 let entry = project.entry_for_path(&project_path, cx)?;
9516 let parent = match &entry.canonical_path {
9517 Some(canonical_path) => canonical_path.to_path_buf(),
9518 None => project.absolute_path(&project_path, cx)?,
9519 }
9520 .parent()?
9521 .to_path_buf();
9522 Some(parent)
9523 }) {
9524 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9525 }
9526 }
9527
9528 fn set_breakpoint_context_menu(
9529 &mut self,
9530 display_row: DisplayRow,
9531 position: Option<Anchor>,
9532 clicked_point: gpui::Point<Pixels>,
9533 window: &mut Window,
9534 cx: &mut Context<Self>,
9535 ) {
9536 if !cx.has_flag::<DebuggerFeatureFlag>() {
9537 return;
9538 }
9539 let source = self
9540 .buffer
9541 .read(cx)
9542 .snapshot(cx)
9543 .anchor_before(Point::new(display_row.0, 0u32));
9544
9545 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9546
9547 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9548 self,
9549 source,
9550 clicked_point,
9551 context_menu,
9552 window,
9553 cx,
9554 );
9555 }
9556
9557 fn add_edit_breakpoint_block(
9558 &mut self,
9559 anchor: Anchor,
9560 breakpoint: &Breakpoint,
9561 edit_action: BreakpointPromptEditAction,
9562 window: &mut Window,
9563 cx: &mut Context<Self>,
9564 ) {
9565 let weak_editor = cx.weak_entity();
9566 let bp_prompt = cx.new(|cx| {
9567 BreakpointPromptEditor::new(
9568 weak_editor,
9569 anchor,
9570 breakpoint.clone(),
9571 edit_action,
9572 window,
9573 cx,
9574 )
9575 });
9576
9577 let height = bp_prompt.update(cx, |this, cx| {
9578 this.prompt
9579 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9580 });
9581 let cloned_prompt = bp_prompt.clone();
9582 let blocks = vec![BlockProperties {
9583 style: BlockStyle::Sticky,
9584 placement: BlockPlacement::Above(anchor),
9585 height: Some(height),
9586 render: Arc::new(move |cx| {
9587 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9588 cloned_prompt.clone().into_any_element()
9589 }),
9590 priority: 0,
9591 render_in_minimap: true,
9592 }];
9593
9594 let focus_handle = bp_prompt.focus_handle(cx);
9595 window.focus(&focus_handle);
9596
9597 let block_ids = self.insert_blocks(blocks, None, cx);
9598 bp_prompt.update(cx, |prompt, _| {
9599 prompt.add_block_ids(block_ids);
9600 });
9601 }
9602
9603 pub(crate) fn breakpoint_at_row(
9604 &self,
9605 row: u32,
9606 window: &mut Window,
9607 cx: &mut Context<Self>,
9608 ) -> Option<(Anchor, Breakpoint)> {
9609 let snapshot = self.snapshot(window, cx);
9610 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9611
9612 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9613 }
9614
9615 pub(crate) fn breakpoint_at_anchor(
9616 &self,
9617 breakpoint_position: Anchor,
9618 snapshot: &EditorSnapshot,
9619 cx: &mut Context<Self>,
9620 ) -> Option<(Anchor, Breakpoint)> {
9621 let project = self.project.clone()?;
9622
9623 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9624 snapshot
9625 .buffer_snapshot
9626 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9627 })?;
9628
9629 let enclosing_excerpt = breakpoint_position.excerpt_id;
9630 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9631 let buffer_snapshot = buffer.read(cx).snapshot();
9632
9633 let row = buffer_snapshot
9634 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9635 .row;
9636
9637 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9638 let anchor_end = snapshot
9639 .buffer_snapshot
9640 .anchor_after(Point::new(row, line_len));
9641
9642 let bp = self
9643 .breakpoint_store
9644 .as_ref()?
9645 .read_with(cx, |breakpoint_store, cx| {
9646 breakpoint_store
9647 .breakpoints(
9648 &buffer,
9649 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9650 &buffer_snapshot,
9651 cx,
9652 )
9653 .next()
9654 .and_then(|(bp, _)| {
9655 let breakpoint_row = buffer_snapshot
9656 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9657 .row;
9658
9659 if breakpoint_row == row {
9660 snapshot
9661 .buffer_snapshot
9662 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9663 .map(|position| (position, bp.bp.clone()))
9664 } else {
9665 None
9666 }
9667 })
9668 });
9669 bp
9670 }
9671
9672 pub fn edit_log_breakpoint(
9673 &mut self,
9674 _: &EditLogBreakpoint,
9675 window: &mut Window,
9676 cx: &mut Context<Self>,
9677 ) {
9678 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9679 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9680 message: None,
9681 state: BreakpointState::Enabled,
9682 condition: None,
9683 hit_condition: None,
9684 });
9685
9686 self.add_edit_breakpoint_block(
9687 anchor,
9688 &breakpoint,
9689 BreakpointPromptEditAction::Log,
9690 window,
9691 cx,
9692 );
9693 }
9694 }
9695
9696 fn breakpoints_at_cursors(
9697 &self,
9698 window: &mut Window,
9699 cx: &mut Context<Self>,
9700 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9701 let snapshot = self.snapshot(window, cx);
9702 let cursors = self
9703 .selections
9704 .disjoint_anchors()
9705 .into_iter()
9706 .map(|selection| {
9707 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9708
9709 let breakpoint_position = self
9710 .breakpoint_at_row(cursor_position.row, window, cx)
9711 .map(|bp| bp.0)
9712 .unwrap_or_else(|| {
9713 snapshot
9714 .display_snapshot
9715 .buffer_snapshot
9716 .anchor_after(Point::new(cursor_position.row, 0))
9717 });
9718
9719 let breakpoint = self
9720 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9721 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9722
9723 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9724 })
9725 // 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.
9726 .collect::<HashMap<Anchor, _>>();
9727
9728 cursors.into_iter().collect()
9729 }
9730
9731 pub fn enable_breakpoint(
9732 &mut self,
9733 _: &crate::actions::EnableBreakpoint,
9734 window: &mut Window,
9735 cx: &mut Context<Self>,
9736 ) {
9737 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9738 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9739 continue;
9740 };
9741 self.edit_breakpoint_at_anchor(
9742 anchor,
9743 breakpoint,
9744 BreakpointEditAction::InvertState,
9745 cx,
9746 );
9747 }
9748 }
9749
9750 pub fn disable_breakpoint(
9751 &mut self,
9752 _: &crate::actions::DisableBreakpoint,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) {
9756 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9757 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9758 continue;
9759 };
9760 self.edit_breakpoint_at_anchor(
9761 anchor,
9762 breakpoint,
9763 BreakpointEditAction::InvertState,
9764 cx,
9765 );
9766 }
9767 }
9768
9769 pub fn toggle_breakpoint(
9770 &mut self,
9771 _: &crate::actions::ToggleBreakpoint,
9772 window: &mut Window,
9773 cx: &mut Context<Self>,
9774 ) {
9775 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9776 if let Some(breakpoint) = breakpoint {
9777 self.edit_breakpoint_at_anchor(
9778 anchor,
9779 breakpoint,
9780 BreakpointEditAction::Toggle,
9781 cx,
9782 );
9783 } else {
9784 self.edit_breakpoint_at_anchor(
9785 anchor,
9786 Breakpoint::new_standard(),
9787 BreakpointEditAction::Toggle,
9788 cx,
9789 );
9790 }
9791 }
9792 }
9793
9794 pub fn edit_breakpoint_at_anchor(
9795 &mut self,
9796 breakpoint_position: Anchor,
9797 breakpoint: Breakpoint,
9798 edit_action: BreakpointEditAction,
9799 cx: &mut Context<Self>,
9800 ) {
9801 let Some(breakpoint_store) = &self.breakpoint_store else {
9802 return;
9803 };
9804
9805 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9806 if breakpoint_position == Anchor::min() {
9807 self.buffer()
9808 .read(cx)
9809 .excerpt_buffer_ids()
9810 .into_iter()
9811 .next()
9812 } else {
9813 None
9814 }
9815 }) else {
9816 return;
9817 };
9818
9819 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9820 return;
9821 };
9822
9823 breakpoint_store.update(cx, |breakpoint_store, cx| {
9824 breakpoint_store.toggle_breakpoint(
9825 buffer,
9826 BreakpointWithPosition {
9827 position: breakpoint_position.text_anchor,
9828 bp: breakpoint,
9829 },
9830 edit_action,
9831 cx,
9832 );
9833 });
9834
9835 cx.notify();
9836 }
9837
9838 #[cfg(any(test, feature = "test-support"))]
9839 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9840 self.breakpoint_store.clone()
9841 }
9842
9843 pub fn prepare_restore_change(
9844 &self,
9845 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9846 hunk: &MultiBufferDiffHunk,
9847 cx: &mut App,
9848 ) -> Option<()> {
9849 if hunk.is_created_file() {
9850 return None;
9851 }
9852 let buffer = self.buffer.read(cx);
9853 let diff = buffer.diff_for(hunk.buffer_id)?;
9854 let buffer = buffer.buffer(hunk.buffer_id)?;
9855 let buffer = buffer.read(cx);
9856 let original_text = diff
9857 .read(cx)
9858 .base_text()
9859 .as_rope()
9860 .slice(hunk.diff_base_byte_range.clone());
9861 let buffer_snapshot = buffer.snapshot();
9862 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9863 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9864 probe
9865 .0
9866 .start
9867 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9868 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9869 }) {
9870 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9871 Some(())
9872 } else {
9873 None
9874 }
9875 }
9876
9877 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9878 self.manipulate_lines(window, cx, |lines| lines.reverse())
9879 }
9880
9881 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9882 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9883 }
9884
9885 fn manipulate_lines<Fn>(
9886 &mut self,
9887 window: &mut Window,
9888 cx: &mut Context<Self>,
9889 mut callback: Fn,
9890 ) where
9891 Fn: FnMut(&mut Vec<&str>),
9892 {
9893 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9894
9895 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9896 let buffer = self.buffer.read(cx).snapshot(cx);
9897
9898 let mut edits = Vec::new();
9899
9900 let selections = self.selections.all::<Point>(cx);
9901 let mut selections = selections.iter().peekable();
9902 let mut contiguous_row_selections = Vec::new();
9903 let mut new_selections = Vec::new();
9904 let mut added_lines = 0;
9905 let mut removed_lines = 0;
9906
9907 while let Some(selection) = selections.next() {
9908 let (start_row, end_row) = consume_contiguous_rows(
9909 &mut contiguous_row_selections,
9910 selection,
9911 &display_map,
9912 &mut selections,
9913 );
9914
9915 let start_point = Point::new(start_row.0, 0);
9916 let end_point = Point::new(
9917 end_row.previous_row().0,
9918 buffer.line_len(end_row.previous_row()),
9919 );
9920 let text = buffer
9921 .text_for_range(start_point..end_point)
9922 .collect::<String>();
9923
9924 let mut lines = text.split('\n').collect_vec();
9925
9926 let lines_before = lines.len();
9927 callback(&mut lines);
9928 let lines_after = lines.len();
9929
9930 edits.push((start_point..end_point, lines.join("\n")));
9931
9932 // Selections must change based on added and removed line count
9933 let start_row =
9934 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9935 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9936 new_selections.push(Selection {
9937 id: selection.id,
9938 start: start_row,
9939 end: end_row,
9940 goal: SelectionGoal::None,
9941 reversed: selection.reversed,
9942 });
9943
9944 if lines_after > lines_before {
9945 added_lines += lines_after - lines_before;
9946 } else if lines_before > lines_after {
9947 removed_lines += lines_before - lines_after;
9948 }
9949 }
9950
9951 self.transact(window, cx, |this, window, cx| {
9952 let buffer = this.buffer.update(cx, |buffer, cx| {
9953 buffer.edit(edits, None, cx);
9954 buffer.snapshot(cx)
9955 });
9956
9957 // Recalculate offsets on newly edited buffer
9958 let new_selections = new_selections
9959 .iter()
9960 .map(|s| {
9961 let start_point = Point::new(s.start.0, 0);
9962 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9963 Selection {
9964 id: s.id,
9965 start: buffer.point_to_offset(start_point),
9966 end: buffer.point_to_offset(end_point),
9967 goal: s.goal,
9968 reversed: s.reversed,
9969 }
9970 })
9971 .collect();
9972
9973 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9974 s.select(new_selections);
9975 });
9976
9977 this.request_autoscroll(Autoscroll::fit(), cx);
9978 });
9979 }
9980
9981 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9982 self.manipulate_text(window, cx, |text| {
9983 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9984 if has_upper_case_characters {
9985 text.to_lowercase()
9986 } else {
9987 text.to_uppercase()
9988 }
9989 })
9990 }
9991
9992 pub fn convert_to_upper_case(
9993 &mut self,
9994 _: &ConvertToUpperCase,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.manipulate_text(window, cx, |text| text.to_uppercase())
9999 }
10000
10001 pub fn convert_to_lower_case(
10002 &mut self,
10003 _: &ConvertToLowerCase,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 self.manipulate_text(window, cx, |text| text.to_lowercase())
10008 }
10009
10010 pub fn convert_to_title_case(
10011 &mut self,
10012 _: &ConvertToTitleCase,
10013 window: &mut Window,
10014 cx: &mut Context<Self>,
10015 ) {
10016 self.manipulate_text(window, cx, |text| {
10017 text.split('\n')
10018 .map(|line| line.to_case(Case::Title))
10019 .join("\n")
10020 })
10021 }
10022
10023 pub fn convert_to_snake_case(
10024 &mut self,
10025 _: &ConvertToSnakeCase,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) {
10029 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10030 }
10031
10032 pub fn convert_to_kebab_case(
10033 &mut self,
10034 _: &ConvertToKebabCase,
10035 window: &mut Window,
10036 cx: &mut Context<Self>,
10037 ) {
10038 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10039 }
10040
10041 pub fn convert_to_upper_camel_case(
10042 &mut self,
10043 _: &ConvertToUpperCamelCase,
10044 window: &mut Window,
10045 cx: &mut Context<Self>,
10046 ) {
10047 self.manipulate_text(window, cx, |text| {
10048 text.split('\n')
10049 .map(|line| line.to_case(Case::UpperCamel))
10050 .join("\n")
10051 })
10052 }
10053
10054 pub fn convert_to_lower_camel_case(
10055 &mut self,
10056 _: &ConvertToLowerCamelCase,
10057 window: &mut Window,
10058 cx: &mut Context<Self>,
10059 ) {
10060 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10061 }
10062
10063 pub fn convert_to_opposite_case(
10064 &mut self,
10065 _: &ConvertToOppositeCase,
10066 window: &mut Window,
10067 cx: &mut Context<Self>,
10068 ) {
10069 self.manipulate_text(window, cx, |text| {
10070 text.chars()
10071 .fold(String::with_capacity(text.len()), |mut t, c| {
10072 if c.is_uppercase() {
10073 t.extend(c.to_lowercase());
10074 } else {
10075 t.extend(c.to_uppercase());
10076 }
10077 t
10078 })
10079 })
10080 }
10081
10082 pub fn convert_to_rot13(
10083 &mut self,
10084 _: &ConvertToRot13,
10085 window: &mut Window,
10086 cx: &mut Context<Self>,
10087 ) {
10088 self.manipulate_text(window, cx, |text| {
10089 text.chars()
10090 .map(|c| match c {
10091 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10092 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10093 _ => c,
10094 })
10095 .collect()
10096 })
10097 }
10098
10099 pub fn convert_to_rot47(
10100 &mut self,
10101 _: &ConvertToRot47,
10102 window: &mut Window,
10103 cx: &mut Context<Self>,
10104 ) {
10105 self.manipulate_text(window, cx, |text| {
10106 text.chars()
10107 .map(|c| {
10108 let code_point = c as u32;
10109 if code_point >= 33 && code_point <= 126 {
10110 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10111 }
10112 c
10113 })
10114 .collect()
10115 })
10116 }
10117
10118 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10119 where
10120 Fn: FnMut(&str) -> String,
10121 {
10122 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10123 let buffer = self.buffer.read(cx).snapshot(cx);
10124
10125 let mut new_selections = Vec::new();
10126 let mut edits = Vec::new();
10127 let mut selection_adjustment = 0i32;
10128
10129 for selection in self.selections.all::<usize>(cx) {
10130 let selection_is_empty = selection.is_empty();
10131
10132 let (start, end) = if selection_is_empty {
10133 let word_range = movement::surrounding_word(
10134 &display_map,
10135 selection.start.to_display_point(&display_map),
10136 );
10137 let start = word_range.start.to_offset(&display_map, Bias::Left);
10138 let end = word_range.end.to_offset(&display_map, Bias::Left);
10139 (start, end)
10140 } else {
10141 (selection.start, selection.end)
10142 };
10143
10144 let text = buffer.text_for_range(start..end).collect::<String>();
10145 let old_length = text.len() as i32;
10146 let text = callback(&text);
10147
10148 new_selections.push(Selection {
10149 start: (start as i32 - selection_adjustment) as usize,
10150 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10151 goal: SelectionGoal::None,
10152 ..selection
10153 });
10154
10155 selection_adjustment += old_length - text.len() as i32;
10156
10157 edits.push((start..end, text));
10158 }
10159
10160 self.transact(window, cx, |this, window, cx| {
10161 this.buffer.update(cx, |buffer, cx| {
10162 buffer.edit(edits, None, cx);
10163 });
10164
10165 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10166 s.select(new_selections);
10167 });
10168
10169 this.request_autoscroll(Autoscroll::fit(), cx);
10170 });
10171 }
10172
10173 pub fn duplicate(
10174 &mut self,
10175 upwards: bool,
10176 whole_lines: bool,
10177 window: &mut Window,
10178 cx: &mut Context<Self>,
10179 ) {
10180 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10181
10182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10183 let buffer = &display_map.buffer_snapshot;
10184 let selections = self.selections.all::<Point>(cx);
10185
10186 let mut edits = Vec::new();
10187 let mut selections_iter = selections.iter().peekable();
10188 while let Some(selection) = selections_iter.next() {
10189 let mut rows = selection.spanned_rows(false, &display_map);
10190 // duplicate line-wise
10191 if whole_lines || selection.start == selection.end {
10192 // Avoid duplicating the same lines twice.
10193 while let Some(next_selection) = selections_iter.peek() {
10194 let next_rows = next_selection.spanned_rows(false, &display_map);
10195 if next_rows.start < rows.end {
10196 rows.end = next_rows.end;
10197 selections_iter.next().unwrap();
10198 } else {
10199 break;
10200 }
10201 }
10202
10203 // Copy the text from the selected row region and splice it either at the start
10204 // or end of the region.
10205 let start = Point::new(rows.start.0, 0);
10206 let end = Point::new(
10207 rows.end.previous_row().0,
10208 buffer.line_len(rows.end.previous_row()),
10209 );
10210 let text = buffer
10211 .text_for_range(start..end)
10212 .chain(Some("\n"))
10213 .collect::<String>();
10214 let insert_location = if upwards {
10215 Point::new(rows.end.0, 0)
10216 } else {
10217 start
10218 };
10219 edits.push((insert_location..insert_location, text));
10220 } else {
10221 // duplicate character-wise
10222 let start = selection.start;
10223 let end = selection.end;
10224 let text = buffer.text_for_range(start..end).collect::<String>();
10225 edits.push((selection.end..selection.end, text));
10226 }
10227 }
10228
10229 self.transact(window, cx, |this, _, cx| {
10230 this.buffer.update(cx, |buffer, cx| {
10231 buffer.edit(edits, None, cx);
10232 });
10233
10234 this.request_autoscroll(Autoscroll::fit(), cx);
10235 });
10236 }
10237
10238 pub fn duplicate_line_up(
10239 &mut self,
10240 _: &DuplicateLineUp,
10241 window: &mut Window,
10242 cx: &mut Context<Self>,
10243 ) {
10244 self.duplicate(true, true, window, cx);
10245 }
10246
10247 pub fn duplicate_line_down(
10248 &mut self,
10249 _: &DuplicateLineDown,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 self.duplicate(false, true, window, cx);
10254 }
10255
10256 pub fn duplicate_selection(
10257 &mut self,
10258 _: &DuplicateSelection,
10259 window: &mut Window,
10260 cx: &mut Context<Self>,
10261 ) {
10262 self.duplicate(false, false, window, cx);
10263 }
10264
10265 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10266 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10267
10268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10269 let buffer = self.buffer.read(cx).snapshot(cx);
10270
10271 let mut edits = Vec::new();
10272 let mut unfold_ranges = Vec::new();
10273 let mut refold_creases = Vec::new();
10274
10275 let selections = self.selections.all::<Point>(cx);
10276 let mut selections = selections.iter().peekable();
10277 let mut contiguous_row_selections = Vec::new();
10278 let mut new_selections = Vec::new();
10279
10280 while let Some(selection) = selections.next() {
10281 // Find all the selections that span a contiguous row range
10282 let (start_row, end_row) = consume_contiguous_rows(
10283 &mut contiguous_row_selections,
10284 selection,
10285 &display_map,
10286 &mut selections,
10287 );
10288
10289 // Move the text spanned by the row range to be before the line preceding the row range
10290 if start_row.0 > 0 {
10291 let range_to_move = Point::new(
10292 start_row.previous_row().0,
10293 buffer.line_len(start_row.previous_row()),
10294 )
10295 ..Point::new(
10296 end_row.previous_row().0,
10297 buffer.line_len(end_row.previous_row()),
10298 );
10299 let insertion_point = display_map
10300 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10301 .0;
10302
10303 // Don't move lines across excerpts
10304 if buffer
10305 .excerpt_containing(insertion_point..range_to_move.end)
10306 .is_some()
10307 {
10308 let text = buffer
10309 .text_for_range(range_to_move.clone())
10310 .flat_map(|s| s.chars())
10311 .skip(1)
10312 .chain(['\n'])
10313 .collect::<String>();
10314
10315 edits.push((
10316 buffer.anchor_after(range_to_move.start)
10317 ..buffer.anchor_before(range_to_move.end),
10318 String::new(),
10319 ));
10320 let insertion_anchor = buffer.anchor_after(insertion_point);
10321 edits.push((insertion_anchor..insertion_anchor, text));
10322
10323 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10324
10325 // Move selections up
10326 new_selections.extend(contiguous_row_selections.drain(..).map(
10327 |mut selection| {
10328 selection.start.row -= row_delta;
10329 selection.end.row -= row_delta;
10330 selection
10331 },
10332 ));
10333
10334 // Move folds up
10335 unfold_ranges.push(range_to_move.clone());
10336 for fold in display_map.folds_in_range(
10337 buffer.anchor_before(range_to_move.start)
10338 ..buffer.anchor_after(range_to_move.end),
10339 ) {
10340 let mut start = fold.range.start.to_point(&buffer);
10341 let mut end = fold.range.end.to_point(&buffer);
10342 start.row -= row_delta;
10343 end.row -= row_delta;
10344 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10345 }
10346 }
10347 }
10348
10349 // If we didn't move line(s), preserve the existing selections
10350 new_selections.append(&mut contiguous_row_selections);
10351 }
10352
10353 self.transact(window, cx, |this, window, cx| {
10354 this.unfold_ranges(&unfold_ranges, true, true, cx);
10355 this.buffer.update(cx, |buffer, cx| {
10356 for (range, text) in edits {
10357 buffer.edit([(range, text)], None, cx);
10358 }
10359 });
10360 this.fold_creases(refold_creases, true, window, cx);
10361 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10362 s.select(new_selections);
10363 })
10364 });
10365 }
10366
10367 pub fn move_line_down(
10368 &mut self,
10369 _: &MoveLineDown,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10374
10375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10376 let buffer = self.buffer.read(cx).snapshot(cx);
10377
10378 let mut edits = Vec::new();
10379 let mut unfold_ranges = Vec::new();
10380 let mut refold_creases = Vec::new();
10381
10382 let selections = self.selections.all::<Point>(cx);
10383 let mut selections = selections.iter().peekable();
10384 let mut contiguous_row_selections = Vec::new();
10385 let mut new_selections = Vec::new();
10386
10387 while let Some(selection) = selections.next() {
10388 // Find all the selections that span a contiguous row range
10389 let (start_row, end_row) = consume_contiguous_rows(
10390 &mut contiguous_row_selections,
10391 selection,
10392 &display_map,
10393 &mut selections,
10394 );
10395
10396 // Move the text spanned by the row range to be after the last line of the row range
10397 if end_row.0 <= buffer.max_point().row {
10398 let range_to_move =
10399 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10400 let insertion_point = display_map
10401 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10402 .0;
10403
10404 // Don't move lines across excerpt boundaries
10405 if buffer
10406 .excerpt_containing(range_to_move.start..insertion_point)
10407 .is_some()
10408 {
10409 let mut text = String::from("\n");
10410 text.extend(buffer.text_for_range(range_to_move.clone()));
10411 text.pop(); // Drop trailing newline
10412 edits.push((
10413 buffer.anchor_after(range_to_move.start)
10414 ..buffer.anchor_before(range_to_move.end),
10415 String::new(),
10416 ));
10417 let insertion_anchor = buffer.anchor_after(insertion_point);
10418 edits.push((insertion_anchor..insertion_anchor, text));
10419
10420 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10421
10422 // Move selections down
10423 new_selections.extend(contiguous_row_selections.drain(..).map(
10424 |mut selection| {
10425 selection.start.row += row_delta;
10426 selection.end.row += row_delta;
10427 selection
10428 },
10429 ));
10430
10431 // Move folds down
10432 unfold_ranges.push(range_to_move.clone());
10433 for fold in display_map.folds_in_range(
10434 buffer.anchor_before(range_to_move.start)
10435 ..buffer.anchor_after(range_to_move.end),
10436 ) {
10437 let mut start = fold.range.start.to_point(&buffer);
10438 let mut end = fold.range.end.to_point(&buffer);
10439 start.row += row_delta;
10440 end.row += row_delta;
10441 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10442 }
10443 }
10444 }
10445
10446 // If we didn't move line(s), preserve the existing selections
10447 new_selections.append(&mut contiguous_row_selections);
10448 }
10449
10450 self.transact(window, cx, |this, window, cx| {
10451 this.unfold_ranges(&unfold_ranges, true, true, cx);
10452 this.buffer.update(cx, |buffer, cx| {
10453 for (range, text) in edits {
10454 buffer.edit([(range, text)], None, cx);
10455 }
10456 });
10457 this.fold_creases(refold_creases, true, window, cx);
10458 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10459 s.select(new_selections)
10460 });
10461 });
10462 }
10463
10464 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10466 let text_layout_details = &self.text_layout_details(window);
10467 self.transact(window, cx, |this, window, cx| {
10468 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10469 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10470 s.move_with(|display_map, selection| {
10471 if !selection.is_empty() {
10472 return;
10473 }
10474
10475 let mut head = selection.head();
10476 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10477 if head.column() == display_map.line_len(head.row()) {
10478 transpose_offset = display_map
10479 .buffer_snapshot
10480 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10481 }
10482
10483 if transpose_offset == 0 {
10484 return;
10485 }
10486
10487 *head.column_mut() += 1;
10488 head = display_map.clip_point(head, Bias::Right);
10489 let goal = SelectionGoal::HorizontalPosition(
10490 display_map
10491 .x_for_display_point(head, text_layout_details)
10492 .into(),
10493 );
10494 selection.collapse_to(head, goal);
10495
10496 let transpose_start = display_map
10497 .buffer_snapshot
10498 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10499 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10500 let transpose_end = display_map
10501 .buffer_snapshot
10502 .clip_offset(transpose_offset + 1, Bias::Right);
10503 if let Some(ch) =
10504 display_map.buffer_snapshot.chars_at(transpose_start).next()
10505 {
10506 edits.push((transpose_start..transpose_offset, String::new()));
10507 edits.push((transpose_end..transpose_end, ch.to_string()));
10508 }
10509 }
10510 });
10511 edits
10512 });
10513 this.buffer
10514 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10515 let selections = this.selections.all::<usize>(cx);
10516 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10517 s.select(selections);
10518 });
10519 });
10520 }
10521
10522 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10523 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10524 self.rewrap_impl(RewrapOptions::default(), cx)
10525 }
10526
10527 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10528 let buffer = self.buffer.read(cx).snapshot(cx);
10529 let selections = self.selections.all::<Point>(cx);
10530 let mut selections = selections.iter().peekable();
10531
10532 let mut edits = Vec::new();
10533 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10534
10535 while let Some(selection) = selections.next() {
10536 let mut start_row = selection.start.row;
10537 let mut end_row = selection.end.row;
10538
10539 // Skip selections that overlap with a range that has already been rewrapped.
10540 let selection_range = start_row..end_row;
10541 if rewrapped_row_ranges
10542 .iter()
10543 .any(|range| range.overlaps(&selection_range))
10544 {
10545 continue;
10546 }
10547
10548 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10549
10550 // Since not all lines in the selection may be at the same indent
10551 // level, choose the indent size that is the most common between all
10552 // of the lines.
10553 //
10554 // If there is a tie, we use the deepest indent.
10555 let (indent_size, indent_end) = {
10556 let mut indent_size_occurrences = HashMap::default();
10557 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10558
10559 for row in start_row..=end_row {
10560 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10561 rows_by_indent_size.entry(indent).or_default().push(row);
10562 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10563 }
10564
10565 let indent_size = indent_size_occurrences
10566 .into_iter()
10567 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10568 .map(|(indent, _)| indent)
10569 .unwrap_or_default();
10570 let row = rows_by_indent_size[&indent_size][0];
10571 let indent_end = Point::new(row, indent_size.len);
10572
10573 (indent_size, indent_end)
10574 };
10575
10576 let mut line_prefix = indent_size.chars().collect::<String>();
10577
10578 let mut inside_comment = false;
10579 if let Some(comment_prefix) =
10580 buffer
10581 .language_scope_at(selection.head())
10582 .and_then(|language| {
10583 language
10584 .line_comment_prefixes()
10585 .iter()
10586 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10587 .cloned()
10588 })
10589 {
10590 line_prefix.push_str(&comment_prefix);
10591 inside_comment = true;
10592 }
10593
10594 let language_settings = buffer.language_settings_at(selection.head(), cx);
10595 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10596 RewrapBehavior::InComments => inside_comment,
10597 RewrapBehavior::InSelections => !selection.is_empty(),
10598 RewrapBehavior::Anywhere => true,
10599 };
10600
10601 let should_rewrap = options.override_language_settings
10602 || allow_rewrap_based_on_language
10603 || self.hard_wrap.is_some();
10604 if !should_rewrap {
10605 continue;
10606 }
10607
10608 if selection.is_empty() {
10609 'expand_upwards: while start_row > 0 {
10610 let prev_row = start_row - 1;
10611 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10612 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10613 {
10614 start_row = prev_row;
10615 } else {
10616 break 'expand_upwards;
10617 }
10618 }
10619
10620 'expand_downwards: while end_row < buffer.max_point().row {
10621 let next_row = end_row + 1;
10622 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10623 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10624 {
10625 end_row = next_row;
10626 } else {
10627 break 'expand_downwards;
10628 }
10629 }
10630 }
10631
10632 let start = Point::new(start_row, 0);
10633 let start_offset = start.to_offset(&buffer);
10634 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10635 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10636 let Some(lines_without_prefixes) = selection_text
10637 .lines()
10638 .map(|line| {
10639 line.strip_prefix(&line_prefix)
10640 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10641 .with_context(|| {
10642 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10643 })
10644 })
10645 .collect::<Result<Vec<_>, _>>()
10646 .log_err()
10647 else {
10648 continue;
10649 };
10650
10651 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10652 buffer
10653 .language_settings_at(Point::new(start_row, 0), cx)
10654 .preferred_line_length as usize
10655 });
10656 let wrapped_text = wrap_with_prefix(
10657 line_prefix,
10658 lines_without_prefixes.join("\n"),
10659 wrap_column,
10660 tab_size,
10661 options.preserve_existing_whitespace,
10662 );
10663
10664 // TODO: should always use char-based diff while still supporting cursor behavior that
10665 // matches vim.
10666 let mut diff_options = DiffOptions::default();
10667 if options.override_language_settings {
10668 diff_options.max_word_diff_len = 0;
10669 diff_options.max_word_diff_line_count = 0;
10670 } else {
10671 diff_options.max_word_diff_len = usize::MAX;
10672 diff_options.max_word_diff_line_count = usize::MAX;
10673 }
10674
10675 for (old_range, new_text) in
10676 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10677 {
10678 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10679 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10680 edits.push((edit_start..edit_end, new_text));
10681 }
10682
10683 rewrapped_row_ranges.push(start_row..=end_row);
10684 }
10685
10686 self.buffer
10687 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10688 }
10689
10690 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10691 let mut text = String::new();
10692 let buffer = self.buffer.read(cx).snapshot(cx);
10693 let mut selections = self.selections.all::<Point>(cx);
10694 let mut clipboard_selections = Vec::with_capacity(selections.len());
10695 {
10696 let max_point = buffer.max_point();
10697 let mut is_first = true;
10698 for selection in &mut selections {
10699 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10700 if is_entire_line {
10701 selection.start = Point::new(selection.start.row, 0);
10702 if !selection.is_empty() && selection.end.column == 0 {
10703 selection.end = cmp::min(max_point, selection.end);
10704 } else {
10705 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10706 }
10707 selection.goal = SelectionGoal::None;
10708 }
10709 if is_first {
10710 is_first = false;
10711 } else {
10712 text += "\n";
10713 }
10714 let mut len = 0;
10715 for chunk in buffer.text_for_range(selection.start..selection.end) {
10716 text.push_str(chunk);
10717 len += chunk.len();
10718 }
10719 clipboard_selections.push(ClipboardSelection {
10720 len,
10721 is_entire_line,
10722 first_line_indent: buffer
10723 .indent_size_for_line(MultiBufferRow(selection.start.row))
10724 .len,
10725 });
10726 }
10727 }
10728
10729 self.transact(window, cx, |this, window, cx| {
10730 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10731 s.select(selections);
10732 });
10733 this.insert("", window, cx);
10734 });
10735 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10736 }
10737
10738 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10739 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10740 let item = self.cut_common(window, cx);
10741 cx.write_to_clipboard(item);
10742 }
10743
10744 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10745 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10746 self.change_selections(None, window, cx, |s| {
10747 s.move_with(|snapshot, sel| {
10748 if sel.is_empty() {
10749 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10750 }
10751 });
10752 });
10753 let item = self.cut_common(window, cx);
10754 cx.set_global(KillRing(item))
10755 }
10756
10757 pub fn kill_ring_yank(
10758 &mut self,
10759 _: &KillRingYank,
10760 window: &mut Window,
10761 cx: &mut Context<Self>,
10762 ) {
10763 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10764 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10765 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10766 (kill_ring.text().to_string(), kill_ring.metadata_json())
10767 } else {
10768 return;
10769 }
10770 } else {
10771 return;
10772 };
10773 self.do_paste(&text, metadata, false, window, cx);
10774 }
10775
10776 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10777 self.do_copy(true, cx);
10778 }
10779
10780 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10781 self.do_copy(false, cx);
10782 }
10783
10784 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10785 let selections = self.selections.all::<Point>(cx);
10786 let buffer = self.buffer.read(cx).read(cx);
10787 let mut text = String::new();
10788
10789 let mut clipboard_selections = Vec::with_capacity(selections.len());
10790 {
10791 let max_point = buffer.max_point();
10792 let mut is_first = true;
10793 for selection in &selections {
10794 let mut start = selection.start;
10795 let mut end = selection.end;
10796 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10797 if is_entire_line {
10798 start = Point::new(start.row, 0);
10799 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10800 }
10801
10802 let mut trimmed_selections = Vec::new();
10803 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10804 let row = MultiBufferRow(start.row);
10805 let first_indent = buffer.indent_size_for_line(row);
10806 if first_indent.len == 0 || start.column > first_indent.len {
10807 trimmed_selections.push(start..end);
10808 } else {
10809 trimmed_selections.push(
10810 Point::new(row.0, first_indent.len)
10811 ..Point::new(row.0, buffer.line_len(row)),
10812 );
10813 for row in start.row + 1..=end.row {
10814 let mut line_len = buffer.line_len(MultiBufferRow(row));
10815 if row == end.row {
10816 line_len = end.column;
10817 }
10818 if line_len == 0 {
10819 trimmed_selections
10820 .push(Point::new(row, 0)..Point::new(row, line_len));
10821 continue;
10822 }
10823 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10824 if row_indent_size.len >= first_indent.len {
10825 trimmed_selections.push(
10826 Point::new(row, first_indent.len)..Point::new(row, line_len),
10827 );
10828 } else {
10829 trimmed_selections.clear();
10830 trimmed_selections.push(start..end);
10831 break;
10832 }
10833 }
10834 }
10835 } else {
10836 trimmed_selections.push(start..end);
10837 }
10838
10839 for trimmed_range in trimmed_selections {
10840 if is_first {
10841 is_first = false;
10842 } else {
10843 text += "\n";
10844 }
10845 let mut len = 0;
10846 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10847 text.push_str(chunk);
10848 len += chunk.len();
10849 }
10850 clipboard_selections.push(ClipboardSelection {
10851 len,
10852 is_entire_line,
10853 first_line_indent: buffer
10854 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10855 .len,
10856 });
10857 }
10858 }
10859 }
10860
10861 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10862 text,
10863 clipboard_selections,
10864 ));
10865 }
10866
10867 pub fn do_paste(
10868 &mut self,
10869 text: &String,
10870 clipboard_selections: Option<Vec<ClipboardSelection>>,
10871 handle_entire_lines: bool,
10872 window: &mut Window,
10873 cx: &mut Context<Self>,
10874 ) {
10875 if self.read_only(cx) {
10876 return;
10877 }
10878
10879 let clipboard_text = Cow::Borrowed(text);
10880
10881 self.transact(window, cx, |this, window, cx| {
10882 if let Some(mut clipboard_selections) = clipboard_selections {
10883 let old_selections = this.selections.all::<usize>(cx);
10884 let all_selections_were_entire_line =
10885 clipboard_selections.iter().all(|s| s.is_entire_line);
10886 let first_selection_indent_column =
10887 clipboard_selections.first().map(|s| s.first_line_indent);
10888 if clipboard_selections.len() != old_selections.len() {
10889 clipboard_selections.drain(..);
10890 }
10891 let cursor_offset = this.selections.last::<usize>(cx).head();
10892 let mut auto_indent_on_paste = true;
10893
10894 this.buffer.update(cx, |buffer, cx| {
10895 let snapshot = buffer.read(cx);
10896 auto_indent_on_paste = snapshot
10897 .language_settings_at(cursor_offset, cx)
10898 .auto_indent_on_paste;
10899
10900 let mut start_offset = 0;
10901 let mut edits = Vec::new();
10902 let mut original_indent_columns = Vec::new();
10903 for (ix, selection) in old_selections.iter().enumerate() {
10904 let to_insert;
10905 let entire_line;
10906 let original_indent_column;
10907 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10908 let end_offset = start_offset + clipboard_selection.len;
10909 to_insert = &clipboard_text[start_offset..end_offset];
10910 entire_line = clipboard_selection.is_entire_line;
10911 start_offset = end_offset + 1;
10912 original_indent_column = Some(clipboard_selection.first_line_indent);
10913 } else {
10914 to_insert = clipboard_text.as_str();
10915 entire_line = all_selections_were_entire_line;
10916 original_indent_column = first_selection_indent_column
10917 }
10918
10919 // If the corresponding selection was empty when this slice of the
10920 // clipboard text was written, then the entire line containing the
10921 // selection was copied. If this selection is also currently empty,
10922 // then paste the line before the current line of the buffer.
10923 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10924 let column = selection.start.to_point(&snapshot).column as usize;
10925 let line_start = selection.start - column;
10926 line_start..line_start
10927 } else {
10928 selection.range()
10929 };
10930
10931 edits.push((range, to_insert));
10932 original_indent_columns.push(original_indent_column);
10933 }
10934 drop(snapshot);
10935
10936 buffer.edit(
10937 edits,
10938 if auto_indent_on_paste {
10939 Some(AutoindentMode::Block {
10940 original_indent_columns,
10941 })
10942 } else {
10943 None
10944 },
10945 cx,
10946 );
10947 });
10948
10949 let selections = this.selections.all::<usize>(cx);
10950 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10951 s.select(selections)
10952 });
10953 } else {
10954 this.insert(&clipboard_text, window, cx);
10955 }
10956 });
10957 }
10958
10959 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10960 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10961 if let Some(item) = cx.read_from_clipboard() {
10962 let entries = item.entries();
10963
10964 match entries.first() {
10965 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10966 // of all the pasted entries.
10967 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10968 .do_paste(
10969 clipboard_string.text(),
10970 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10971 true,
10972 window,
10973 cx,
10974 ),
10975 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10976 }
10977 }
10978 }
10979
10980 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10981 if self.read_only(cx) {
10982 return;
10983 }
10984
10985 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10986
10987 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10988 if let Some((selections, _)) =
10989 self.selection_history.transaction(transaction_id).cloned()
10990 {
10991 self.change_selections(None, window, cx, |s| {
10992 s.select_anchors(selections.to_vec());
10993 });
10994 } else {
10995 log::error!(
10996 "No entry in selection_history found for undo. \
10997 This may correspond to a bug where undo does not update the selection. \
10998 If this is occurring, please add details to \
10999 https://github.com/zed-industries/zed/issues/22692"
11000 );
11001 }
11002 self.request_autoscroll(Autoscroll::fit(), cx);
11003 self.unmark_text(window, cx);
11004 self.refresh_inline_completion(true, false, window, cx);
11005 cx.emit(EditorEvent::Edited { transaction_id });
11006 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11007 }
11008 }
11009
11010 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11011 if self.read_only(cx) {
11012 return;
11013 }
11014
11015 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11016
11017 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11018 if let Some((_, Some(selections))) =
11019 self.selection_history.transaction(transaction_id).cloned()
11020 {
11021 self.change_selections(None, window, cx, |s| {
11022 s.select_anchors(selections.to_vec());
11023 });
11024 } else {
11025 log::error!(
11026 "No entry in selection_history found for redo. \
11027 This may correspond to a bug where undo does not update the selection. \
11028 If this is occurring, please add details to \
11029 https://github.com/zed-industries/zed/issues/22692"
11030 );
11031 }
11032 self.request_autoscroll(Autoscroll::fit(), cx);
11033 self.unmark_text(window, cx);
11034 self.refresh_inline_completion(true, false, window, cx);
11035 cx.emit(EditorEvent::Edited { transaction_id });
11036 }
11037 }
11038
11039 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11040 self.buffer
11041 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11042 }
11043
11044 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11045 self.buffer
11046 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11047 }
11048
11049 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11050 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11052 s.move_with(|map, selection| {
11053 let cursor = if selection.is_empty() {
11054 movement::left(map, selection.start)
11055 } else {
11056 selection.start
11057 };
11058 selection.collapse_to(cursor, SelectionGoal::None);
11059 });
11060 })
11061 }
11062
11063 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11064 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11065 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11066 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11067 })
11068 }
11069
11070 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11071 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11072 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11073 s.move_with(|map, selection| {
11074 let cursor = if selection.is_empty() {
11075 movement::right(map, selection.end)
11076 } else {
11077 selection.end
11078 };
11079 selection.collapse_to(cursor, SelectionGoal::None)
11080 });
11081 })
11082 }
11083
11084 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11086 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11087 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11088 })
11089 }
11090
11091 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11092 if self.take_rename(true, window, cx).is_some() {
11093 return;
11094 }
11095
11096 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11097 cx.propagate();
11098 return;
11099 }
11100
11101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11102
11103 let text_layout_details = &self.text_layout_details(window);
11104 let selection_count = self.selections.count();
11105 let first_selection = self.selections.first_anchor();
11106
11107 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11108 s.move_with(|map, selection| {
11109 if !selection.is_empty() {
11110 selection.goal = SelectionGoal::None;
11111 }
11112 let (cursor, goal) = movement::up(
11113 map,
11114 selection.start,
11115 selection.goal,
11116 false,
11117 text_layout_details,
11118 );
11119 selection.collapse_to(cursor, goal);
11120 });
11121 });
11122
11123 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11124 {
11125 cx.propagate();
11126 }
11127 }
11128
11129 pub fn move_up_by_lines(
11130 &mut self,
11131 action: &MoveUpByLines,
11132 window: &mut Window,
11133 cx: &mut Context<Self>,
11134 ) {
11135 if self.take_rename(true, window, cx).is_some() {
11136 return;
11137 }
11138
11139 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11140 cx.propagate();
11141 return;
11142 }
11143
11144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11145
11146 let text_layout_details = &self.text_layout_details(window);
11147
11148 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11149 s.move_with(|map, selection| {
11150 if !selection.is_empty() {
11151 selection.goal = SelectionGoal::None;
11152 }
11153 let (cursor, goal) = movement::up_by_rows(
11154 map,
11155 selection.start,
11156 action.lines,
11157 selection.goal,
11158 false,
11159 text_layout_details,
11160 );
11161 selection.collapse_to(cursor, goal);
11162 });
11163 })
11164 }
11165
11166 pub fn move_down_by_lines(
11167 &mut self,
11168 action: &MoveDownByLines,
11169 window: &mut Window,
11170 cx: &mut Context<Self>,
11171 ) {
11172 if self.take_rename(true, window, cx).is_some() {
11173 return;
11174 }
11175
11176 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11177 cx.propagate();
11178 return;
11179 }
11180
11181 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11182
11183 let text_layout_details = &self.text_layout_details(window);
11184
11185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11186 s.move_with(|map, selection| {
11187 if !selection.is_empty() {
11188 selection.goal = SelectionGoal::None;
11189 }
11190 let (cursor, goal) = movement::down_by_rows(
11191 map,
11192 selection.start,
11193 action.lines,
11194 selection.goal,
11195 false,
11196 text_layout_details,
11197 );
11198 selection.collapse_to(cursor, goal);
11199 });
11200 })
11201 }
11202
11203 pub fn select_down_by_lines(
11204 &mut self,
11205 action: &SelectDownByLines,
11206 window: &mut Window,
11207 cx: &mut Context<Self>,
11208 ) {
11209 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11210 let text_layout_details = &self.text_layout_details(window);
11211 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11212 s.move_heads_with(|map, head, goal| {
11213 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11214 })
11215 })
11216 }
11217
11218 pub fn select_up_by_lines(
11219 &mut self,
11220 action: &SelectUpByLines,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11225 let text_layout_details = &self.text_layout_details(window);
11226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11227 s.move_heads_with(|map, head, goal| {
11228 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11229 })
11230 })
11231 }
11232
11233 pub fn select_page_up(
11234 &mut self,
11235 _: &SelectPageUp,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 let Some(row_count) = self.visible_row_count() else {
11240 return;
11241 };
11242
11243 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11244
11245 let text_layout_details = &self.text_layout_details(window);
11246
11247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11248 s.move_heads_with(|map, head, goal| {
11249 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11250 })
11251 })
11252 }
11253
11254 pub fn move_page_up(
11255 &mut self,
11256 action: &MovePageUp,
11257 window: &mut Window,
11258 cx: &mut Context<Self>,
11259 ) {
11260 if self.take_rename(true, window, cx).is_some() {
11261 return;
11262 }
11263
11264 if self
11265 .context_menu
11266 .borrow_mut()
11267 .as_mut()
11268 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11269 .unwrap_or(false)
11270 {
11271 return;
11272 }
11273
11274 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11275 cx.propagate();
11276 return;
11277 }
11278
11279 let Some(row_count) = self.visible_row_count() else {
11280 return;
11281 };
11282
11283 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11284
11285 let autoscroll = if action.center_cursor {
11286 Autoscroll::center()
11287 } else {
11288 Autoscroll::fit()
11289 };
11290
11291 let text_layout_details = &self.text_layout_details(window);
11292
11293 self.change_selections(Some(autoscroll), window, cx, |s| {
11294 s.move_with(|map, selection| {
11295 if !selection.is_empty() {
11296 selection.goal = SelectionGoal::None;
11297 }
11298 let (cursor, goal) = movement::up_by_rows(
11299 map,
11300 selection.end,
11301 row_count,
11302 selection.goal,
11303 false,
11304 text_layout_details,
11305 );
11306 selection.collapse_to(cursor, goal);
11307 });
11308 });
11309 }
11310
11311 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11312 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11313 let text_layout_details = &self.text_layout_details(window);
11314 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11315 s.move_heads_with(|map, head, goal| {
11316 movement::up(map, head, goal, false, text_layout_details)
11317 })
11318 })
11319 }
11320
11321 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11322 self.take_rename(true, window, cx);
11323
11324 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11325 cx.propagate();
11326 return;
11327 }
11328
11329 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11330
11331 let text_layout_details = &self.text_layout_details(window);
11332 let selection_count = self.selections.count();
11333 let first_selection = self.selections.first_anchor();
11334
11335 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11336 s.move_with(|map, selection| {
11337 if !selection.is_empty() {
11338 selection.goal = SelectionGoal::None;
11339 }
11340 let (cursor, goal) = movement::down(
11341 map,
11342 selection.end,
11343 selection.goal,
11344 false,
11345 text_layout_details,
11346 );
11347 selection.collapse_to(cursor, goal);
11348 });
11349 });
11350
11351 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11352 {
11353 cx.propagate();
11354 }
11355 }
11356
11357 pub fn select_page_down(
11358 &mut self,
11359 _: &SelectPageDown,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 let Some(row_count) = self.visible_row_count() else {
11364 return;
11365 };
11366
11367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11368
11369 let text_layout_details = &self.text_layout_details(window);
11370
11371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11372 s.move_heads_with(|map, head, goal| {
11373 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11374 })
11375 })
11376 }
11377
11378 pub fn move_page_down(
11379 &mut self,
11380 action: &MovePageDown,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) {
11384 if self.take_rename(true, window, cx).is_some() {
11385 return;
11386 }
11387
11388 if self
11389 .context_menu
11390 .borrow_mut()
11391 .as_mut()
11392 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11393 .unwrap_or(false)
11394 {
11395 return;
11396 }
11397
11398 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11399 cx.propagate();
11400 return;
11401 }
11402
11403 let Some(row_count) = self.visible_row_count() else {
11404 return;
11405 };
11406
11407 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11408
11409 let autoscroll = if action.center_cursor {
11410 Autoscroll::center()
11411 } else {
11412 Autoscroll::fit()
11413 };
11414
11415 let text_layout_details = &self.text_layout_details(window);
11416 self.change_selections(Some(autoscroll), window, cx, |s| {
11417 s.move_with(|map, selection| {
11418 if !selection.is_empty() {
11419 selection.goal = SelectionGoal::None;
11420 }
11421 let (cursor, goal) = movement::down_by_rows(
11422 map,
11423 selection.end,
11424 row_count,
11425 selection.goal,
11426 false,
11427 text_layout_details,
11428 );
11429 selection.collapse_to(cursor, goal);
11430 });
11431 });
11432 }
11433
11434 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11436 let text_layout_details = &self.text_layout_details(window);
11437 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11438 s.move_heads_with(|map, head, goal| {
11439 movement::down(map, head, goal, false, text_layout_details)
11440 })
11441 });
11442 }
11443
11444 pub fn context_menu_first(
11445 &mut self,
11446 _: &ContextMenuFirst,
11447 _window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11451 context_menu.select_first(self.completion_provider.as_deref(), cx);
11452 }
11453 }
11454
11455 pub fn context_menu_prev(
11456 &mut self,
11457 _: &ContextMenuPrevious,
11458 _window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11462 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11463 }
11464 }
11465
11466 pub fn context_menu_next(
11467 &mut self,
11468 _: &ContextMenuNext,
11469 _window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11473 context_menu.select_next(self.completion_provider.as_deref(), cx);
11474 }
11475 }
11476
11477 pub fn context_menu_last(
11478 &mut self,
11479 _: &ContextMenuLast,
11480 _window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11484 context_menu.select_last(self.completion_provider.as_deref(), cx);
11485 }
11486 }
11487
11488 pub fn move_to_previous_word_start(
11489 &mut self,
11490 _: &MoveToPreviousWordStart,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11495 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11496 s.move_cursors_with(|map, head, _| {
11497 (
11498 movement::previous_word_start(map, head),
11499 SelectionGoal::None,
11500 )
11501 });
11502 })
11503 }
11504
11505 pub fn move_to_previous_subword_start(
11506 &mut self,
11507 _: &MoveToPreviousSubwordStart,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11512 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11513 s.move_cursors_with(|map, head, _| {
11514 (
11515 movement::previous_subword_start(map, head),
11516 SelectionGoal::None,
11517 )
11518 });
11519 })
11520 }
11521
11522 pub fn select_to_previous_word_start(
11523 &mut self,
11524 _: &SelectToPreviousWordStart,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) {
11528 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11530 s.move_heads_with(|map, head, _| {
11531 (
11532 movement::previous_word_start(map, head),
11533 SelectionGoal::None,
11534 )
11535 });
11536 })
11537 }
11538
11539 pub fn select_to_previous_subword_start(
11540 &mut self,
11541 _: &SelectToPreviousSubwordStart,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11546 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11547 s.move_heads_with(|map, head, _| {
11548 (
11549 movement::previous_subword_start(map, head),
11550 SelectionGoal::None,
11551 )
11552 });
11553 })
11554 }
11555
11556 pub fn delete_to_previous_word_start(
11557 &mut self,
11558 action: &DeleteToPreviousWordStart,
11559 window: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) {
11562 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11563 self.transact(window, cx, |this, window, cx| {
11564 this.select_autoclose_pair(window, cx);
11565 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11566 s.move_with(|map, selection| {
11567 if selection.is_empty() {
11568 let cursor = if action.ignore_newlines {
11569 movement::previous_word_start(map, selection.head())
11570 } else {
11571 movement::previous_word_start_or_newline(map, selection.head())
11572 };
11573 selection.set_head(cursor, SelectionGoal::None);
11574 }
11575 });
11576 });
11577 this.insert("", window, cx);
11578 });
11579 }
11580
11581 pub fn delete_to_previous_subword_start(
11582 &mut self,
11583 _: &DeleteToPreviousSubwordStart,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11588 self.transact(window, cx, |this, window, cx| {
11589 this.select_autoclose_pair(window, cx);
11590 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11591 s.move_with(|map, selection| {
11592 if selection.is_empty() {
11593 let cursor = movement::previous_subword_start(map, selection.head());
11594 selection.set_head(cursor, SelectionGoal::None);
11595 }
11596 });
11597 });
11598 this.insert("", window, cx);
11599 });
11600 }
11601
11602 pub fn move_to_next_word_end(
11603 &mut self,
11604 _: &MoveToNextWordEnd,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11610 s.move_cursors_with(|map, head, _| {
11611 (movement::next_word_end(map, head), SelectionGoal::None)
11612 });
11613 })
11614 }
11615
11616 pub fn move_to_next_subword_end(
11617 &mut self,
11618 _: &MoveToNextSubwordEnd,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11623 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11624 s.move_cursors_with(|map, head, _| {
11625 (movement::next_subword_end(map, head), SelectionGoal::None)
11626 });
11627 })
11628 }
11629
11630 pub fn select_to_next_word_end(
11631 &mut self,
11632 _: &SelectToNextWordEnd,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11638 s.move_heads_with(|map, head, _| {
11639 (movement::next_word_end(map, head), SelectionGoal::None)
11640 });
11641 })
11642 }
11643
11644 pub fn select_to_next_subword_end(
11645 &mut self,
11646 _: &SelectToNextSubwordEnd,
11647 window: &mut Window,
11648 cx: &mut Context<Self>,
11649 ) {
11650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11651 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11652 s.move_heads_with(|map, head, _| {
11653 (movement::next_subword_end(map, head), SelectionGoal::None)
11654 });
11655 })
11656 }
11657
11658 pub fn delete_to_next_word_end(
11659 &mut self,
11660 action: &DeleteToNextWordEnd,
11661 window: &mut Window,
11662 cx: &mut Context<Self>,
11663 ) {
11664 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11665 self.transact(window, cx, |this, window, cx| {
11666 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11667 s.move_with(|map, selection| {
11668 if selection.is_empty() {
11669 let cursor = if action.ignore_newlines {
11670 movement::next_word_end(map, selection.head())
11671 } else {
11672 movement::next_word_end_or_newline(map, selection.head())
11673 };
11674 selection.set_head(cursor, SelectionGoal::None);
11675 }
11676 });
11677 });
11678 this.insert("", window, cx);
11679 });
11680 }
11681
11682 pub fn delete_to_next_subword_end(
11683 &mut self,
11684 _: &DeleteToNextSubwordEnd,
11685 window: &mut Window,
11686 cx: &mut Context<Self>,
11687 ) {
11688 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11689 self.transact(window, cx, |this, window, cx| {
11690 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11691 s.move_with(|map, selection| {
11692 if selection.is_empty() {
11693 let cursor = movement::next_subword_end(map, selection.head());
11694 selection.set_head(cursor, SelectionGoal::None);
11695 }
11696 });
11697 });
11698 this.insert("", window, cx);
11699 });
11700 }
11701
11702 pub fn move_to_beginning_of_line(
11703 &mut self,
11704 action: &MoveToBeginningOfLine,
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_cursors_with(|map, head, _| {
11711 (
11712 movement::indented_line_beginning(
11713 map,
11714 head,
11715 action.stop_at_soft_wraps,
11716 action.stop_at_indent,
11717 ),
11718 SelectionGoal::None,
11719 )
11720 });
11721 })
11722 }
11723
11724 pub fn select_to_beginning_of_line(
11725 &mut self,
11726 action: &SelectToBeginningOfLine,
11727 window: &mut Window,
11728 cx: &mut Context<Self>,
11729 ) {
11730 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11732 s.move_heads_with(|map, head, _| {
11733 (
11734 movement::indented_line_beginning(
11735 map,
11736 head,
11737 action.stop_at_soft_wraps,
11738 action.stop_at_indent,
11739 ),
11740 SelectionGoal::None,
11741 )
11742 });
11743 });
11744 }
11745
11746 pub fn delete_to_beginning_of_line(
11747 &mut self,
11748 action: &DeleteToBeginningOfLine,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11753 self.transact(window, cx, |this, window, cx| {
11754 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_with(|_, selection| {
11756 selection.reversed = true;
11757 });
11758 });
11759
11760 this.select_to_beginning_of_line(
11761 &SelectToBeginningOfLine {
11762 stop_at_soft_wraps: false,
11763 stop_at_indent: action.stop_at_indent,
11764 },
11765 window,
11766 cx,
11767 );
11768 this.backspace(&Backspace, window, cx);
11769 });
11770 }
11771
11772 pub fn move_to_end_of_line(
11773 &mut self,
11774 action: &MoveToEndOfLine,
11775 window: &mut Window,
11776 cx: &mut Context<Self>,
11777 ) {
11778 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11780 s.move_cursors_with(|map, head, _| {
11781 (
11782 movement::line_end(map, head, action.stop_at_soft_wraps),
11783 SelectionGoal::None,
11784 )
11785 });
11786 })
11787 }
11788
11789 pub fn select_to_end_of_line(
11790 &mut self,
11791 action: &SelectToEndOfLine,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11797 s.move_heads_with(|map, head, _| {
11798 (
11799 movement::line_end(map, head, action.stop_at_soft_wraps),
11800 SelectionGoal::None,
11801 )
11802 });
11803 })
11804 }
11805
11806 pub fn delete_to_end_of_line(
11807 &mut self,
11808 _: &DeleteToEndOfLine,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 ) {
11812 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11813 self.transact(window, cx, |this, window, cx| {
11814 this.select_to_end_of_line(
11815 &SelectToEndOfLine {
11816 stop_at_soft_wraps: false,
11817 },
11818 window,
11819 cx,
11820 );
11821 this.delete(&Delete, window, cx);
11822 });
11823 }
11824
11825 pub fn cut_to_end_of_line(
11826 &mut self,
11827 _: &CutToEndOfLine,
11828 window: &mut Window,
11829 cx: &mut Context<Self>,
11830 ) {
11831 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11832 self.transact(window, cx, |this, window, cx| {
11833 this.select_to_end_of_line(
11834 &SelectToEndOfLine {
11835 stop_at_soft_wraps: false,
11836 },
11837 window,
11838 cx,
11839 );
11840 this.cut(&Cut, window, cx);
11841 });
11842 }
11843
11844 pub fn move_to_start_of_paragraph(
11845 &mut self,
11846 _: &MoveToStartOfParagraph,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11851 cx.propagate();
11852 return;
11853 }
11854 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11855 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11856 s.move_with(|map, selection| {
11857 selection.collapse_to(
11858 movement::start_of_paragraph(map, selection.head(), 1),
11859 SelectionGoal::None,
11860 )
11861 });
11862 })
11863 }
11864
11865 pub fn move_to_end_of_paragraph(
11866 &mut self,
11867 _: &MoveToEndOfParagraph,
11868 window: &mut Window,
11869 cx: &mut Context<Self>,
11870 ) {
11871 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11872 cx.propagate();
11873 return;
11874 }
11875 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11876 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11877 s.move_with(|map, selection| {
11878 selection.collapse_to(
11879 movement::end_of_paragraph(map, selection.head(), 1),
11880 SelectionGoal::None,
11881 )
11882 });
11883 })
11884 }
11885
11886 pub fn select_to_start_of_paragraph(
11887 &mut self,
11888 _: &SelectToStartOfParagraph,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11893 cx.propagate();
11894 return;
11895 }
11896 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11897 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11898 s.move_heads_with(|map, head, _| {
11899 (
11900 movement::start_of_paragraph(map, head, 1),
11901 SelectionGoal::None,
11902 )
11903 });
11904 })
11905 }
11906
11907 pub fn select_to_end_of_paragraph(
11908 &mut self,
11909 _: &SelectToEndOfParagraph,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11914 cx.propagate();
11915 return;
11916 }
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11919 s.move_heads_with(|map, head, _| {
11920 (
11921 movement::end_of_paragraph(map, head, 1),
11922 SelectionGoal::None,
11923 )
11924 });
11925 })
11926 }
11927
11928 pub fn move_to_start_of_excerpt(
11929 &mut self,
11930 _: &MoveToStartOfExcerpt,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11935 cx.propagate();
11936 return;
11937 }
11938 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11939 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11940 s.move_with(|map, selection| {
11941 selection.collapse_to(
11942 movement::start_of_excerpt(
11943 map,
11944 selection.head(),
11945 workspace::searchable::Direction::Prev,
11946 ),
11947 SelectionGoal::None,
11948 )
11949 });
11950 })
11951 }
11952
11953 pub fn move_to_start_of_next_excerpt(
11954 &mut self,
11955 _: &MoveToStartOfNextExcerpt,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11960 cx.propagate();
11961 return;
11962 }
11963
11964 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11965 s.move_with(|map, selection| {
11966 selection.collapse_to(
11967 movement::start_of_excerpt(
11968 map,
11969 selection.head(),
11970 workspace::searchable::Direction::Next,
11971 ),
11972 SelectionGoal::None,
11973 )
11974 });
11975 })
11976 }
11977
11978 pub fn move_to_end_of_excerpt(
11979 &mut self,
11980 _: &MoveToEndOfExcerpt,
11981 window: &mut Window,
11982 cx: &mut Context<Self>,
11983 ) {
11984 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11985 cx.propagate();
11986 return;
11987 }
11988 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11989 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11990 s.move_with(|map, selection| {
11991 selection.collapse_to(
11992 movement::end_of_excerpt(
11993 map,
11994 selection.head(),
11995 workspace::searchable::Direction::Next,
11996 ),
11997 SelectionGoal::None,
11998 )
11999 });
12000 })
12001 }
12002
12003 pub fn move_to_end_of_previous_excerpt(
12004 &mut self,
12005 _: &MoveToEndOfPreviousExcerpt,
12006 window: &mut Window,
12007 cx: &mut Context<Self>,
12008 ) {
12009 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12010 cx.propagate();
12011 return;
12012 }
12013 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12014 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12015 s.move_with(|map, selection| {
12016 selection.collapse_to(
12017 movement::end_of_excerpt(
12018 map,
12019 selection.head(),
12020 workspace::searchable::Direction::Prev,
12021 ),
12022 SelectionGoal::None,
12023 )
12024 });
12025 })
12026 }
12027
12028 pub fn select_to_start_of_excerpt(
12029 &mut self,
12030 _: &SelectToStartOfExcerpt,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12035 cx.propagate();
12036 return;
12037 }
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.move_heads_with(|map, head, _| {
12041 (
12042 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12043 SelectionGoal::None,
12044 )
12045 });
12046 })
12047 }
12048
12049 pub fn select_to_start_of_next_excerpt(
12050 &mut self,
12051 _: &SelectToStartOfNextExcerpt,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12056 cx.propagate();
12057 return;
12058 }
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_heads_with(|map, head, _| {
12062 (
12063 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12064 SelectionGoal::None,
12065 )
12066 });
12067 })
12068 }
12069
12070 pub fn select_to_end_of_excerpt(
12071 &mut self,
12072 _: &SelectToEndOfExcerpt,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12077 cx.propagate();
12078 return;
12079 }
12080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12081 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12082 s.move_heads_with(|map, head, _| {
12083 (
12084 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12085 SelectionGoal::None,
12086 )
12087 });
12088 })
12089 }
12090
12091 pub fn select_to_end_of_previous_excerpt(
12092 &mut self,
12093 _: &SelectToEndOfPreviousExcerpt,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12098 cx.propagate();
12099 return;
12100 }
12101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12103 s.move_heads_with(|map, head, _| {
12104 (
12105 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12106 SelectionGoal::None,
12107 )
12108 });
12109 })
12110 }
12111
12112 pub fn move_to_beginning(
12113 &mut self,
12114 _: &MoveToBeginning,
12115 window: &mut Window,
12116 cx: &mut Context<Self>,
12117 ) {
12118 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12119 cx.propagate();
12120 return;
12121 }
12122 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12123 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12124 s.select_ranges(vec![0..0]);
12125 });
12126 }
12127
12128 pub fn select_to_beginning(
12129 &mut self,
12130 _: &SelectToBeginning,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 let mut selection = self.selections.last::<Point>(cx);
12135 selection.set_head(Point::zero(), SelectionGoal::None);
12136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12137 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12138 s.select(vec![selection]);
12139 });
12140 }
12141
12142 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12143 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12144 cx.propagate();
12145 return;
12146 }
12147 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12148 let cursor = self.buffer.read(cx).read(cx).len();
12149 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12150 s.select_ranges(vec![cursor..cursor])
12151 });
12152 }
12153
12154 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12155 self.nav_history = nav_history;
12156 }
12157
12158 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12159 self.nav_history.as_ref()
12160 }
12161
12162 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12163 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12164 }
12165
12166 fn push_to_nav_history(
12167 &mut self,
12168 cursor_anchor: Anchor,
12169 new_position: Option<Point>,
12170 is_deactivate: bool,
12171 cx: &mut Context<Self>,
12172 ) {
12173 if let Some(nav_history) = self.nav_history.as_mut() {
12174 let buffer = self.buffer.read(cx).read(cx);
12175 let cursor_position = cursor_anchor.to_point(&buffer);
12176 let scroll_state = self.scroll_manager.anchor();
12177 let scroll_top_row = scroll_state.top_row(&buffer);
12178 drop(buffer);
12179
12180 if let Some(new_position) = new_position {
12181 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12182 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12183 return;
12184 }
12185 }
12186
12187 nav_history.push(
12188 Some(NavigationData {
12189 cursor_anchor,
12190 cursor_position,
12191 scroll_anchor: scroll_state,
12192 scroll_top_row,
12193 }),
12194 cx,
12195 );
12196 cx.emit(EditorEvent::PushedToNavHistory {
12197 anchor: cursor_anchor,
12198 is_deactivate,
12199 })
12200 }
12201 }
12202
12203 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12205 let buffer = self.buffer.read(cx).snapshot(cx);
12206 let mut selection = self.selections.first::<usize>(cx);
12207 selection.set_head(buffer.len(), SelectionGoal::None);
12208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12209 s.select(vec![selection]);
12210 });
12211 }
12212
12213 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12215 let end = self.buffer.read(cx).read(cx).len();
12216 self.change_selections(None, window, cx, |s| {
12217 s.select_ranges(vec![0..end]);
12218 });
12219 }
12220
12221 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12222 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12224 let mut selections = self.selections.all::<Point>(cx);
12225 let max_point = display_map.buffer_snapshot.max_point();
12226 for selection in &mut selections {
12227 let rows = selection.spanned_rows(true, &display_map);
12228 selection.start = Point::new(rows.start.0, 0);
12229 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12230 selection.reversed = false;
12231 }
12232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12233 s.select(selections);
12234 });
12235 }
12236
12237 pub fn split_selection_into_lines(
12238 &mut self,
12239 _: &SplitSelectionIntoLines,
12240 window: &mut Window,
12241 cx: &mut Context<Self>,
12242 ) {
12243 let selections = self
12244 .selections
12245 .all::<Point>(cx)
12246 .into_iter()
12247 .map(|selection| selection.start..selection.end)
12248 .collect::<Vec<_>>();
12249 self.unfold_ranges(&selections, true, true, cx);
12250
12251 let mut new_selection_ranges = Vec::new();
12252 {
12253 let buffer = self.buffer.read(cx).read(cx);
12254 for selection in selections {
12255 for row in selection.start.row..selection.end.row {
12256 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12257 new_selection_ranges.push(cursor..cursor);
12258 }
12259
12260 let is_multiline_selection = selection.start.row != selection.end.row;
12261 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12262 // so this action feels more ergonomic when paired with other selection operations
12263 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12264 if !should_skip_last {
12265 new_selection_ranges.push(selection.end..selection.end);
12266 }
12267 }
12268 }
12269 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12270 s.select_ranges(new_selection_ranges);
12271 });
12272 }
12273
12274 pub fn add_selection_above(
12275 &mut self,
12276 _: &AddSelectionAbove,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 self.add_selection(true, window, cx);
12281 }
12282
12283 pub fn add_selection_below(
12284 &mut self,
12285 _: &AddSelectionBelow,
12286 window: &mut Window,
12287 cx: &mut Context<Self>,
12288 ) {
12289 self.add_selection(false, window, cx);
12290 }
12291
12292 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12293 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12294
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 text_layout_details = self.text_layout_details(window);
12298 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12299 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12300 let range = oldest_selection.display_range(&display_map).sorted();
12301
12302 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12303 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12304 let positions = start_x.min(end_x)..start_x.max(end_x);
12305
12306 selections.clear();
12307 let mut stack = Vec::new();
12308 for row in range.start.row().0..=range.end.row().0 {
12309 if let Some(selection) = self.selections.build_columnar_selection(
12310 &display_map,
12311 DisplayRow(row),
12312 &positions,
12313 oldest_selection.reversed,
12314 &text_layout_details,
12315 ) {
12316 stack.push(selection.id);
12317 selections.push(selection);
12318 }
12319 }
12320
12321 if above {
12322 stack.reverse();
12323 }
12324
12325 AddSelectionsState { above, stack }
12326 });
12327
12328 let last_added_selection = *state.stack.last().unwrap();
12329 let mut new_selections = Vec::new();
12330 if above == state.above {
12331 let end_row = if above {
12332 DisplayRow(0)
12333 } else {
12334 display_map.max_point().row()
12335 };
12336
12337 'outer: for selection in selections {
12338 if selection.id == last_added_selection {
12339 let range = selection.display_range(&display_map).sorted();
12340 debug_assert_eq!(range.start.row(), range.end.row());
12341 let mut row = range.start.row();
12342 let positions =
12343 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12344 px(start)..px(end)
12345 } else {
12346 let start_x =
12347 display_map.x_for_display_point(range.start, &text_layout_details);
12348 let end_x =
12349 display_map.x_for_display_point(range.end, &text_layout_details);
12350 start_x.min(end_x)..start_x.max(end_x)
12351 };
12352
12353 while row != end_row {
12354 if above {
12355 row.0 -= 1;
12356 } else {
12357 row.0 += 1;
12358 }
12359
12360 if let Some(new_selection) = self.selections.build_columnar_selection(
12361 &display_map,
12362 row,
12363 &positions,
12364 selection.reversed,
12365 &text_layout_details,
12366 ) {
12367 state.stack.push(new_selection.id);
12368 if above {
12369 new_selections.push(new_selection);
12370 new_selections.push(selection);
12371 } else {
12372 new_selections.push(selection);
12373 new_selections.push(new_selection);
12374 }
12375
12376 continue 'outer;
12377 }
12378 }
12379 }
12380
12381 new_selections.push(selection);
12382 }
12383 } else {
12384 new_selections = selections;
12385 new_selections.retain(|s| s.id != last_added_selection);
12386 state.stack.pop();
12387 }
12388
12389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12390 s.select(new_selections);
12391 });
12392 if state.stack.len() > 1 {
12393 self.add_selections_state = Some(state);
12394 }
12395 }
12396
12397 fn select_match_ranges(
12398 &mut self,
12399 range: Range<usize>,
12400 reversed: bool,
12401 replace_newest: bool,
12402 auto_scroll: Option<Autoscroll>,
12403 window: &mut Window,
12404 cx: &mut Context<Editor>,
12405 ) {
12406 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12407 self.change_selections(auto_scroll, window, cx, |s| {
12408 if replace_newest {
12409 s.delete(s.newest_anchor().id);
12410 }
12411 if reversed {
12412 s.insert_range(range.end..range.start);
12413 } else {
12414 s.insert_range(range);
12415 }
12416 });
12417 }
12418
12419 pub fn select_next_match_internal(
12420 &mut self,
12421 display_map: &DisplaySnapshot,
12422 replace_newest: bool,
12423 autoscroll: Option<Autoscroll>,
12424 window: &mut Window,
12425 cx: &mut Context<Self>,
12426 ) -> Result<()> {
12427 let buffer = &display_map.buffer_snapshot;
12428 let mut selections = self.selections.all::<usize>(cx);
12429 if let Some(mut select_next_state) = self.select_next_state.take() {
12430 let query = &select_next_state.query;
12431 if !select_next_state.done {
12432 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12433 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12434 let mut next_selected_range = None;
12435
12436 let bytes_after_last_selection =
12437 buffer.bytes_in_range(last_selection.end..buffer.len());
12438 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12439 let query_matches = query
12440 .stream_find_iter(bytes_after_last_selection)
12441 .map(|result| (last_selection.end, result))
12442 .chain(
12443 query
12444 .stream_find_iter(bytes_before_first_selection)
12445 .map(|result| (0, result)),
12446 );
12447
12448 for (start_offset, query_match) in query_matches {
12449 let query_match = query_match.unwrap(); // can only fail due to I/O
12450 let offset_range =
12451 start_offset + query_match.start()..start_offset + query_match.end();
12452 let display_range = offset_range.start.to_display_point(display_map)
12453 ..offset_range.end.to_display_point(display_map);
12454
12455 if !select_next_state.wordwise
12456 || (!movement::is_inside_word(display_map, display_range.start)
12457 && !movement::is_inside_word(display_map, display_range.end))
12458 {
12459 // TODO: This is n^2, because we might check all the selections
12460 if !selections
12461 .iter()
12462 .any(|selection| selection.range().overlaps(&offset_range))
12463 {
12464 next_selected_range = Some(offset_range);
12465 break;
12466 }
12467 }
12468 }
12469
12470 if let Some(next_selected_range) = next_selected_range {
12471 self.select_match_ranges(
12472 next_selected_range,
12473 last_selection.reversed,
12474 replace_newest,
12475 autoscroll,
12476 window,
12477 cx,
12478 );
12479 } else {
12480 select_next_state.done = true;
12481 }
12482 }
12483
12484 self.select_next_state = Some(select_next_state);
12485 } else {
12486 let mut only_carets = true;
12487 let mut same_text_selected = true;
12488 let mut selected_text = None;
12489
12490 let mut selections_iter = selections.iter().peekable();
12491 while let Some(selection) = selections_iter.next() {
12492 if selection.start != selection.end {
12493 only_carets = false;
12494 }
12495
12496 if same_text_selected {
12497 if selected_text.is_none() {
12498 selected_text =
12499 Some(buffer.text_for_range(selection.range()).collect::<String>());
12500 }
12501
12502 if let Some(next_selection) = selections_iter.peek() {
12503 if next_selection.range().len() == selection.range().len() {
12504 let next_selected_text = buffer
12505 .text_for_range(next_selection.range())
12506 .collect::<String>();
12507 if Some(next_selected_text) != selected_text {
12508 same_text_selected = false;
12509 selected_text = None;
12510 }
12511 } else {
12512 same_text_selected = false;
12513 selected_text = None;
12514 }
12515 }
12516 }
12517 }
12518
12519 if only_carets {
12520 for selection in &mut selections {
12521 let word_range = movement::surrounding_word(
12522 display_map,
12523 selection.start.to_display_point(display_map),
12524 );
12525 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12526 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12527 selection.goal = SelectionGoal::None;
12528 selection.reversed = false;
12529 self.select_match_ranges(
12530 selection.start..selection.end,
12531 selection.reversed,
12532 replace_newest,
12533 autoscroll,
12534 window,
12535 cx,
12536 );
12537 }
12538
12539 if selections.len() == 1 {
12540 let selection = selections
12541 .last()
12542 .expect("ensured that there's only one selection");
12543 let query = buffer
12544 .text_for_range(selection.start..selection.end)
12545 .collect::<String>();
12546 let is_empty = query.is_empty();
12547 let select_state = SelectNextState {
12548 query: AhoCorasick::new(&[query])?,
12549 wordwise: true,
12550 done: is_empty,
12551 };
12552 self.select_next_state = Some(select_state);
12553 } else {
12554 self.select_next_state = None;
12555 }
12556 } else if let Some(selected_text) = selected_text {
12557 self.select_next_state = Some(SelectNextState {
12558 query: AhoCorasick::new(&[selected_text])?,
12559 wordwise: false,
12560 done: false,
12561 });
12562 self.select_next_match_internal(
12563 display_map,
12564 replace_newest,
12565 autoscroll,
12566 window,
12567 cx,
12568 )?;
12569 }
12570 }
12571 Ok(())
12572 }
12573
12574 pub fn select_all_matches(
12575 &mut self,
12576 _action: &SelectAllMatches,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) -> Result<()> {
12580 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12581
12582 self.push_to_selection_history();
12583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12584
12585 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12586 let Some(select_next_state) = self.select_next_state.as_mut() else {
12587 return Ok(());
12588 };
12589 if select_next_state.done {
12590 return Ok(());
12591 }
12592
12593 let mut new_selections = Vec::new();
12594
12595 let reversed = self.selections.oldest::<usize>(cx).reversed;
12596 let buffer = &display_map.buffer_snapshot;
12597 let query_matches = select_next_state
12598 .query
12599 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12600
12601 for query_match in query_matches.into_iter() {
12602 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12603 let offset_range = if reversed {
12604 query_match.end()..query_match.start()
12605 } else {
12606 query_match.start()..query_match.end()
12607 };
12608 let display_range = offset_range.start.to_display_point(&display_map)
12609 ..offset_range.end.to_display_point(&display_map);
12610
12611 if !select_next_state.wordwise
12612 || (!movement::is_inside_word(&display_map, display_range.start)
12613 && !movement::is_inside_word(&display_map, display_range.end))
12614 {
12615 new_selections.push(offset_range.start..offset_range.end);
12616 }
12617 }
12618
12619 select_next_state.done = true;
12620 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12621 self.change_selections(None, window, cx, |selections| {
12622 selections.select_ranges(new_selections)
12623 });
12624
12625 Ok(())
12626 }
12627
12628 pub fn select_next(
12629 &mut self,
12630 action: &SelectNext,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) -> Result<()> {
12634 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12635 self.push_to_selection_history();
12636 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12637 self.select_next_match_internal(
12638 &display_map,
12639 action.replace_newest,
12640 Some(Autoscroll::newest()),
12641 window,
12642 cx,
12643 )?;
12644 Ok(())
12645 }
12646
12647 pub fn select_previous(
12648 &mut self,
12649 action: &SelectPrevious,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) -> Result<()> {
12653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12654 self.push_to_selection_history();
12655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12656 let buffer = &display_map.buffer_snapshot;
12657 let mut selections = self.selections.all::<usize>(cx);
12658 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12659 let query = &select_prev_state.query;
12660 if !select_prev_state.done {
12661 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12662 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12663 let mut next_selected_range = None;
12664 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12665 let bytes_before_last_selection =
12666 buffer.reversed_bytes_in_range(0..last_selection.start);
12667 let bytes_after_first_selection =
12668 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12669 let query_matches = query
12670 .stream_find_iter(bytes_before_last_selection)
12671 .map(|result| (last_selection.start, result))
12672 .chain(
12673 query
12674 .stream_find_iter(bytes_after_first_selection)
12675 .map(|result| (buffer.len(), result)),
12676 );
12677 for (end_offset, query_match) in query_matches {
12678 let query_match = query_match.unwrap(); // can only fail due to I/O
12679 let offset_range =
12680 end_offset - query_match.end()..end_offset - query_match.start();
12681 let display_range = offset_range.start.to_display_point(&display_map)
12682 ..offset_range.end.to_display_point(&display_map);
12683
12684 if !select_prev_state.wordwise
12685 || (!movement::is_inside_word(&display_map, display_range.start)
12686 && !movement::is_inside_word(&display_map, display_range.end))
12687 {
12688 next_selected_range = Some(offset_range);
12689 break;
12690 }
12691 }
12692
12693 if let Some(next_selected_range) = next_selected_range {
12694 self.select_match_ranges(
12695 next_selected_range,
12696 last_selection.reversed,
12697 action.replace_newest,
12698 Some(Autoscroll::newest()),
12699 window,
12700 cx,
12701 );
12702 } else {
12703 select_prev_state.done = true;
12704 }
12705 }
12706
12707 self.select_prev_state = Some(select_prev_state);
12708 } else {
12709 let mut only_carets = true;
12710 let mut same_text_selected = true;
12711 let mut selected_text = None;
12712
12713 let mut selections_iter = selections.iter().peekable();
12714 while let Some(selection) = selections_iter.next() {
12715 if selection.start != selection.end {
12716 only_carets = false;
12717 }
12718
12719 if same_text_selected {
12720 if selected_text.is_none() {
12721 selected_text =
12722 Some(buffer.text_for_range(selection.range()).collect::<String>());
12723 }
12724
12725 if let Some(next_selection) = selections_iter.peek() {
12726 if next_selection.range().len() == selection.range().len() {
12727 let next_selected_text = buffer
12728 .text_for_range(next_selection.range())
12729 .collect::<String>();
12730 if Some(next_selected_text) != selected_text {
12731 same_text_selected = false;
12732 selected_text = None;
12733 }
12734 } else {
12735 same_text_selected = false;
12736 selected_text = None;
12737 }
12738 }
12739 }
12740 }
12741
12742 if only_carets {
12743 for selection in &mut selections {
12744 let word_range = movement::surrounding_word(
12745 &display_map,
12746 selection.start.to_display_point(&display_map),
12747 );
12748 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12749 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12750 selection.goal = SelectionGoal::None;
12751 selection.reversed = false;
12752 self.select_match_ranges(
12753 selection.start..selection.end,
12754 selection.reversed,
12755 action.replace_newest,
12756 Some(Autoscroll::newest()),
12757 window,
12758 cx,
12759 );
12760 }
12761 if selections.len() == 1 {
12762 let selection = selections
12763 .last()
12764 .expect("ensured that there's only one selection");
12765 let query = buffer
12766 .text_for_range(selection.start..selection.end)
12767 .collect::<String>();
12768 let is_empty = query.is_empty();
12769 let select_state = SelectNextState {
12770 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12771 wordwise: true,
12772 done: is_empty,
12773 };
12774 self.select_prev_state = Some(select_state);
12775 } else {
12776 self.select_prev_state = None;
12777 }
12778 } else if let Some(selected_text) = selected_text {
12779 self.select_prev_state = Some(SelectNextState {
12780 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12781 wordwise: false,
12782 done: false,
12783 });
12784 self.select_previous(action, window, cx)?;
12785 }
12786 }
12787 Ok(())
12788 }
12789
12790 pub fn find_next_match(
12791 &mut self,
12792 _: &FindNextMatch,
12793 window: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) -> Result<()> {
12796 let selections = self.selections.disjoint_anchors();
12797 match selections.first() {
12798 Some(first) if selections.len() >= 2 => {
12799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12800 s.select_ranges([first.range()]);
12801 });
12802 }
12803 _ => self.select_next(
12804 &SelectNext {
12805 replace_newest: true,
12806 },
12807 window,
12808 cx,
12809 )?,
12810 }
12811 Ok(())
12812 }
12813
12814 pub fn find_previous_match(
12815 &mut self,
12816 _: &FindPreviousMatch,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) -> Result<()> {
12820 let selections = self.selections.disjoint_anchors();
12821 match selections.last() {
12822 Some(last) if selections.len() >= 2 => {
12823 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12824 s.select_ranges([last.range()]);
12825 });
12826 }
12827 _ => self.select_previous(
12828 &SelectPrevious {
12829 replace_newest: true,
12830 },
12831 window,
12832 cx,
12833 )?,
12834 }
12835 Ok(())
12836 }
12837
12838 pub fn toggle_comments(
12839 &mut self,
12840 action: &ToggleComments,
12841 window: &mut Window,
12842 cx: &mut Context<Self>,
12843 ) {
12844 if self.read_only(cx) {
12845 return;
12846 }
12847 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12848 let text_layout_details = &self.text_layout_details(window);
12849 self.transact(window, cx, |this, window, cx| {
12850 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12851 let mut edits = Vec::new();
12852 let mut selection_edit_ranges = Vec::new();
12853 let mut last_toggled_row = None;
12854 let snapshot = this.buffer.read(cx).read(cx);
12855 let empty_str: Arc<str> = Arc::default();
12856 let mut suffixes_inserted = Vec::new();
12857 let ignore_indent = action.ignore_indent;
12858
12859 fn comment_prefix_range(
12860 snapshot: &MultiBufferSnapshot,
12861 row: MultiBufferRow,
12862 comment_prefix: &str,
12863 comment_prefix_whitespace: &str,
12864 ignore_indent: bool,
12865 ) -> Range<Point> {
12866 let indent_size = if ignore_indent {
12867 0
12868 } else {
12869 snapshot.indent_size_for_line(row).len
12870 };
12871
12872 let start = Point::new(row.0, indent_size);
12873
12874 let mut line_bytes = snapshot
12875 .bytes_in_range(start..snapshot.max_point())
12876 .flatten()
12877 .copied();
12878
12879 // If this line currently begins with the line comment prefix, then record
12880 // the range containing the prefix.
12881 if line_bytes
12882 .by_ref()
12883 .take(comment_prefix.len())
12884 .eq(comment_prefix.bytes())
12885 {
12886 // Include any whitespace that matches the comment prefix.
12887 let matching_whitespace_len = line_bytes
12888 .zip(comment_prefix_whitespace.bytes())
12889 .take_while(|(a, b)| a == b)
12890 .count() as u32;
12891 let end = Point::new(
12892 start.row,
12893 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12894 );
12895 start..end
12896 } else {
12897 start..start
12898 }
12899 }
12900
12901 fn comment_suffix_range(
12902 snapshot: &MultiBufferSnapshot,
12903 row: MultiBufferRow,
12904 comment_suffix: &str,
12905 comment_suffix_has_leading_space: bool,
12906 ) -> Range<Point> {
12907 let end = Point::new(row.0, snapshot.line_len(row));
12908 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12909
12910 let mut line_end_bytes = snapshot
12911 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12912 .flatten()
12913 .copied();
12914
12915 let leading_space_len = if suffix_start_column > 0
12916 && line_end_bytes.next() == Some(b' ')
12917 && comment_suffix_has_leading_space
12918 {
12919 1
12920 } else {
12921 0
12922 };
12923
12924 // If this line currently begins with the line comment prefix, then record
12925 // the range containing the prefix.
12926 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12927 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12928 start..end
12929 } else {
12930 end..end
12931 }
12932 }
12933
12934 // TODO: Handle selections that cross excerpts
12935 for selection in &mut selections {
12936 let start_column = snapshot
12937 .indent_size_for_line(MultiBufferRow(selection.start.row))
12938 .len;
12939 let language = if let Some(language) =
12940 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12941 {
12942 language
12943 } else {
12944 continue;
12945 };
12946
12947 selection_edit_ranges.clear();
12948
12949 // If multiple selections contain a given row, avoid processing that
12950 // row more than once.
12951 let mut start_row = MultiBufferRow(selection.start.row);
12952 if last_toggled_row == Some(start_row) {
12953 start_row = start_row.next_row();
12954 }
12955 let end_row =
12956 if selection.end.row > selection.start.row && selection.end.column == 0 {
12957 MultiBufferRow(selection.end.row - 1)
12958 } else {
12959 MultiBufferRow(selection.end.row)
12960 };
12961 last_toggled_row = Some(end_row);
12962
12963 if start_row > end_row {
12964 continue;
12965 }
12966
12967 // If the language has line comments, toggle those.
12968 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12969
12970 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12971 if ignore_indent {
12972 full_comment_prefixes = full_comment_prefixes
12973 .into_iter()
12974 .map(|s| Arc::from(s.trim_end()))
12975 .collect();
12976 }
12977
12978 if !full_comment_prefixes.is_empty() {
12979 let first_prefix = full_comment_prefixes
12980 .first()
12981 .expect("prefixes is non-empty");
12982 let prefix_trimmed_lengths = full_comment_prefixes
12983 .iter()
12984 .map(|p| p.trim_end_matches(' ').len())
12985 .collect::<SmallVec<[usize; 4]>>();
12986
12987 let mut all_selection_lines_are_comments = true;
12988
12989 for row in start_row.0..=end_row.0 {
12990 let row = MultiBufferRow(row);
12991 if start_row < end_row && snapshot.is_line_blank(row) {
12992 continue;
12993 }
12994
12995 let prefix_range = full_comment_prefixes
12996 .iter()
12997 .zip(prefix_trimmed_lengths.iter().copied())
12998 .map(|(prefix, trimmed_prefix_len)| {
12999 comment_prefix_range(
13000 snapshot.deref(),
13001 row,
13002 &prefix[..trimmed_prefix_len],
13003 &prefix[trimmed_prefix_len..],
13004 ignore_indent,
13005 )
13006 })
13007 .max_by_key(|range| range.end.column - range.start.column)
13008 .expect("prefixes is non-empty");
13009
13010 if prefix_range.is_empty() {
13011 all_selection_lines_are_comments = false;
13012 }
13013
13014 selection_edit_ranges.push(prefix_range);
13015 }
13016
13017 if all_selection_lines_are_comments {
13018 edits.extend(
13019 selection_edit_ranges
13020 .iter()
13021 .cloned()
13022 .map(|range| (range, empty_str.clone())),
13023 );
13024 } else {
13025 let min_column = selection_edit_ranges
13026 .iter()
13027 .map(|range| range.start.column)
13028 .min()
13029 .unwrap_or(0);
13030 edits.extend(selection_edit_ranges.iter().map(|range| {
13031 let position = Point::new(range.start.row, min_column);
13032 (position..position, first_prefix.clone())
13033 }));
13034 }
13035 } else if let Some((full_comment_prefix, comment_suffix)) =
13036 language.block_comment_delimiters()
13037 {
13038 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13039 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13040 let prefix_range = comment_prefix_range(
13041 snapshot.deref(),
13042 start_row,
13043 comment_prefix,
13044 comment_prefix_whitespace,
13045 ignore_indent,
13046 );
13047 let suffix_range = comment_suffix_range(
13048 snapshot.deref(),
13049 end_row,
13050 comment_suffix.trim_start_matches(' '),
13051 comment_suffix.starts_with(' '),
13052 );
13053
13054 if prefix_range.is_empty() || suffix_range.is_empty() {
13055 edits.push((
13056 prefix_range.start..prefix_range.start,
13057 full_comment_prefix.clone(),
13058 ));
13059 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13060 suffixes_inserted.push((end_row, comment_suffix.len()));
13061 } else {
13062 edits.push((prefix_range, empty_str.clone()));
13063 edits.push((suffix_range, empty_str.clone()));
13064 }
13065 } else {
13066 continue;
13067 }
13068 }
13069
13070 drop(snapshot);
13071 this.buffer.update(cx, |buffer, cx| {
13072 buffer.edit(edits, None, cx);
13073 });
13074
13075 // Adjust selections so that they end before any comment suffixes that
13076 // were inserted.
13077 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13078 let mut selections = this.selections.all::<Point>(cx);
13079 let snapshot = this.buffer.read(cx).read(cx);
13080 for selection in &mut selections {
13081 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13082 match row.cmp(&MultiBufferRow(selection.end.row)) {
13083 Ordering::Less => {
13084 suffixes_inserted.next();
13085 continue;
13086 }
13087 Ordering::Greater => break,
13088 Ordering::Equal => {
13089 if selection.end.column == snapshot.line_len(row) {
13090 if selection.is_empty() {
13091 selection.start.column -= suffix_len as u32;
13092 }
13093 selection.end.column -= suffix_len as u32;
13094 }
13095 break;
13096 }
13097 }
13098 }
13099 }
13100
13101 drop(snapshot);
13102 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13103 s.select(selections)
13104 });
13105
13106 let selections = this.selections.all::<Point>(cx);
13107 let selections_on_single_row = selections.windows(2).all(|selections| {
13108 selections[0].start.row == selections[1].start.row
13109 && selections[0].end.row == selections[1].end.row
13110 && selections[0].start.row == selections[0].end.row
13111 });
13112 let selections_selecting = selections
13113 .iter()
13114 .any(|selection| selection.start != selection.end);
13115 let advance_downwards = action.advance_downwards
13116 && selections_on_single_row
13117 && !selections_selecting
13118 && !matches!(this.mode, EditorMode::SingleLine { .. });
13119
13120 if advance_downwards {
13121 let snapshot = this.buffer.read(cx).snapshot(cx);
13122
13123 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13124 s.move_cursors_with(|display_snapshot, display_point, _| {
13125 let mut point = display_point.to_point(display_snapshot);
13126 point.row += 1;
13127 point = snapshot.clip_point(point, Bias::Left);
13128 let display_point = point.to_display_point(display_snapshot);
13129 let goal = SelectionGoal::HorizontalPosition(
13130 display_snapshot
13131 .x_for_display_point(display_point, text_layout_details)
13132 .into(),
13133 );
13134 (display_point, goal)
13135 })
13136 });
13137 }
13138 });
13139 }
13140
13141 pub fn select_enclosing_symbol(
13142 &mut self,
13143 _: &SelectEnclosingSymbol,
13144 window: &mut Window,
13145 cx: &mut Context<Self>,
13146 ) {
13147 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13148
13149 let buffer = self.buffer.read(cx).snapshot(cx);
13150 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13151
13152 fn update_selection(
13153 selection: &Selection<usize>,
13154 buffer_snap: &MultiBufferSnapshot,
13155 ) -> Option<Selection<usize>> {
13156 let cursor = selection.head();
13157 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13158 for symbol in symbols.iter().rev() {
13159 let start = symbol.range.start.to_offset(buffer_snap);
13160 let end = symbol.range.end.to_offset(buffer_snap);
13161 let new_range = start..end;
13162 if start < selection.start || end > selection.end {
13163 return Some(Selection {
13164 id: selection.id,
13165 start: new_range.start,
13166 end: new_range.end,
13167 goal: SelectionGoal::None,
13168 reversed: selection.reversed,
13169 });
13170 }
13171 }
13172 None
13173 }
13174
13175 let mut selected_larger_symbol = false;
13176 let new_selections = old_selections
13177 .iter()
13178 .map(|selection| match update_selection(selection, &buffer) {
13179 Some(new_selection) => {
13180 if new_selection.range() != selection.range() {
13181 selected_larger_symbol = true;
13182 }
13183 new_selection
13184 }
13185 None => selection.clone(),
13186 })
13187 .collect::<Vec<_>>();
13188
13189 if selected_larger_symbol {
13190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13191 s.select(new_selections);
13192 });
13193 }
13194 }
13195
13196 pub fn select_larger_syntax_node(
13197 &mut self,
13198 _: &SelectLargerSyntaxNode,
13199 window: &mut Window,
13200 cx: &mut Context<Self>,
13201 ) {
13202 let Some(visible_row_count) = self.visible_row_count() else {
13203 return;
13204 };
13205 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13206 if old_selections.is_empty() {
13207 return;
13208 }
13209
13210 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13211
13212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13213 let buffer = self.buffer.read(cx).snapshot(cx);
13214
13215 let mut selected_larger_node = false;
13216 let mut new_selections = old_selections
13217 .iter()
13218 .map(|selection| {
13219 let old_range = selection.start..selection.end;
13220
13221 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13222 // manually select word at selection
13223 if ["string_content", "inline"].contains(&node.kind()) {
13224 let word_range = {
13225 let display_point = buffer
13226 .offset_to_point(old_range.start)
13227 .to_display_point(&display_map);
13228 let Range { start, end } =
13229 movement::surrounding_word(&display_map, display_point);
13230 start.to_point(&display_map).to_offset(&buffer)
13231 ..end.to_point(&display_map).to_offset(&buffer)
13232 };
13233 // ignore if word is already selected
13234 if !word_range.is_empty() && old_range != word_range {
13235 let last_word_range = {
13236 let display_point = buffer
13237 .offset_to_point(old_range.end)
13238 .to_display_point(&display_map);
13239 let Range { start, end } =
13240 movement::surrounding_word(&display_map, display_point);
13241 start.to_point(&display_map).to_offset(&buffer)
13242 ..end.to_point(&display_map).to_offset(&buffer)
13243 };
13244 // only select word if start and end point belongs to same word
13245 if word_range == last_word_range {
13246 selected_larger_node = true;
13247 return Selection {
13248 id: selection.id,
13249 start: word_range.start,
13250 end: word_range.end,
13251 goal: SelectionGoal::None,
13252 reversed: selection.reversed,
13253 };
13254 }
13255 }
13256 }
13257 }
13258
13259 let mut new_range = old_range.clone();
13260 while let Some((_node, containing_range)) =
13261 buffer.syntax_ancestor(new_range.clone())
13262 {
13263 new_range = match containing_range {
13264 MultiOrSingleBufferOffsetRange::Single(_) => break,
13265 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13266 };
13267 if !display_map.intersects_fold(new_range.start)
13268 && !display_map.intersects_fold(new_range.end)
13269 {
13270 break;
13271 }
13272 }
13273
13274 selected_larger_node |= new_range != old_range;
13275 Selection {
13276 id: selection.id,
13277 start: new_range.start,
13278 end: new_range.end,
13279 goal: SelectionGoal::None,
13280 reversed: selection.reversed,
13281 }
13282 })
13283 .collect::<Vec<_>>();
13284
13285 if !selected_larger_node {
13286 return; // don't put this call in the history
13287 }
13288
13289 // scroll based on transformation done to the last selection created by the user
13290 let (last_old, last_new) = old_selections
13291 .last()
13292 .zip(new_selections.last().cloned())
13293 .expect("old_selections isn't empty");
13294
13295 // revert selection
13296 let is_selection_reversed = {
13297 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13298 new_selections.last_mut().expect("checked above").reversed =
13299 should_newest_selection_be_reversed;
13300 should_newest_selection_be_reversed
13301 };
13302
13303 if selected_larger_node {
13304 self.select_syntax_node_history.disable_clearing = true;
13305 self.change_selections(None, window, cx, |s| {
13306 s.select(new_selections.clone());
13307 });
13308 self.select_syntax_node_history.disable_clearing = false;
13309 }
13310
13311 let start_row = last_new.start.to_display_point(&display_map).row().0;
13312 let end_row = last_new.end.to_display_point(&display_map).row().0;
13313 let selection_height = end_row - start_row + 1;
13314 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13315
13316 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13317 let scroll_behavior = if fits_on_the_screen {
13318 self.request_autoscroll(Autoscroll::fit(), cx);
13319 SelectSyntaxNodeScrollBehavior::FitSelection
13320 } else if is_selection_reversed {
13321 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13322 SelectSyntaxNodeScrollBehavior::CursorTop
13323 } else {
13324 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13325 SelectSyntaxNodeScrollBehavior::CursorBottom
13326 };
13327
13328 self.select_syntax_node_history.push((
13329 old_selections,
13330 scroll_behavior,
13331 is_selection_reversed,
13332 ));
13333 }
13334
13335 pub fn select_smaller_syntax_node(
13336 &mut self,
13337 _: &SelectSmallerSyntaxNode,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) {
13341 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13342
13343 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13344 self.select_syntax_node_history.pop()
13345 {
13346 if let Some(selection) = selections.last_mut() {
13347 selection.reversed = is_selection_reversed;
13348 }
13349
13350 self.select_syntax_node_history.disable_clearing = true;
13351 self.change_selections(None, window, cx, |s| {
13352 s.select(selections.to_vec());
13353 });
13354 self.select_syntax_node_history.disable_clearing = false;
13355
13356 match scroll_behavior {
13357 SelectSyntaxNodeScrollBehavior::CursorTop => {
13358 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13359 }
13360 SelectSyntaxNodeScrollBehavior::FitSelection => {
13361 self.request_autoscroll(Autoscroll::fit(), cx);
13362 }
13363 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13364 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13365 }
13366 }
13367 }
13368 }
13369
13370 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13371 if !EditorSettings::get_global(cx).gutter.runnables {
13372 self.clear_tasks();
13373 return Task::ready(());
13374 }
13375 let project = self.project.as_ref().map(Entity::downgrade);
13376 let task_sources = self.lsp_task_sources(cx);
13377 cx.spawn_in(window, async move |editor, cx| {
13378 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13379 let Some(project) = project.and_then(|p| p.upgrade()) else {
13380 return;
13381 };
13382 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13383 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13384 }) else {
13385 return;
13386 };
13387
13388 let hide_runnables = project
13389 .update(cx, |project, cx| {
13390 // Do not display any test indicators in non-dev server remote projects.
13391 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13392 })
13393 .unwrap_or(true);
13394 if hide_runnables {
13395 return;
13396 }
13397 let new_rows =
13398 cx.background_spawn({
13399 let snapshot = display_snapshot.clone();
13400 async move {
13401 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13402 }
13403 })
13404 .await;
13405 let Ok(lsp_tasks) =
13406 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13407 else {
13408 return;
13409 };
13410 let lsp_tasks = lsp_tasks.await;
13411
13412 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13413 lsp_tasks
13414 .into_iter()
13415 .flat_map(|(kind, tasks)| {
13416 tasks.into_iter().filter_map(move |(location, task)| {
13417 Some((kind.clone(), location?, task))
13418 })
13419 })
13420 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13421 let buffer = location.target.buffer;
13422 let buffer_snapshot = buffer.read(cx).snapshot();
13423 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13424 |(excerpt_id, snapshot, _)| {
13425 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13426 display_snapshot
13427 .buffer_snapshot
13428 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13429 } else {
13430 None
13431 }
13432 },
13433 );
13434 if let Some(offset) = offset {
13435 let task_buffer_range =
13436 location.target.range.to_point(&buffer_snapshot);
13437 let context_buffer_range =
13438 task_buffer_range.to_offset(&buffer_snapshot);
13439 let context_range = BufferOffset(context_buffer_range.start)
13440 ..BufferOffset(context_buffer_range.end);
13441
13442 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13443 .or_insert_with(|| RunnableTasks {
13444 templates: Vec::new(),
13445 offset,
13446 column: task_buffer_range.start.column,
13447 extra_variables: HashMap::default(),
13448 context_range,
13449 })
13450 .templates
13451 .push((kind, task.original_task().clone()));
13452 }
13453
13454 acc
13455 })
13456 }) else {
13457 return;
13458 };
13459
13460 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13461 editor
13462 .update(cx, |editor, _| {
13463 editor.clear_tasks();
13464 for (key, mut value) in rows {
13465 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13466 value.templates.extend(lsp_tasks.templates);
13467 }
13468
13469 editor.insert_tasks(key, value);
13470 }
13471 for (key, value) in lsp_tasks_by_rows {
13472 editor.insert_tasks(key, value);
13473 }
13474 })
13475 .ok();
13476 })
13477 }
13478 fn fetch_runnable_ranges(
13479 snapshot: &DisplaySnapshot,
13480 range: Range<Anchor>,
13481 ) -> Vec<language::RunnableRange> {
13482 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13483 }
13484
13485 fn runnable_rows(
13486 project: Entity<Project>,
13487 snapshot: DisplaySnapshot,
13488 runnable_ranges: Vec<RunnableRange>,
13489 mut cx: AsyncWindowContext,
13490 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13491 runnable_ranges
13492 .into_iter()
13493 .filter_map(|mut runnable| {
13494 let tasks = cx
13495 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13496 .ok()?;
13497 if tasks.is_empty() {
13498 return None;
13499 }
13500
13501 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13502
13503 let row = snapshot
13504 .buffer_snapshot
13505 .buffer_line_for_row(MultiBufferRow(point.row))?
13506 .1
13507 .start
13508 .row;
13509
13510 let context_range =
13511 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13512 Some((
13513 (runnable.buffer_id, row),
13514 RunnableTasks {
13515 templates: tasks,
13516 offset: snapshot
13517 .buffer_snapshot
13518 .anchor_before(runnable.run_range.start),
13519 context_range,
13520 column: point.column,
13521 extra_variables: runnable.extra_captures,
13522 },
13523 ))
13524 })
13525 .collect()
13526 }
13527
13528 fn templates_with_tags(
13529 project: &Entity<Project>,
13530 runnable: &mut Runnable,
13531 cx: &mut App,
13532 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13533 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13534 let (worktree_id, file) = project
13535 .buffer_for_id(runnable.buffer, cx)
13536 .and_then(|buffer| buffer.read(cx).file())
13537 .map(|file| (file.worktree_id(cx), file.clone()))
13538 .unzip();
13539
13540 (
13541 project.task_store().read(cx).task_inventory().cloned(),
13542 worktree_id,
13543 file,
13544 )
13545 });
13546
13547 let mut templates_with_tags = mem::take(&mut runnable.tags)
13548 .into_iter()
13549 .flat_map(|RunnableTag(tag)| {
13550 inventory
13551 .as_ref()
13552 .into_iter()
13553 .flat_map(|inventory| {
13554 inventory.read(cx).list_tasks(
13555 file.clone(),
13556 Some(runnable.language.clone()),
13557 worktree_id,
13558 cx,
13559 )
13560 })
13561 .filter(move |(_, template)| {
13562 template.tags.iter().any(|source_tag| source_tag == &tag)
13563 })
13564 })
13565 .sorted_by_key(|(kind, _)| kind.to_owned())
13566 .collect::<Vec<_>>();
13567 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13568 // Strongest source wins; if we have worktree tag binding, prefer that to
13569 // global and language bindings;
13570 // if we have a global binding, prefer that to language binding.
13571 let first_mismatch = templates_with_tags
13572 .iter()
13573 .position(|(tag_source, _)| tag_source != leading_tag_source);
13574 if let Some(index) = first_mismatch {
13575 templates_with_tags.truncate(index);
13576 }
13577 }
13578
13579 templates_with_tags
13580 }
13581
13582 pub fn move_to_enclosing_bracket(
13583 &mut self,
13584 _: &MoveToEnclosingBracket,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13590 s.move_offsets_with(|snapshot, selection| {
13591 let Some(enclosing_bracket_ranges) =
13592 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13593 else {
13594 return;
13595 };
13596
13597 let mut best_length = usize::MAX;
13598 let mut best_inside = false;
13599 let mut best_in_bracket_range = false;
13600 let mut best_destination = None;
13601 for (open, close) in enclosing_bracket_ranges {
13602 let close = close.to_inclusive();
13603 let length = close.end() - open.start;
13604 let inside = selection.start >= open.end && selection.end <= *close.start();
13605 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13606 || close.contains(&selection.head());
13607
13608 // If best is next to a bracket and current isn't, skip
13609 if !in_bracket_range && best_in_bracket_range {
13610 continue;
13611 }
13612
13613 // Prefer smaller lengths unless best is inside and current isn't
13614 if length > best_length && (best_inside || !inside) {
13615 continue;
13616 }
13617
13618 best_length = length;
13619 best_inside = inside;
13620 best_in_bracket_range = in_bracket_range;
13621 best_destination = Some(
13622 if close.contains(&selection.start) && close.contains(&selection.end) {
13623 if inside { open.end } else { open.start }
13624 } else if inside {
13625 *close.start()
13626 } else {
13627 *close.end()
13628 },
13629 );
13630 }
13631
13632 if let Some(destination) = best_destination {
13633 selection.collapse_to(destination, SelectionGoal::None);
13634 }
13635 })
13636 });
13637 }
13638
13639 pub fn undo_selection(
13640 &mut self,
13641 _: &UndoSelection,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13646 self.end_selection(window, cx);
13647 self.selection_history.mode = SelectionHistoryMode::Undoing;
13648 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13649 self.change_selections(None, window, cx, |s| {
13650 s.select_anchors(entry.selections.to_vec())
13651 });
13652 self.select_next_state = entry.select_next_state;
13653 self.select_prev_state = entry.select_prev_state;
13654 self.add_selections_state = entry.add_selections_state;
13655 self.request_autoscroll(Autoscroll::newest(), cx);
13656 }
13657 self.selection_history.mode = SelectionHistoryMode::Normal;
13658 }
13659
13660 pub fn redo_selection(
13661 &mut self,
13662 _: &RedoSelection,
13663 window: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13667 self.end_selection(window, cx);
13668 self.selection_history.mode = SelectionHistoryMode::Redoing;
13669 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13670 self.change_selections(None, window, cx, |s| {
13671 s.select_anchors(entry.selections.to_vec())
13672 });
13673 self.select_next_state = entry.select_next_state;
13674 self.select_prev_state = entry.select_prev_state;
13675 self.add_selections_state = entry.add_selections_state;
13676 self.request_autoscroll(Autoscroll::newest(), cx);
13677 }
13678 self.selection_history.mode = SelectionHistoryMode::Normal;
13679 }
13680
13681 pub fn expand_excerpts(
13682 &mut self,
13683 action: &ExpandExcerpts,
13684 _: &mut Window,
13685 cx: &mut Context<Self>,
13686 ) {
13687 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13688 }
13689
13690 pub fn expand_excerpts_down(
13691 &mut self,
13692 action: &ExpandExcerptsDown,
13693 _: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13697 }
13698
13699 pub fn expand_excerpts_up(
13700 &mut self,
13701 action: &ExpandExcerptsUp,
13702 _: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13706 }
13707
13708 pub fn expand_excerpts_for_direction(
13709 &mut self,
13710 lines: u32,
13711 direction: ExpandExcerptDirection,
13712
13713 cx: &mut Context<Self>,
13714 ) {
13715 let selections = self.selections.disjoint_anchors();
13716
13717 let lines = if lines == 0 {
13718 EditorSettings::get_global(cx).expand_excerpt_lines
13719 } else {
13720 lines
13721 };
13722
13723 self.buffer.update(cx, |buffer, cx| {
13724 let snapshot = buffer.snapshot(cx);
13725 let mut excerpt_ids = selections
13726 .iter()
13727 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13728 .collect::<Vec<_>>();
13729 excerpt_ids.sort();
13730 excerpt_ids.dedup();
13731 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13732 })
13733 }
13734
13735 pub fn expand_excerpt(
13736 &mut self,
13737 excerpt: ExcerptId,
13738 direction: ExpandExcerptDirection,
13739 window: &mut Window,
13740 cx: &mut Context<Self>,
13741 ) {
13742 let current_scroll_position = self.scroll_position(cx);
13743 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13744 let mut should_scroll_up = false;
13745
13746 if direction == ExpandExcerptDirection::Down {
13747 let multi_buffer = self.buffer.read(cx);
13748 let snapshot = multi_buffer.snapshot(cx);
13749 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13750 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13751 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13752 let buffer_snapshot = buffer.read(cx).snapshot();
13753 let excerpt_end_row =
13754 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13755 let last_row = buffer_snapshot.max_point().row;
13756 let lines_below = last_row.saturating_sub(excerpt_end_row);
13757 should_scroll_up = lines_below >= lines_to_expand;
13758 }
13759 }
13760 }
13761 }
13762
13763 self.buffer.update(cx, |buffer, cx| {
13764 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13765 });
13766
13767 if should_scroll_up {
13768 let new_scroll_position =
13769 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13770 self.set_scroll_position(new_scroll_position, window, cx);
13771 }
13772 }
13773
13774 pub fn go_to_singleton_buffer_point(
13775 &mut self,
13776 point: Point,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 self.go_to_singleton_buffer_range(point..point, window, cx);
13781 }
13782
13783 pub fn go_to_singleton_buffer_range(
13784 &mut self,
13785 range: Range<Point>,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) {
13789 let multibuffer = self.buffer().read(cx);
13790 let Some(buffer) = multibuffer.as_singleton() else {
13791 return;
13792 };
13793 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13794 return;
13795 };
13796 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13797 return;
13798 };
13799 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13800 s.select_anchor_ranges([start..end])
13801 });
13802 }
13803
13804 pub fn go_to_diagnostic(
13805 &mut self,
13806 _: &GoToDiagnostic,
13807 window: &mut Window,
13808 cx: &mut Context<Self>,
13809 ) {
13810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13811 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13812 }
13813
13814 pub fn go_to_prev_diagnostic(
13815 &mut self,
13816 _: &GoToPreviousDiagnostic,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13821 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13822 }
13823
13824 pub fn go_to_diagnostic_impl(
13825 &mut self,
13826 direction: Direction,
13827 window: &mut Window,
13828 cx: &mut Context<Self>,
13829 ) {
13830 let buffer = self.buffer.read(cx).snapshot(cx);
13831 let selection = self.selections.newest::<usize>(cx);
13832
13833 let mut active_group_id = None;
13834 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13835 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13836 active_group_id = Some(active_group.group_id);
13837 }
13838 }
13839
13840 fn filtered(
13841 snapshot: EditorSnapshot,
13842 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13843 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13844 diagnostics
13845 .filter(|entry| entry.range.start != entry.range.end)
13846 .filter(|entry| !entry.diagnostic.is_unnecessary)
13847 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13848 }
13849
13850 let snapshot = self.snapshot(window, cx);
13851 let before = filtered(
13852 snapshot.clone(),
13853 buffer
13854 .diagnostics_in_range(0..selection.start)
13855 .filter(|entry| entry.range.start <= selection.start),
13856 );
13857 let after = filtered(
13858 snapshot,
13859 buffer
13860 .diagnostics_in_range(selection.start..buffer.len())
13861 .filter(|entry| entry.range.start >= selection.start),
13862 );
13863
13864 let mut found: Option<DiagnosticEntry<usize>> = None;
13865 if direction == Direction::Prev {
13866 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13867 {
13868 for diagnostic in prev_diagnostics.into_iter().rev() {
13869 if diagnostic.range.start != selection.start
13870 || active_group_id
13871 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13872 {
13873 found = Some(diagnostic);
13874 break 'outer;
13875 }
13876 }
13877 }
13878 } else {
13879 for diagnostic in after.chain(before) {
13880 if diagnostic.range.start != selection.start
13881 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13882 {
13883 found = Some(diagnostic);
13884 break;
13885 }
13886 }
13887 }
13888 let Some(next_diagnostic) = found else {
13889 return;
13890 };
13891
13892 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13893 return;
13894 };
13895 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13896 s.select_ranges(vec![
13897 next_diagnostic.range.start..next_diagnostic.range.start,
13898 ])
13899 });
13900 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13901 self.refresh_inline_completion(false, true, window, cx);
13902 }
13903
13904 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13905 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13906 let snapshot = self.snapshot(window, cx);
13907 let selection = self.selections.newest::<Point>(cx);
13908 self.go_to_hunk_before_or_after_position(
13909 &snapshot,
13910 selection.head(),
13911 Direction::Next,
13912 window,
13913 cx,
13914 );
13915 }
13916
13917 pub fn go_to_hunk_before_or_after_position(
13918 &mut self,
13919 snapshot: &EditorSnapshot,
13920 position: Point,
13921 direction: Direction,
13922 window: &mut Window,
13923 cx: &mut Context<Editor>,
13924 ) {
13925 let row = if direction == Direction::Next {
13926 self.hunk_after_position(snapshot, position)
13927 .map(|hunk| hunk.row_range.start)
13928 } else {
13929 self.hunk_before_position(snapshot, position)
13930 };
13931
13932 if let Some(row) = row {
13933 let destination = Point::new(row.0, 0);
13934 let autoscroll = Autoscroll::center();
13935
13936 self.unfold_ranges(&[destination..destination], false, false, cx);
13937 self.change_selections(Some(autoscroll), window, cx, |s| {
13938 s.select_ranges([destination..destination]);
13939 });
13940 }
13941 }
13942
13943 fn hunk_after_position(
13944 &mut self,
13945 snapshot: &EditorSnapshot,
13946 position: Point,
13947 ) -> Option<MultiBufferDiffHunk> {
13948 snapshot
13949 .buffer_snapshot
13950 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13951 .find(|hunk| hunk.row_range.start.0 > position.row)
13952 .or_else(|| {
13953 snapshot
13954 .buffer_snapshot
13955 .diff_hunks_in_range(Point::zero()..position)
13956 .find(|hunk| hunk.row_range.end.0 < position.row)
13957 })
13958 }
13959
13960 fn go_to_prev_hunk(
13961 &mut self,
13962 _: &GoToPreviousHunk,
13963 window: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13967 let snapshot = self.snapshot(window, cx);
13968 let selection = self.selections.newest::<Point>(cx);
13969 self.go_to_hunk_before_or_after_position(
13970 &snapshot,
13971 selection.head(),
13972 Direction::Prev,
13973 window,
13974 cx,
13975 );
13976 }
13977
13978 fn hunk_before_position(
13979 &mut self,
13980 snapshot: &EditorSnapshot,
13981 position: Point,
13982 ) -> Option<MultiBufferRow> {
13983 snapshot
13984 .buffer_snapshot
13985 .diff_hunk_before(position)
13986 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13987 }
13988
13989 fn go_to_next_change(
13990 &mut self,
13991 _: &GoToNextChange,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 if let Some(selections) = self
13996 .change_list
13997 .next_change(1, Direction::Next)
13998 .map(|s| s.to_vec())
13999 {
14000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14001 let map = s.display_map();
14002 s.select_display_ranges(selections.iter().map(|a| {
14003 let point = a.to_display_point(&map);
14004 point..point
14005 }))
14006 })
14007 }
14008 }
14009
14010 fn go_to_previous_change(
14011 &mut self,
14012 _: &GoToPreviousChange,
14013 window: &mut Window,
14014 cx: &mut Context<Self>,
14015 ) {
14016 if let Some(selections) = self
14017 .change_list
14018 .next_change(1, Direction::Prev)
14019 .map(|s| s.to_vec())
14020 {
14021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14022 let map = s.display_map();
14023 s.select_display_ranges(selections.iter().map(|a| {
14024 let point = a.to_display_point(&map);
14025 point..point
14026 }))
14027 })
14028 }
14029 }
14030
14031 fn go_to_line<T: 'static>(
14032 &mut self,
14033 position: Anchor,
14034 highlight_color: Option<Hsla>,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 let snapshot = self.snapshot(window, cx).display_snapshot;
14039 let position = position.to_point(&snapshot.buffer_snapshot);
14040 let start = snapshot
14041 .buffer_snapshot
14042 .clip_point(Point::new(position.row, 0), Bias::Left);
14043 let end = start + Point::new(1, 0);
14044 let start = snapshot.buffer_snapshot.anchor_before(start);
14045 let end = snapshot.buffer_snapshot.anchor_before(end);
14046
14047 self.highlight_rows::<T>(
14048 start..end,
14049 highlight_color
14050 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14051 Default::default(),
14052 cx,
14053 );
14054
14055 if self.buffer.read(cx).is_singleton() {
14056 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14057 }
14058 }
14059
14060 pub fn go_to_definition(
14061 &mut self,
14062 _: &GoToDefinition,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) -> Task<Result<Navigated>> {
14066 let definition =
14067 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14068 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14069 cx.spawn_in(window, async move |editor, cx| {
14070 if definition.await? == Navigated::Yes {
14071 return Ok(Navigated::Yes);
14072 }
14073 match fallback_strategy {
14074 GoToDefinitionFallback::None => Ok(Navigated::No),
14075 GoToDefinitionFallback::FindAllReferences => {
14076 match editor.update_in(cx, |editor, window, cx| {
14077 editor.find_all_references(&FindAllReferences, window, cx)
14078 })? {
14079 Some(references) => references.await,
14080 None => Ok(Navigated::No),
14081 }
14082 }
14083 }
14084 })
14085 }
14086
14087 pub fn go_to_declaration(
14088 &mut self,
14089 _: &GoToDeclaration,
14090 window: &mut Window,
14091 cx: &mut Context<Self>,
14092 ) -> Task<Result<Navigated>> {
14093 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14094 }
14095
14096 pub fn go_to_declaration_split(
14097 &mut self,
14098 _: &GoToDeclaration,
14099 window: &mut Window,
14100 cx: &mut Context<Self>,
14101 ) -> Task<Result<Navigated>> {
14102 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14103 }
14104
14105 pub fn go_to_implementation(
14106 &mut self,
14107 _: &GoToImplementation,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) -> Task<Result<Navigated>> {
14111 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14112 }
14113
14114 pub fn go_to_implementation_split(
14115 &mut self,
14116 _: &GoToImplementationSplit,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) -> Task<Result<Navigated>> {
14120 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14121 }
14122
14123 pub fn go_to_type_definition(
14124 &mut self,
14125 _: &GoToTypeDefinition,
14126 window: &mut Window,
14127 cx: &mut Context<Self>,
14128 ) -> Task<Result<Navigated>> {
14129 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14130 }
14131
14132 pub fn go_to_definition_split(
14133 &mut self,
14134 _: &GoToDefinitionSplit,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) -> Task<Result<Navigated>> {
14138 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14139 }
14140
14141 pub fn go_to_type_definition_split(
14142 &mut self,
14143 _: &GoToTypeDefinitionSplit,
14144 window: &mut Window,
14145 cx: &mut Context<Self>,
14146 ) -> Task<Result<Navigated>> {
14147 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14148 }
14149
14150 fn go_to_definition_of_kind(
14151 &mut self,
14152 kind: GotoDefinitionKind,
14153 split: bool,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) -> Task<Result<Navigated>> {
14157 let Some(provider) = self.semantics_provider.clone() else {
14158 return Task::ready(Ok(Navigated::No));
14159 };
14160 let head = self.selections.newest::<usize>(cx).head();
14161 let buffer = self.buffer.read(cx);
14162 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14163 text_anchor
14164 } else {
14165 return Task::ready(Ok(Navigated::No));
14166 };
14167
14168 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14169 return Task::ready(Ok(Navigated::No));
14170 };
14171
14172 cx.spawn_in(window, async move |editor, cx| {
14173 let definitions = definitions.await?;
14174 let navigated = editor
14175 .update_in(cx, |editor, window, cx| {
14176 editor.navigate_to_hover_links(
14177 Some(kind),
14178 definitions
14179 .into_iter()
14180 .filter(|location| {
14181 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14182 })
14183 .map(HoverLink::Text)
14184 .collect::<Vec<_>>(),
14185 split,
14186 window,
14187 cx,
14188 )
14189 })?
14190 .await?;
14191 anyhow::Ok(navigated)
14192 })
14193 }
14194
14195 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14196 let selection = self.selections.newest_anchor();
14197 let head = selection.head();
14198 let tail = selection.tail();
14199
14200 let Some((buffer, start_position)) =
14201 self.buffer.read(cx).text_anchor_for_position(head, cx)
14202 else {
14203 return;
14204 };
14205
14206 let end_position = if head != tail {
14207 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14208 return;
14209 };
14210 Some(pos)
14211 } else {
14212 None
14213 };
14214
14215 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14216 let url = if let Some(end_pos) = end_position {
14217 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14218 } else {
14219 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14220 };
14221
14222 if let Some(url) = url {
14223 editor.update(cx, |_, cx| {
14224 cx.open_url(&url);
14225 })
14226 } else {
14227 Ok(())
14228 }
14229 });
14230
14231 url_finder.detach();
14232 }
14233
14234 pub fn open_selected_filename(
14235 &mut self,
14236 _: &OpenSelectedFilename,
14237 window: &mut Window,
14238 cx: &mut Context<Self>,
14239 ) {
14240 let Some(workspace) = self.workspace() else {
14241 return;
14242 };
14243
14244 let position = self.selections.newest_anchor().head();
14245
14246 let Some((buffer, buffer_position)) =
14247 self.buffer.read(cx).text_anchor_for_position(position, cx)
14248 else {
14249 return;
14250 };
14251
14252 let project = self.project.clone();
14253
14254 cx.spawn_in(window, async move |_, cx| {
14255 let result = find_file(&buffer, project, buffer_position, cx).await;
14256
14257 if let Some((_, path)) = result {
14258 workspace
14259 .update_in(cx, |workspace, window, cx| {
14260 workspace.open_resolved_path(path, window, cx)
14261 })?
14262 .await?;
14263 }
14264 anyhow::Ok(())
14265 })
14266 .detach();
14267 }
14268
14269 pub(crate) fn navigate_to_hover_links(
14270 &mut self,
14271 kind: Option<GotoDefinitionKind>,
14272 mut definitions: Vec<HoverLink>,
14273 split: bool,
14274 window: &mut Window,
14275 cx: &mut Context<Editor>,
14276 ) -> Task<Result<Navigated>> {
14277 // If there is one definition, just open it directly
14278 if definitions.len() == 1 {
14279 let definition = definitions.pop().unwrap();
14280
14281 enum TargetTaskResult {
14282 Location(Option<Location>),
14283 AlreadyNavigated,
14284 }
14285
14286 let target_task = match definition {
14287 HoverLink::Text(link) => {
14288 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14289 }
14290 HoverLink::InlayHint(lsp_location, server_id) => {
14291 let computation =
14292 self.compute_target_location(lsp_location, server_id, window, cx);
14293 cx.background_spawn(async move {
14294 let location = computation.await?;
14295 Ok(TargetTaskResult::Location(location))
14296 })
14297 }
14298 HoverLink::Url(url) => {
14299 cx.open_url(&url);
14300 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14301 }
14302 HoverLink::File(path) => {
14303 if let Some(workspace) = self.workspace() {
14304 cx.spawn_in(window, async move |_, cx| {
14305 workspace
14306 .update_in(cx, |workspace, window, cx| {
14307 workspace.open_resolved_path(path, window, cx)
14308 })?
14309 .await
14310 .map(|_| TargetTaskResult::AlreadyNavigated)
14311 })
14312 } else {
14313 Task::ready(Ok(TargetTaskResult::Location(None)))
14314 }
14315 }
14316 };
14317 cx.spawn_in(window, async move |editor, cx| {
14318 let target = match target_task.await.context("target resolution task")? {
14319 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14320 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14321 TargetTaskResult::Location(Some(target)) => target,
14322 };
14323
14324 editor.update_in(cx, |editor, window, cx| {
14325 let Some(workspace) = editor.workspace() else {
14326 return Navigated::No;
14327 };
14328 let pane = workspace.read(cx).active_pane().clone();
14329
14330 let range = target.range.to_point(target.buffer.read(cx));
14331 let range = editor.range_for_match(&range);
14332 let range = collapse_multiline_range(range);
14333
14334 if !split
14335 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14336 {
14337 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14338 } else {
14339 window.defer(cx, move |window, cx| {
14340 let target_editor: Entity<Self> =
14341 workspace.update(cx, |workspace, cx| {
14342 let pane = if split {
14343 workspace.adjacent_pane(window, cx)
14344 } else {
14345 workspace.active_pane().clone()
14346 };
14347
14348 workspace.open_project_item(
14349 pane,
14350 target.buffer.clone(),
14351 true,
14352 true,
14353 window,
14354 cx,
14355 )
14356 });
14357 target_editor.update(cx, |target_editor, cx| {
14358 // When selecting a definition in a different buffer, disable the nav history
14359 // to avoid creating a history entry at the previous cursor location.
14360 pane.update(cx, |pane, _| pane.disable_history());
14361 target_editor.go_to_singleton_buffer_range(range, window, cx);
14362 pane.update(cx, |pane, _| pane.enable_history());
14363 });
14364 });
14365 }
14366 Navigated::Yes
14367 })
14368 })
14369 } else if !definitions.is_empty() {
14370 cx.spawn_in(window, async move |editor, cx| {
14371 let (title, location_tasks, workspace) = editor
14372 .update_in(cx, |editor, window, cx| {
14373 let tab_kind = match kind {
14374 Some(GotoDefinitionKind::Implementation) => "Implementations",
14375 _ => "Definitions",
14376 };
14377 let title = definitions
14378 .iter()
14379 .find_map(|definition| match definition {
14380 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14381 let buffer = origin.buffer.read(cx);
14382 format!(
14383 "{} for {}",
14384 tab_kind,
14385 buffer
14386 .text_for_range(origin.range.clone())
14387 .collect::<String>()
14388 )
14389 }),
14390 HoverLink::InlayHint(_, _) => None,
14391 HoverLink::Url(_) => None,
14392 HoverLink::File(_) => None,
14393 })
14394 .unwrap_or(tab_kind.to_string());
14395 let location_tasks = definitions
14396 .into_iter()
14397 .map(|definition| match definition {
14398 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14399 HoverLink::InlayHint(lsp_location, server_id) => editor
14400 .compute_target_location(lsp_location, server_id, window, cx),
14401 HoverLink::Url(_) => Task::ready(Ok(None)),
14402 HoverLink::File(_) => Task::ready(Ok(None)),
14403 })
14404 .collect::<Vec<_>>();
14405 (title, location_tasks, editor.workspace().clone())
14406 })
14407 .context("location tasks preparation")?;
14408
14409 let locations = future::join_all(location_tasks)
14410 .await
14411 .into_iter()
14412 .filter_map(|location| location.transpose())
14413 .collect::<Result<_>>()
14414 .context("location tasks")?;
14415
14416 let Some(workspace) = workspace else {
14417 return Ok(Navigated::No);
14418 };
14419 let opened = workspace
14420 .update_in(cx, |workspace, window, cx| {
14421 Self::open_locations_in_multibuffer(
14422 workspace,
14423 locations,
14424 title,
14425 split,
14426 MultibufferSelectionMode::First,
14427 window,
14428 cx,
14429 )
14430 })
14431 .ok();
14432
14433 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14434 })
14435 } else {
14436 Task::ready(Ok(Navigated::No))
14437 }
14438 }
14439
14440 fn compute_target_location(
14441 &self,
14442 lsp_location: lsp::Location,
14443 server_id: LanguageServerId,
14444 window: &mut Window,
14445 cx: &mut Context<Self>,
14446 ) -> Task<anyhow::Result<Option<Location>>> {
14447 let Some(project) = self.project.clone() else {
14448 return Task::ready(Ok(None));
14449 };
14450
14451 cx.spawn_in(window, async move |editor, cx| {
14452 let location_task = editor.update(cx, |_, cx| {
14453 project.update(cx, |project, cx| {
14454 let language_server_name = project
14455 .language_server_statuses(cx)
14456 .find(|(id, _)| server_id == *id)
14457 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14458 language_server_name.map(|language_server_name| {
14459 project.open_local_buffer_via_lsp(
14460 lsp_location.uri.clone(),
14461 server_id,
14462 language_server_name,
14463 cx,
14464 )
14465 })
14466 })
14467 })?;
14468 let location = match location_task {
14469 Some(task) => Some({
14470 let target_buffer_handle = task.await.context("open local buffer")?;
14471 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14472 let target_start = target_buffer
14473 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14474 let target_end = target_buffer
14475 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14476 target_buffer.anchor_after(target_start)
14477 ..target_buffer.anchor_before(target_end)
14478 })?;
14479 Location {
14480 buffer: target_buffer_handle,
14481 range,
14482 }
14483 }),
14484 None => None,
14485 };
14486 Ok(location)
14487 })
14488 }
14489
14490 pub fn find_all_references(
14491 &mut self,
14492 _: &FindAllReferences,
14493 window: &mut Window,
14494 cx: &mut Context<Self>,
14495 ) -> Option<Task<Result<Navigated>>> {
14496 let selection = self.selections.newest::<usize>(cx);
14497 let multi_buffer = self.buffer.read(cx);
14498 let head = selection.head();
14499
14500 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14501 let head_anchor = multi_buffer_snapshot.anchor_at(
14502 head,
14503 if head < selection.tail() {
14504 Bias::Right
14505 } else {
14506 Bias::Left
14507 },
14508 );
14509
14510 match self
14511 .find_all_references_task_sources
14512 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14513 {
14514 Ok(_) => {
14515 log::info!(
14516 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14517 );
14518 return None;
14519 }
14520 Err(i) => {
14521 self.find_all_references_task_sources.insert(i, head_anchor);
14522 }
14523 }
14524
14525 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14526 let workspace = self.workspace()?;
14527 let project = workspace.read(cx).project().clone();
14528 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14529 Some(cx.spawn_in(window, async move |editor, cx| {
14530 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14531 if let Ok(i) = editor
14532 .find_all_references_task_sources
14533 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14534 {
14535 editor.find_all_references_task_sources.remove(i);
14536 }
14537 });
14538
14539 let locations = references.await?;
14540 if locations.is_empty() {
14541 return anyhow::Ok(Navigated::No);
14542 }
14543
14544 workspace.update_in(cx, |workspace, window, cx| {
14545 let title = locations
14546 .first()
14547 .as_ref()
14548 .map(|location| {
14549 let buffer = location.buffer.read(cx);
14550 format!(
14551 "References to `{}`",
14552 buffer
14553 .text_for_range(location.range.clone())
14554 .collect::<String>()
14555 )
14556 })
14557 .unwrap();
14558 Self::open_locations_in_multibuffer(
14559 workspace,
14560 locations,
14561 title,
14562 false,
14563 MultibufferSelectionMode::First,
14564 window,
14565 cx,
14566 );
14567 Navigated::Yes
14568 })
14569 }))
14570 }
14571
14572 /// Opens a multibuffer with the given project locations in it
14573 pub fn open_locations_in_multibuffer(
14574 workspace: &mut Workspace,
14575 mut locations: Vec<Location>,
14576 title: String,
14577 split: bool,
14578 multibuffer_selection_mode: MultibufferSelectionMode,
14579 window: &mut Window,
14580 cx: &mut Context<Workspace>,
14581 ) {
14582 // If there are multiple definitions, open them in a multibuffer
14583 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14584 let mut locations = locations.into_iter().peekable();
14585 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14586 let capability = workspace.project().read(cx).capability();
14587
14588 let excerpt_buffer = cx.new(|cx| {
14589 let mut multibuffer = MultiBuffer::new(capability);
14590 while let Some(location) = locations.next() {
14591 let buffer = location.buffer.read(cx);
14592 let mut ranges_for_buffer = Vec::new();
14593 let range = location.range.to_point(buffer);
14594 ranges_for_buffer.push(range.clone());
14595
14596 while let Some(next_location) = locations.peek() {
14597 if next_location.buffer == location.buffer {
14598 ranges_for_buffer.push(next_location.range.to_point(buffer));
14599 locations.next();
14600 } else {
14601 break;
14602 }
14603 }
14604
14605 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14606 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14607 PathKey::for_buffer(&location.buffer, cx),
14608 location.buffer.clone(),
14609 ranges_for_buffer,
14610 DEFAULT_MULTIBUFFER_CONTEXT,
14611 cx,
14612 );
14613 ranges.extend(new_ranges)
14614 }
14615
14616 multibuffer.with_title(title)
14617 });
14618
14619 let editor = cx.new(|cx| {
14620 Editor::for_multibuffer(
14621 excerpt_buffer,
14622 Some(workspace.project().clone()),
14623 window,
14624 cx,
14625 )
14626 });
14627 editor.update(cx, |editor, cx| {
14628 match multibuffer_selection_mode {
14629 MultibufferSelectionMode::First => {
14630 if let Some(first_range) = ranges.first() {
14631 editor.change_selections(None, window, cx, |selections| {
14632 selections.clear_disjoint();
14633 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14634 });
14635 }
14636 editor.highlight_background::<Self>(
14637 &ranges,
14638 |theme| theme.editor_highlighted_line_background,
14639 cx,
14640 );
14641 }
14642 MultibufferSelectionMode::All => {
14643 editor.change_selections(None, window, cx, |selections| {
14644 selections.clear_disjoint();
14645 selections.select_anchor_ranges(ranges);
14646 });
14647 }
14648 }
14649 editor.register_buffers_with_language_servers(cx);
14650 });
14651
14652 let item = Box::new(editor);
14653 let item_id = item.item_id();
14654
14655 if split {
14656 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14657 } else {
14658 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14659 let (preview_item_id, preview_item_idx) =
14660 workspace.active_pane().update(cx, |pane, _| {
14661 (pane.preview_item_id(), pane.preview_item_idx())
14662 });
14663
14664 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14665
14666 if let Some(preview_item_id) = preview_item_id {
14667 workspace.active_pane().update(cx, |pane, cx| {
14668 pane.remove_item(preview_item_id, false, false, window, cx);
14669 });
14670 }
14671 } else {
14672 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14673 }
14674 }
14675 workspace.active_pane().update(cx, |pane, cx| {
14676 pane.set_preview_item_id(Some(item_id), cx);
14677 });
14678 }
14679
14680 pub fn rename(
14681 &mut self,
14682 _: &Rename,
14683 window: &mut Window,
14684 cx: &mut Context<Self>,
14685 ) -> Option<Task<Result<()>>> {
14686 use language::ToOffset as _;
14687
14688 let provider = self.semantics_provider.clone()?;
14689 let selection = self.selections.newest_anchor().clone();
14690 let (cursor_buffer, cursor_buffer_position) = self
14691 .buffer
14692 .read(cx)
14693 .text_anchor_for_position(selection.head(), cx)?;
14694 let (tail_buffer, cursor_buffer_position_end) = self
14695 .buffer
14696 .read(cx)
14697 .text_anchor_for_position(selection.tail(), cx)?;
14698 if tail_buffer != cursor_buffer {
14699 return None;
14700 }
14701
14702 let snapshot = cursor_buffer.read(cx).snapshot();
14703 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14704 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14705 let prepare_rename = provider
14706 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14707 .unwrap_or_else(|| Task::ready(Ok(None)));
14708 drop(snapshot);
14709
14710 Some(cx.spawn_in(window, async move |this, cx| {
14711 let rename_range = if let Some(range) = prepare_rename.await? {
14712 Some(range)
14713 } else {
14714 this.update(cx, |this, cx| {
14715 let buffer = this.buffer.read(cx).snapshot(cx);
14716 let mut buffer_highlights = this
14717 .document_highlights_for_position(selection.head(), &buffer)
14718 .filter(|highlight| {
14719 highlight.start.excerpt_id == selection.head().excerpt_id
14720 && highlight.end.excerpt_id == selection.head().excerpt_id
14721 });
14722 buffer_highlights
14723 .next()
14724 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14725 })?
14726 };
14727 if let Some(rename_range) = rename_range {
14728 this.update_in(cx, |this, window, cx| {
14729 let snapshot = cursor_buffer.read(cx).snapshot();
14730 let rename_buffer_range = rename_range.to_offset(&snapshot);
14731 let cursor_offset_in_rename_range =
14732 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14733 let cursor_offset_in_rename_range_end =
14734 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14735
14736 this.take_rename(false, window, cx);
14737 let buffer = this.buffer.read(cx).read(cx);
14738 let cursor_offset = selection.head().to_offset(&buffer);
14739 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14740 let rename_end = rename_start + rename_buffer_range.len();
14741 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14742 let mut old_highlight_id = None;
14743 let old_name: Arc<str> = buffer
14744 .chunks(rename_start..rename_end, true)
14745 .map(|chunk| {
14746 if old_highlight_id.is_none() {
14747 old_highlight_id = chunk.syntax_highlight_id;
14748 }
14749 chunk.text
14750 })
14751 .collect::<String>()
14752 .into();
14753
14754 drop(buffer);
14755
14756 // Position the selection in the rename editor so that it matches the current selection.
14757 this.show_local_selections = false;
14758 let rename_editor = cx.new(|cx| {
14759 let mut editor = Editor::single_line(window, cx);
14760 editor.buffer.update(cx, |buffer, cx| {
14761 buffer.edit([(0..0, old_name.clone())], None, cx)
14762 });
14763 let rename_selection_range = match cursor_offset_in_rename_range
14764 .cmp(&cursor_offset_in_rename_range_end)
14765 {
14766 Ordering::Equal => {
14767 editor.select_all(&SelectAll, window, cx);
14768 return editor;
14769 }
14770 Ordering::Less => {
14771 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14772 }
14773 Ordering::Greater => {
14774 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14775 }
14776 };
14777 if rename_selection_range.end > old_name.len() {
14778 editor.select_all(&SelectAll, window, cx);
14779 } else {
14780 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14781 s.select_ranges([rename_selection_range]);
14782 });
14783 }
14784 editor
14785 });
14786 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14787 if e == &EditorEvent::Focused {
14788 cx.emit(EditorEvent::FocusedIn)
14789 }
14790 })
14791 .detach();
14792
14793 let write_highlights =
14794 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14795 let read_highlights =
14796 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14797 let ranges = write_highlights
14798 .iter()
14799 .flat_map(|(_, ranges)| ranges.iter())
14800 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14801 .cloned()
14802 .collect();
14803
14804 this.highlight_text::<Rename>(
14805 ranges,
14806 HighlightStyle {
14807 fade_out: Some(0.6),
14808 ..Default::default()
14809 },
14810 cx,
14811 );
14812 let rename_focus_handle = rename_editor.focus_handle(cx);
14813 window.focus(&rename_focus_handle);
14814 let block_id = this.insert_blocks(
14815 [BlockProperties {
14816 style: BlockStyle::Flex,
14817 placement: BlockPlacement::Below(range.start),
14818 height: Some(1),
14819 render: Arc::new({
14820 let rename_editor = rename_editor.clone();
14821 move |cx: &mut BlockContext| {
14822 let mut text_style = cx.editor_style.text.clone();
14823 if let Some(highlight_style) = old_highlight_id
14824 .and_then(|h| h.style(&cx.editor_style.syntax))
14825 {
14826 text_style = text_style.highlight(highlight_style);
14827 }
14828 div()
14829 .block_mouse_down()
14830 .pl(cx.anchor_x)
14831 .child(EditorElement::new(
14832 &rename_editor,
14833 EditorStyle {
14834 background: cx.theme().system().transparent,
14835 local_player: cx.editor_style.local_player,
14836 text: text_style,
14837 scrollbar_width: cx.editor_style.scrollbar_width,
14838 syntax: cx.editor_style.syntax.clone(),
14839 status: cx.editor_style.status.clone(),
14840 inlay_hints_style: HighlightStyle {
14841 font_weight: Some(FontWeight::BOLD),
14842 ..make_inlay_hints_style(cx.app)
14843 },
14844 inline_completion_styles: make_suggestion_styles(
14845 cx.app,
14846 ),
14847 ..EditorStyle::default()
14848 },
14849 ))
14850 .into_any_element()
14851 }
14852 }),
14853 priority: 0,
14854 render_in_minimap: true,
14855 }],
14856 Some(Autoscroll::fit()),
14857 cx,
14858 )[0];
14859 this.pending_rename = Some(RenameState {
14860 range,
14861 old_name,
14862 editor: rename_editor,
14863 block_id,
14864 });
14865 })?;
14866 }
14867
14868 Ok(())
14869 }))
14870 }
14871
14872 pub fn confirm_rename(
14873 &mut self,
14874 _: &ConfirmRename,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) -> Option<Task<Result<()>>> {
14878 let rename = self.take_rename(false, window, cx)?;
14879 let workspace = self.workspace()?.downgrade();
14880 let (buffer, start) = self
14881 .buffer
14882 .read(cx)
14883 .text_anchor_for_position(rename.range.start, cx)?;
14884 let (end_buffer, _) = self
14885 .buffer
14886 .read(cx)
14887 .text_anchor_for_position(rename.range.end, cx)?;
14888 if buffer != end_buffer {
14889 return None;
14890 }
14891
14892 let old_name = rename.old_name;
14893 let new_name = rename.editor.read(cx).text(cx);
14894
14895 let rename = self.semantics_provider.as_ref()?.perform_rename(
14896 &buffer,
14897 start,
14898 new_name.clone(),
14899 cx,
14900 )?;
14901
14902 Some(cx.spawn_in(window, async move |editor, cx| {
14903 let project_transaction = rename.await?;
14904 Self::open_project_transaction(
14905 &editor,
14906 workspace,
14907 project_transaction,
14908 format!("Rename: {} → {}", old_name, new_name),
14909 cx,
14910 )
14911 .await?;
14912
14913 editor.update(cx, |editor, cx| {
14914 editor.refresh_document_highlights(cx);
14915 })?;
14916 Ok(())
14917 }))
14918 }
14919
14920 fn take_rename(
14921 &mut self,
14922 moving_cursor: bool,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) -> Option<RenameState> {
14926 let rename = self.pending_rename.take()?;
14927 if rename.editor.focus_handle(cx).is_focused(window) {
14928 window.focus(&self.focus_handle);
14929 }
14930
14931 self.remove_blocks(
14932 [rename.block_id].into_iter().collect(),
14933 Some(Autoscroll::fit()),
14934 cx,
14935 );
14936 self.clear_highlights::<Rename>(cx);
14937 self.show_local_selections = true;
14938
14939 if moving_cursor {
14940 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14941 editor.selections.newest::<usize>(cx).head()
14942 });
14943
14944 // Update the selection to match the position of the selection inside
14945 // the rename editor.
14946 let snapshot = self.buffer.read(cx).read(cx);
14947 let rename_range = rename.range.to_offset(&snapshot);
14948 let cursor_in_editor = snapshot
14949 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14950 .min(rename_range.end);
14951 drop(snapshot);
14952
14953 self.change_selections(None, window, cx, |s| {
14954 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14955 });
14956 } else {
14957 self.refresh_document_highlights(cx);
14958 }
14959
14960 Some(rename)
14961 }
14962
14963 pub fn pending_rename(&self) -> Option<&RenameState> {
14964 self.pending_rename.as_ref()
14965 }
14966
14967 fn format(
14968 &mut self,
14969 _: &Format,
14970 window: &mut Window,
14971 cx: &mut Context<Self>,
14972 ) -> Option<Task<Result<()>>> {
14973 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14974
14975 let project = match &self.project {
14976 Some(project) => project.clone(),
14977 None => return None,
14978 };
14979
14980 Some(self.perform_format(
14981 project,
14982 FormatTrigger::Manual,
14983 FormatTarget::Buffers,
14984 window,
14985 cx,
14986 ))
14987 }
14988
14989 fn format_selections(
14990 &mut self,
14991 _: &FormatSelections,
14992 window: &mut Window,
14993 cx: &mut Context<Self>,
14994 ) -> Option<Task<Result<()>>> {
14995 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14996
14997 let project = match &self.project {
14998 Some(project) => project.clone(),
14999 None => return None,
15000 };
15001
15002 let ranges = self
15003 .selections
15004 .all_adjusted(cx)
15005 .into_iter()
15006 .map(|selection| selection.range())
15007 .collect_vec();
15008
15009 Some(self.perform_format(
15010 project,
15011 FormatTrigger::Manual,
15012 FormatTarget::Ranges(ranges),
15013 window,
15014 cx,
15015 ))
15016 }
15017
15018 fn perform_format(
15019 &mut self,
15020 project: Entity<Project>,
15021 trigger: FormatTrigger,
15022 target: FormatTarget,
15023 window: &mut Window,
15024 cx: &mut Context<Self>,
15025 ) -> Task<Result<()>> {
15026 let buffer = self.buffer.clone();
15027 let (buffers, target) = match target {
15028 FormatTarget::Buffers => {
15029 let mut buffers = buffer.read(cx).all_buffers();
15030 if trigger == FormatTrigger::Save {
15031 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15032 }
15033 (buffers, LspFormatTarget::Buffers)
15034 }
15035 FormatTarget::Ranges(selection_ranges) => {
15036 let multi_buffer = buffer.read(cx);
15037 let snapshot = multi_buffer.read(cx);
15038 let mut buffers = HashSet::default();
15039 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15040 BTreeMap::new();
15041 for selection_range in selection_ranges {
15042 for (buffer, buffer_range, _) in
15043 snapshot.range_to_buffer_ranges(selection_range)
15044 {
15045 let buffer_id = buffer.remote_id();
15046 let start = buffer.anchor_before(buffer_range.start);
15047 let end = buffer.anchor_after(buffer_range.end);
15048 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15049 buffer_id_to_ranges
15050 .entry(buffer_id)
15051 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15052 .or_insert_with(|| vec![start..end]);
15053 }
15054 }
15055 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15056 }
15057 };
15058
15059 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15060 let selections_prev = transaction_id_prev
15061 .and_then(|transaction_id_prev| {
15062 // default to selections as they were after the last edit, if we have them,
15063 // instead of how they are now.
15064 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15065 // will take you back to where you made the last edit, instead of staying where you scrolled
15066 self.selection_history
15067 .transaction(transaction_id_prev)
15068 .map(|t| t.0.clone())
15069 })
15070 .unwrap_or_else(|| {
15071 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15072 self.selections.disjoint_anchors()
15073 });
15074
15075 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15076 let format = project.update(cx, |project, cx| {
15077 project.format(buffers, target, true, trigger, cx)
15078 });
15079
15080 cx.spawn_in(window, async move |editor, cx| {
15081 let transaction = futures::select_biased! {
15082 transaction = format.log_err().fuse() => transaction,
15083 () = timeout => {
15084 log::warn!("timed out waiting for formatting");
15085 None
15086 }
15087 };
15088
15089 buffer
15090 .update(cx, |buffer, cx| {
15091 if let Some(transaction) = transaction {
15092 if !buffer.is_singleton() {
15093 buffer.push_transaction(&transaction.0, cx);
15094 }
15095 }
15096 cx.notify();
15097 })
15098 .ok();
15099
15100 if let Some(transaction_id_now) =
15101 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15102 {
15103 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15104 if has_new_transaction {
15105 _ = editor.update(cx, |editor, _| {
15106 editor
15107 .selection_history
15108 .insert_transaction(transaction_id_now, selections_prev);
15109 });
15110 }
15111 }
15112
15113 Ok(())
15114 })
15115 }
15116
15117 fn organize_imports(
15118 &mut self,
15119 _: &OrganizeImports,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) -> Option<Task<Result<()>>> {
15123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15124 let project = match &self.project {
15125 Some(project) => project.clone(),
15126 None => return None,
15127 };
15128 Some(self.perform_code_action_kind(
15129 project,
15130 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15131 window,
15132 cx,
15133 ))
15134 }
15135
15136 fn perform_code_action_kind(
15137 &mut self,
15138 project: Entity<Project>,
15139 kind: CodeActionKind,
15140 window: &mut Window,
15141 cx: &mut Context<Self>,
15142 ) -> Task<Result<()>> {
15143 let buffer = self.buffer.clone();
15144 let buffers = buffer.read(cx).all_buffers();
15145 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15146 let apply_action = project.update(cx, |project, cx| {
15147 project.apply_code_action_kind(buffers, kind, true, cx)
15148 });
15149 cx.spawn_in(window, async move |_, cx| {
15150 let transaction = futures::select_biased! {
15151 () = timeout => {
15152 log::warn!("timed out waiting for executing code action");
15153 None
15154 }
15155 transaction = apply_action.log_err().fuse() => transaction,
15156 };
15157 buffer
15158 .update(cx, |buffer, cx| {
15159 // check if we need this
15160 if let Some(transaction) = transaction {
15161 if !buffer.is_singleton() {
15162 buffer.push_transaction(&transaction.0, cx);
15163 }
15164 }
15165 cx.notify();
15166 })
15167 .ok();
15168 Ok(())
15169 })
15170 }
15171
15172 fn restart_language_server(
15173 &mut self,
15174 _: &RestartLanguageServer,
15175 _: &mut Window,
15176 cx: &mut Context<Self>,
15177 ) {
15178 if let Some(project) = self.project.clone() {
15179 self.buffer.update(cx, |multi_buffer, cx| {
15180 project.update(cx, |project, cx| {
15181 project.restart_language_servers_for_buffers(
15182 multi_buffer.all_buffers().into_iter().collect(),
15183 cx,
15184 );
15185 });
15186 })
15187 }
15188 }
15189
15190 fn stop_language_server(
15191 &mut self,
15192 _: &StopLanguageServer,
15193 _: &mut Window,
15194 cx: &mut Context<Self>,
15195 ) {
15196 if let Some(project) = self.project.clone() {
15197 self.buffer.update(cx, |multi_buffer, cx| {
15198 project.update(cx, |project, cx| {
15199 project.stop_language_servers_for_buffers(
15200 multi_buffer.all_buffers().into_iter().collect(),
15201 cx,
15202 );
15203 cx.emit(project::Event::RefreshInlayHints);
15204 });
15205 });
15206 }
15207 }
15208
15209 fn cancel_language_server_work(
15210 workspace: &mut Workspace,
15211 _: &actions::CancelLanguageServerWork,
15212 _: &mut Window,
15213 cx: &mut Context<Workspace>,
15214 ) {
15215 let project = workspace.project();
15216 let buffers = workspace
15217 .active_item(cx)
15218 .and_then(|item| item.act_as::<Editor>(cx))
15219 .map_or(HashSet::default(), |editor| {
15220 editor.read(cx).buffer.read(cx).all_buffers()
15221 });
15222 project.update(cx, |project, cx| {
15223 project.cancel_language_server_work_for_buffers(buffers, cx);
15224 });
15225 }
15226
15227 fn show_character_palette(
15228 &mut self,
15229 _: &ShowCharacterPalette,
15230 window: &mut Window,
15231 _: &mut Context<Self>,
15232 ) {
15233 window.show_character_palette();
15234 }
15235
15236 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15237 if self.mode.is_minimap() {
15238 return;
15239 }
15240
15241 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15242 let buffer = self.buffer.read(cx).snapshot(cx);
15243 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15244 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15245 let is_valid = buffer
15246 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15247 .any(|entry| {
15248 entry.diagnostic.is_primary
15249 && !entry.range.is_empty()
15250 && entry.range.start == primary_range_start
15251 && entry.diagnostic.message == active_diagnostics.active_message
15252 });
15253
15254 if !is_valid {
15255 self.dismiss_diagnostics(cx);
15256 }
15257 }
15258 }
15259
15260 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15261 match &self.active_diagnostics {
15262 ActiveDiagnostic::Group(group) => Some(group),
15263 _ => None,
15264 }
15265 }
15266
15267 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15268 self.dismiss_diagnostics(cx);
15269 self.active_diagnostics = ActiveDiagnostic::All;
15270 }
15271
15272 fn activate_diagnostics(
15273 &mut self,
15274 buffer_id: BufferId,
15275 diagnostic: DiagnosticEntry<usize>,
15276 window: &mut Window,
15277 cx: &mut Context<Self>,
15278 ) {
15279 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15280 return;
15281 }
15282 self.dismiss_diagnostics(cx);
15283 let snapshot = self.snapshot(window, cx);
15284 let buffer = self.buffer.read(cx).snapshot(cx);
15285 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15286 return;
15287 };
15288
15289 let diagnostic_group = buffer
15290 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15291 .collect::<Vec<_>>();
15292
15293 let blocks =
15294 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15295
15296 let blocks = self.display_map.update(cx, |display_map, cx| {
15297 display_map.insert_blocks(blocks, cx).into_iter().collect()
15298 });
15299 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15300 active_range: buffer.anchor_before(diagnostic.range.start)
15301 ..buffer.anchor_after(diagnostic.range.end),
15302 active_message: diagnostic.diagnostic.message.clone(),
15303 group_id: diagnostic.diagnostic.group_id,
15304 blocks,
15305 });
15306 cx.notify();
15307 }
15308
15309 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15310 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15311 return;
15312 };
15313
15314 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15315 if let ActiveDiagnostic::Group(group) = prev {
15316 self.display_map.update(cx, |display_map, cx| {
15317 display_map.remove_blocks(group.blocks, cx);
15318 });
15319 cx.notify();
15320 }
15321 }
15322
15323 /// Disable inline diagnostics rendering for this editor.
15324 pub fn disable_inline_diagnostics(&mut self) {
15325 self.inline_diagnostics_enabled = false;
15326 self.inline_diagnostics_update = Task::ready(());
15327 self.inline_diagnostics.clear();
15328 }
15329
15330 pub fn diagnostics_enabled(&self) -> bool {
15331 self.mode.is_full()
15332 }
15333
15334 pub fn inline_diagnostics_enabled(&self) -> bool {
15335 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15336 }
15337
15338 pub fn show_inline_diagnostics(&self) -> bool {
15339 self.show_inline_diagnostics
15340 }
15341
15342 pub fn toggle_inline_diagnostics(
15343 &mut self,
15344 _: &ToggleInlineDiagnostics,
15345 window: &mut Window,
15346 cx: &mut Context<Editor>,
15347 ) {
15348 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15349 self.refresh_inline_diagnostics(false, window, cx);
15350 }
15351
15352 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15353 self.diagnostics_max_severity = severity;
15354 self.display_map.update(cx, |display_map, _| {
15355 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15356 });
15357 }
15358
15359 pub fn toggle_diagnostics(
15360 &mut self,
15361 _: &ToggleDiagnostics,
15362 window: &mut Window,
15363 cx: &mut Context<Editor>,
15364 ) {
15365 if !self.diagnostics_enabled() {
15366 return;
15367 }
15368
15369 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15370 EditorSettings::get_global(cx)
15371 .diagnostics_max_severity
15372 .filter(|severity| severity != &DiagnosticSeverity::Off)
15373 .unwrap_or(DiagnosticSeverity::Hint)
15374 } else {
15375 DiagnosticSeverity::Off
15376 };
15377 self.set_max_diagnostics_severity(new_severity, cx);
15378 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15379 self.active_diagnostics = ActiveDiagnostic::None;
15380 self.inline_diagnostics_update = Task::ready(());
15381 self.inline_diagnostics.clear();
15382 } else {
15383 self.refresh_inline_diagnostics(false, window, cx);
15384 }
15385
15386 cx.notify();
15387 }
15388
15389 pub fn toggle_minimap(
15390 &mut self,
15391 _: &ToggleMinimap,
15392 window: &mut Window,
15393 cx: &mut Context<Editor>,
15394 ) {
15395 if self.supports_minimap(cx) {
15396 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15397 }
15398 }
15399
15400 fn refresh_inline_diagnostics(
15401 &mut self,
15402 debounce: bool,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) {
15406 let max_severity = ProjectSettings::get_global(cx)
15407 .diagnostics
15408 .inline
15409 .max_severity
15410 .unwrap_or(self.diagnostics_max_severity);
15411
15412 if self.mode.is_minimap()
15413 || !self.inline_diagnostics_enabled()
15414 || !self.show_inline_diagnostics
15415 || max_severity == DiagnosticSeverity::Off
15416 {
15417 self.inline_diagnostics_update = Task::ready(());
15418 self.inline_diagnostics.clear();
15419 return;
15420 }
15421
15422 let debounce_ms = ProjectSettings::get_global(cx)
15423 .diagnostics
15424 .inline
15425 .update_debounce_ms;
15426 let debounce = if debounce && debounce_ms > 0 {
15427 Some(Duration::from_millis(debounce_ms))
15428 } else {
15429 None
15430 };
15431 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15432 let editor = editor.upgrade().unwrap();
15433
15434 if let Some(debounce) = debounce {
15435 cx.background_executor().timer(debounce).await;
15436 }
15437 let Some(snapshot) = editor
15438 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15439 .ok()
15440 else {
15441 return;
15442 };
15443
15444 let new_inline_diagnostics = cx
15445 .background_spawn(async move {
15446 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15447 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15448 let message = diagnostic_entry
15449 .diagnostic
15450 .message
15451 .split_once('\n')
15452 .map(|(line, _)| line)
15453 .map(SharedString::new)
15454 .unwrap_or_else(|| {
15455 SharedString::from(diagnostic_entry.diagnostic.message)
15456 });
15457 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15458 let (Ok(i) | Err(i)) = inline_diagnostics
15459 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15460 inline_diagnostics.insert(
15461 i,
15462 (
15463 start_anchor,
15464 InlineDiagnostic {
15465 message,
15466 group_id: diagnostic_entry.diagnostic.group_id,
15467 start: diagnostic_entry.range.start.to_point(&snapshot),
15468 is_primary: diagnostic_entry.diagnostic.is_primary,
15469 severity: diagnostic_entry.diagnostic.severity,
15470 },
15471 ),
15472 );
15473 }
15474 inline_diagnostics
15475 })
15476 .await;
15477
15478 editor
15479 .update(cx, |editor, cx| {
15480 editor.inline_diagnostics = new_inline_diagnostics;
15481 cx.notify();
15482 })
15483 .ok();
15484 });
15485 }
15486
15487 pub fn set_selections_from_remote(
15488 &mut self,
15489 selections: Vec<Selection<Anchor>>,
15490 pending_selection: Option<Selection<Anchor>>,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) {
15494 let old_cursor_position = self.selections.newest_anchor().head();
15495 self.selections.change_with(cx, |s| {
15496 s.select_anchors(selections);
15497 if let Some(pending_selection) = pending_selection {
15498 s.set_pending(pending_selection, SelectMode::Character);
15499 } else {
15500 s.clear_pending();
15501 }
15502 });
15503 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15504 }
15505
15506 fn push_to_selection_history(&mut self) {
15507 self.selection_history.push(SelectionHistoryEntry {
15508 selections: self.selections.disjoint_anchors(),
15509 select_next_state: self.select_next_state.clone(),
15510 select_prev_state: self.select_prev_state.clone(),
15511 add_selections_state: self.add_selections_state.clone(),
15512 });
15513 }
15514
15515 pub fn transact(
15516 &mut self,
15517 window: &mut Window,
15518 cx: &mut Context<Self>,
15519 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15520 ) -> Option<TransactionId> {
15521 self.start_transaction_at(Instant::now(), window, cx);
15522 update(self, window, cx);
15523 self.end_transaction_at(Instant::now(), cx)
15524 }
15525
15526 pub fn start_transaction_at(
15527 &mut self,
15528 now: Instant,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) {
15532 self.end_selection(window, cx);
15533 if let Some(tx_id) = self
15534 .buffer
15535 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15536 {
15537 self.selection_history
15538 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15539 cx.emit(EditorEvent::TransactionBegun {
15540 transaction_id: tx_id,
15541 })
15542 }
15543 }
15544
15545 pub fn end_transaction_at(
15546 &mut self,
15547 now: Instant,
15548 cx: &mut Context<Self>,
15549 ) -> Option<TransactionId> {
15550 if let Some(transaction_id) = self
15551 .buffer
15552 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15553 {
15554 if let Some((_, end_selections)) =
15555 self.selection_history.transaction_mut(transaction_id)
15556 {
15557 *end_selections = Some(self.selections.disjoint_anchors());
15558 } else {
15559 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15560 }
15561
15562 cx.emit(EditorEvent::Edited { transaction_id });
15563 Some(transaction_id)
15564 } else {
15565 None
15566 }
15567 }
15568
15569 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15570 if self.selection_mark_mode {
15571 self.change_selections(None, window, cx, |s| {
15572 s.move_with(|_, sel| {
15573 sel.collapse_to(sel.head(), SelectionGoal::None);
15574 });
15575 })
15576 }
15577 self.selection_mark_mode = true;
15578 cx.notify();
15579 }
15580
15581 pub fn swap_selection_ends(
15582 &mut self,
15583 _: &actions::SwapSelectionEnds,
15584 window: &mut Window,
15585 cx: &mut Context<Self>,
15586 ) {
15587 self.change_selections(None, window, cx, |s| {
15588 s.move_with(|_, sel| {
15589 if sel.start != sel.end {
15590 sel.reversed = !sel.reversed
15591 }
15592 });
15593 });
15594 self.request_autoscroll(Autoscroll::newest(), cx);
15595 cx.notify();
15596 }
15597
15598 pub fn toggle_fold(
15599 &mut self,
15600 _: &actions::ToggleFold,
15601 window: &mut Window,
15602 cx: &mut Context<Self>,
15603 ) {
15604 if self.is_singleton(cx) {
15605 let selection = self.selections.newest::<Point>(cx);
15606
15607 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15608 let range = if selection.is_empty() {
15609 let point = selection.head().to_display_point(&display_map);
15610 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15611 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15612 .to_point(&display_map);
15613 start..end
15614 } else {
15615 selection.range()
15616 };
15617 if display_map.folds_in_range(range).next().is_some() {
15618 self.unfold_lines(&Default::default(), window, cx)
15619 } else {
15620 self.fold(&Default::default(), window, cx)
15621 }
15622 } else {
15623 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15624 let buffer_ids: HashSet<_> = self
15625 .selections
15626 .disjoint_anchor_ranges()
15627 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15628 .collect();
15629
15630 let should_unfold = buffer_ids
15631 .iter()
15632 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15633
15634 for buffer_id in buffer_ids {
15635 if should_unfold {
15636 self.unfold_buffer(buffer_id, cx);
15637 } else {
15638 self.fold_buffer(buffer_id, cx);
15639 }
15640 }
15641 }
15642 }
15643
15644 pub fn toggle_fold_recursive(
15645 &mut self,
15646 _: &actions::ToggleFoldRecursive,
15647 window: &mut Window,
15648 cx: &mut Context<Self>,
15649 ) {
15650 let selection = self.selections.newest::<Point>(cx);
15651
15652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15653 let range = if selection.is_empty() {
15654 let point = selection.head().to_display_point(&display_map);
15655 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15656 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15657 .to_point(&display_map);
15658 start..end
15659 } else {
15660 selection.range()
15661 };
15662 if display_map.folds_in_range(range).next().is_some() {
15663 self.unfold_recursive(&Default::default(), window, cx)
15664 } else {
15665 self.fold_recursive(&Default::default(), window, cx)
15666 }
15667 }
15668
15669 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15670 if self.is_singleton(cx) {
15671 let mut to_fold = Vec::new();
15672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15673 let selections = self.selections.all_adjusted(cx);
15674
15675 for selection in selections {
15676 let range = selection.range().sorted();
15677 let buffer_start_row = range.start.row;
15678
15679 if range.start.row != range.end.row {
15680 let mut found = false;
15681 let mut row = range.start.row;
15682 while row <= range.end.row {
15683 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15684 {
15685 found = true;
15686 row = crease.range().end.row + 1;
15687 to_fold.push(crease);
15688 } else {
15689 row += 1
15690 }
15691 }
15692 if found {
15693 continue;
15694 }
15695 }
15696
15697 for row in (0..=range.start.row).rev() {
15698 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15699 if crease.range().end.row >= buffer_start_row {
15700 to_fold.push(crease);
15701 if row <= range.start.row {
15702 break;
15703 }
15704 }
15705 }
15706 }
15707 }
15708
15709 self.fold_creases(to_fold, true, window, cx);
15710 } else {
15711 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15712 let buffer_ids = self
15713 .selections
15714 .disjoint_anchor_ranges()
15715 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15716 .collect::<HashSet<_>>();
15717 for buffer_id in buffer_ids {
15718 self.fold_buffer(buffer_id, cx);
15719 }
15720 }
15721 }
15722
15723 fn fold_at_level(
15724 &mut self,
15725 fold_at: &FoldAtLevel,
15726 window: &mut Window,
15727 cx: &mut Context<Self>,
15728 ) {
15729 if !self.buffer.read(cx).is_singleton() {
15730 return;
15731 }
15732
15733 let fold_at_level = fold_at.0;
15734 let snapshot = self.buffer.read(cx).snapshot(cx);
15735 let mut to_fold = Vec::new();
15736 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15737
15738 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15739 while start_row < end_row {
15740 match self
15741 .snapshot(window, cx)
15742 .crease_for_buffer_row(MultiBufferRow(start_row))
15743 {
15744 Some(crease) => {
15745 let nested_start_row = crease.range().start.row + 1;
15746 let nested_end_row = crease.range().end.row;
15747
15748 if current_level < fold_at_level {
15749 stack.push((nested_start_row, nested_end_row, current_level + 1));
15750 } else if current_level == fold_at_level {
15751 to_fold.push(crease);
15752 }
15753
15754 start_row = nested_end_row + 1;
15755 }
15756 None => start_row += 1,
15757 }
15758 }
15759 }
15760
15761 self.fold_creases(to_fold, true, window, cx);
15762 }
15763
15764 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15765 if self.buffer.read(cx).is_singleton() {
15766 let mut fold_ranges = Vec::new();
15767 let snapshot = self.buffer.read(cx).snapshot(cx);
15768
15769 for row in 0..snapshot.max_row().0 {
15770 if let Some(foldable_range) = self
15771 .snapshot(window, cx)
15772 .crease_for_buffer_row(MultiBufferRow(row))
15773 {
15774 fold_ranges.push(foldable_range);
15775 }
15776 }
15777
15778 self.fold_creases(fold_ranges, true, window, cx);
15779 } else {
15780 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15781 editor
15782 .update_in(cx, |editor, _, cx| {
15783 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15784 editor.fold_buffer(buffer_id, cx);
15785 }
15786 })
15787 .ok();
15788 });
15789 }
15790 }
15791
15792 pub fn fold_function_bodies(
15793 &mut self,
15794 _: &actions::FoldFunctionBodies,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 let snapshot = self.buffer.read(cx).snapshot(cx);
15799
15800 let ranges = snapshot
15801 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15802 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15803 .collect::<Vec<_>>();
15804
15805 let creases = ranges
15806 .into_iter()
15807 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15808 .collect();
15809
15810 self.fold_creases(creases, true, window, cx);
15811 }
15812
15813 pub fn fold_recursive(
15814 &mut self,
15815 _: &actions::FoldRecursive,
15816 window: &mut Window,
15817 cx: &mut Context<Self>,
15818 ) {
15819 let mut to_fold = Vec::new();
15820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15821 let selections = self.selections.all_adjusted(cx);
15822
15823 for selection in selections {
15824 let range = selection.range().sorted();
15825 let buffer_start_row = range.start.row;
15826
15827 if range.start.row != range.end.row {
15828 let mut found = false;
15829 for row in range.start.row..=range.end.row {
15830 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15831 found = true;
15832 to_fold.push(crease);
15833 }
15834 }
15835 if found {
15836 continue;
15837 }
15838 }
15839
15840 for row in (0..=range.start.row).rev() {
15841 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15842 if crease.range().end.row >= buffer_start_row {
15843 to_fold.push(crease);
15844 } else {
15845 break;
15846 }
15847 }
15848 }
15849 }
15850
15851 self.fold_creases(to_fold, true, window, cx);
15852 }
15853
15854 pub fn fold_at(
15855 &mut self,
15856 buffer_row: MultiBufferRow,
15857 window: &mut Window,
15858 cx: &mut Context<Self>,
15859 ) {
15860 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15861
15862 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15863 let autoscroll = self
15864 .selections
15865 .all::<Point>(cx)
15866 .iter()
15867 .any(|selection| crease.range().overlaps(&selection.range()));
15868
15869 self.fold_creases(vec![crease], autoscroll, window, cx);
15870 }
15871 }
15872
15873 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15874 if self.is_singleton(cx) {
15875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15876 let buffer = &display_map.buffer_snapshot;
15877 let selections = self.selections.all::<Point>(cx);
15878 let ranges = selections
15879 .iter()
15880 .map(|s| {
15881 let range = s.display_range(&display_map).sorted();
15882 let mut start = range.start.to_point(&display_map);
15883 let mut end = range.end.to_point(&display_map);
15884 start.column = 0;
15885 end.column = buffer.line_len(MultiBufferRow(end.row));
15886 start..end
15887 })
15888 .collect::<Vec<_>>();
15889
15890 self.unfold_ranges(&ranges, true, true, cx);
15891 } else {
15892 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15893 let buffer_ids = self
15894 .selections
15895 .disjoint_anchor_ranges()
15896 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15897 .collect::<HashSet<_>>();
15898 for buffer_id in buffer_ids {
15899 self.unfold_buffer(buffer_id, cx);
15900 }
15901 }
15902 }
15903
15904 pub fn unfold_recursive(
15905 &mut self,
15906 _: &UnfoldRecursive,
15907 _window: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) {
15910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15911 let selections = self.selections.all::<Point>(cx);
15912 let ranges = selections
15913 .iter()
15914 .map(|s| {
15915 let mut range = s.display_range(&display_map).sorted();
15916 *range.start.column_mut() = 0;
15917 *range.end.column_mut() = display_map.line_len(range.end.row());
15918 let start = range.start.to_point(&display_map);
15919 let end = range.end.to_point(&display_map);
15920 start..end
15921 })
15922 .collect::<Vec<_>>();
15923
15924 self.unfold_ranges(&ranges, true, true, cx);
15925 }
15926
15927 pub fn unfold_at(
15928 &mut self,
15929 buffer_row: MultiBufferRow,
15930 _window: &mut Window,
15931 cx: &mut Context<Self>,
15932 ) {
15933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15934
15935 let intersection_range = Point::new(buffer_row.0, 0)
15936 ..Point::new(
15937 buffer_row.0,
15938 display_map.buffer_snapshot.line_len(buffer_row),
15939 );
15940
15941 let autoscroll = self
15942 .selections
15943 .all::<Point>(cx)
15944 .iter()
15945 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15946
15947 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15948 }
15949
15950 pub fn unfold_all(
15951 &mut self,
15952 _: &actions::UnfoldAll,
15953 _window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 if self.buffer.read(cx).is_singleton() {
15957 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15958 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15959 } else {
15960 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15961 editor
15962 .update(cx, |editor, cx| {
15963 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15964 editor.unfold_buffer(buffer_id, cx);
15965 }
15966 })
15967 .ok();
15968 });
15969 }
15970 }
15971
15972 pub fn fold_selected_ranges(
15973 &mut self,
15974 _: &FoldSelectedRanges,
15975 window: &mut Window,
15976 cx: &mut Context<Self>,
15977 ) {
15978 let selections = self.selections.all_adjusted(cx);
15979 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15980 let ranges = selections
15981 .into_iter()
15982 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15983 .collect::<Vec<_>>();
15984 self.fold_creases(ranges, true, window, cx);
15985 }
15986
15987 pub fn fold_ranges<T: ToOffset + Clone>(
15988 &mut self,
15989 ranges: Vec<Range<T>>,
15990 auto_scroll: bool,
15991 window: &mut Window,
15992 cx: &mut Context<Self>,
15993 ) {
15994 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15995 let ranges = ranges
15996 .into_iter()
15997 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15998 .collect::<Vec<_>>();
15999 self.fold_creases(ranges, auto_scroll, window, cx);
16000 }
16001
16002 pub fn fold_creases<T: ToOffset + Clone>(
16003 &mut self,
16004 creases: Vec<Crease<T>>,
16005 auto_scroll: bool,
16006 _window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 if creases.is_empty() {
16010 return;
16011 }
16012
16013 let mut buffers_affected = HashSet::default();
16014 let multi_buffer = self.buffer().read(cx);
16015 for crease in &creases {
16016 if let Some((_, buffer, _)) =
16017 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16018 {
16019 buffers_affected.insert(buffer.read(cx).remote_id());
16020 };
16021 }
16022
16023 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16024
16025 if auto_scroll {
16026 self.request_autoscroll(Autoscroll::fit(), cx);
16027 }
16028
16029 cx.notify();
16030
16031 self.scrollbar_marker_state.dirty = true;
16032 self.folds_did_change(cx);
16033 }
16034
16035 /// Removes any folds whose ranges intersect any of the given ranges.
16036 pub fn unfold_ranges<T: ToOffset + Clone>(
16037 &mut self,
16038 ranges: &[Range<T>],
16039 inclusive: bool,
16040 auto_scroll: bool,
16041 cx: &mut Context<Self>,
16042 ) {
16043 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16044 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16045 });
16046 self.folds_did_change(cx);
16047 }
16048
16049 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16050 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16051 return;
16052 }
16053 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16054 self.display_map.update(cx, |display_map, cx| {
16055 display_map.fold_buffers([buffer_id], cx)
16056 });
16057 cx.emit(EditorEvent::BufferFoldToggled {
16058 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16059 folded: true,
16060 });
16061 cx.notify();
16062 }
16063
16064 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16065 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16066 return;
16067 }
16068 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16069 self.display_map.update(cx, |display_map, cx| {
16070 display_map.unfold_buffers([buffer_id], cx);
16071 });
16072 cx.emit(EditorEvent::BufferFoldToggled {
16073 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16074 folded: false,
16075 });
16076 cx.notify();
16077 }
16078
16079 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16080 self.display_map.read(cx).is_buffer_folded(buffer)
16081 }
16082
16083 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16084 self.display_map.read(cx).folded_buffers()
16085 }
16086
16087 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16088 self.display_map.update(cx, |display_map, cx| {
16089 display_map.disable_header_for_buffer(buffer_id, cx);
16090 });
16091 cx.notify();
16092 }
16093
16094 /// Removes any folds with the given ranges.
16095 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16096 &mut self,
16097 ranges: &[Range<T>],
16098 type_id: TypeId,
16099 auto_scroll: bool,
16100 cx: &mut Context<Self>,
16101 ) {
16102 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16103 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16104 });
16105 self.folds_did_change(cx);
16106 }
16107
16108 fn remove_folds_with<T: ToOffset + Clone>(
16109 &mut self,
16110 ranges: &[Range<T>],
16111 auto_scroll: bool,
16112 cx: &mut Context<Self>,
16113 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16114 ) {
16115 if ranges.is_empty() {
16116 return;
16117 }
16118
16119 let mut buffers_affected = HashSet::default();
16120 let multi_buffer = self.buffer().read(cx);
16121 for range in ranges {
16122 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16123 buffers_affected.insert(buffer.read(cx).remote_id());
16124 };
16125 }
16126
16127 self.display_map.update(cx, update);
16128
16129 if auto_scroll {
16130 self.request_autoscroll(Autoscroll::fit(), cx);
16131 }
16132
16133 cx.notify();
16134 self.scrollbar_marker_state.dirty = true;
16135 self.active_indent_guides_state.dirty = true;
16136 }
16137
16138 pub fn update_fold_widths(
16139 &mut self,
16140 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16141 cx: &mut Context<Self>,
16142 ) -> bool {
16143 self.display_map
16144 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16145 }
16146
16147 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16148 self.display_map.read(cx).fold_placeholder.clone()
16149 }
16150
16151 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16152 self.buffer.update(cx, |buffer, cx| {
16153 buffer.set_all_diff_hunks_expanded(cx);
16154 });
16155 }
16156
16157 pub fn expand_all_diff_hunks(
16158 &mut self,
16159 _: &ExpandAllDiffHunks,
16160 _window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) {
16163 self.buffer.update(cx, |buffer, cx| {
16164 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16165 });
16166 }
16167
16168 pub fn toggle_selected_diff_hunks(
16169 &mut self,
16170 _: &ToggleSelectedDiffHunks,
16171 _window: &mut Window,
16172 cx: &mut Context<Self>,
16173 ) {
16174 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16175 self.toggle_diff_hunks_in_ranges(ranges, cx);
16176 }
16177
16178 pub fn diff_hunks_in_ranges<'a>(
16179 &'a self,
16180 ranges: &'a [Range<Anchor>],
16181 buffer: &'a MultiBufferSnapshot,
16182 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16183 ranges.iter().flat_map(move |range| {
16184 let end_excerpt_id = range.end.excerpt_id;
16185 let range = range.to_point(buffer);
16186 let mut peek_end = range.end;
16187 if range.end.row < buffer.max_row().0 {
16188 peek_end = Point::new(range.end.row + 1, 0);
16189 }
16190 buffer
16191 .diff_hunks_in_range(range.start..peek_end)
16192 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16193 })
16194 }
16195
16196 pub fn has_stageable_diff_hunks_in_ranges(
16197 &self,
16198 ranges: &[Range<Anchor>],
16199 snapshot: &MultiBufferSnapshot,
16200 ) -> bool {
16201 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16202 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16203 }
16204
16205 pub fn toggle_staged_selected_diff_hunks(
16206 &mut self,
16207 _: &::git::ToggleStaged,
16208 _: &mut Window,
16209 cx: &mut Context<Self>,
16210 ) {
16211 let snapshot = self.buffer.read(cx).snapshot(cx);
16212 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16213 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16214 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16215 }
16216
16217 pub fn set_render_diff_hunk_controls(
16218 &mut self,
16219 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16220 cx: &mut Context<Self>,
16221 ) {
16222 self.render_diff_hunk_controls = render_diff_hunk_controls;
16223 cx.notify();
16224 }
16225
16226 pub fn stage_and_next(
16227 &mut self,
16228 _: &::git::StageAndNext,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) {
16232 self.do_stage_or_unstage_and_next(true, window, cx);
16233 }
16234
16235 pub fn unstage_and_next(
16236 &mut self,
16237 _: &::git::UnstageAndNext,
16238 window: &mut Window,
16239 cx: &mut Context<Self>,
16240 ) {
16241 self.do_stage_or_unstage_and_next(false, window, cx);
16242 }
16243
16244 pub fn stage_or_unstage_diff_hunks(
16245 &mut self,
16246 stage: bool,
16247 ranges: Vec<Range<Anchor>>,
16248 cx: &mut Context<Self>,
16249 ) {
16250 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16251 cx.spawn(async move |this, cx| {
16252 task.await?;
16253 this.update(cx, |this, cx| {
16254 let snapshot = this.buffer.read(cx).snapshot(cx);
16255 let chunk_by = this
16256 .diff_hunks_in_ranges(&ranges, &snapshot)
16257 .chunk_by(|hunk| hunk.buffer_id);
16258 for (buffer_id, hunks) in &chunk_by {
16259 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16260 }
16261 })
16262 })
16263 .detach_and_log_err(cx);
16264 }
16265
16266 fn save_buffers_for_ranges_if_needed(
16267 &mut self,
16268 ranges: &[Range<Anchor>],
16269 cx: &mut Context<Editor>,
16270 ) -> Task<Result<()>> {
16271 let multibuffer = self.buffer.read(cx);
16272 let snapshot = multibuffer.read(cx);
16273 let buffer_ids: HashSet<_> = ranges
16274 .iter()
16275 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16276 .collect();
16277 drop(snapshot);
16278
16279 let mut buffers = HashSet::default();
16280 for buffer_id in buffer_ids {
16281 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16282 let buffer = buffer_entity.read(cx);
16283 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16284 {
16285 buffers.insert(buffer_entity);
16286 }
16287 }
16288 }
16289
16290 if let Some(project) = &self.project {
16291 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16292 } else {
16293 Task::ready(Ok(()))
16294 }
16295 }
16296
16297 fn do_stage_or_unstage_and_next(
16298 &mut self,
16299 stage: bool,
16300 window: &mut Window,
16301 cx: &mut Context<Self>,
16302 ) {
16303 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16304
16305 if ranges.iter().any(|range| range.start != range.end) {
16306 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16307 return;
16308 }
16309
16310 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16311 let snapshot = self.snapshot(window, cx);
16312 let position = self.selections.newest::<Point>(cx).head();
16313 let mut row = snapshot
16314 .buffer_snapshot
16315 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16316 .find(|hunk| hunk.row_range.start.0 > position.row)
16317 .map(|hunk| hunk.row_range.start);
16318
16319 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16320 // Outside of the project diff editor, wrap around to the beginning.
16321 if !all_diff_hunks_expanded {
16322 row = row.or_else(|| {
16323 snapshot
16324 .buffer_snapshot
16325 .diff_hunks_in_range(Point::zero()..position)
16326 .find(|hunk| hunk.row_range.end.0 < position.row)
16327 .map(|hunk| hunk.row_range.start)
16328 });
16329 }
16330
16331 if let Some(row) = row {
16332 let destination = Point::new(row.0, 0);
16333 let autoscroll = Autoscroll::center();
16334
16335 self.unfold_ranges(&[destination..destination], false, false, cx);
16336 self.change_selections(Some(autoscroll), window, cx, |s| {
16337 s.select_ranges([destination..destination]);
16338 });
16339 }
16340 }
16341
16342 fn do_stage_or_unstage(
16343 &self,
16344 stage: bool,
16345 buffer_id: BufferId,
16346 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16347 cx: &mut App,
16348 ) -> Option<()> {
16349 let project = self.project.as_ref()?;
16350 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16351 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16352 let buffer_snapshot = buffer.read(cx).snapshot();
16353 let file_exists = buffer_snapshot
16354 .file()
16355 .is_some_and(|file| file.disk_state().exists());
16356 diff.update(cx, |diff, cx| {
16357 diff.stage_or_unstage_hunks(
16358 stage,
16359 &hunks
16360 .map(|hunk| buffer_diff::DiffHunk {
16361 buffer_range: hunk.buffer_range,
16362 diff_base_byte_range: hunk.diff_base_byte_range,
16363 secondary_status: hunk.secondary_status,
16364 range: Point::zero()..Point::zero(), // unused
16365 })
16366 .collect::<Vec<_>>(),
16367 &buffer_snapshot,
16368 file_exists,
16369 cx,
16370 )
16371 });
16372 None
16373 }
16374
16375 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16376 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16377 self.buffer
16378 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16379 }
16380
16381 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16382 self.buffer.update(cx, |buffer, cx| {
16383 let ranges = vec![Anchor::min()..Anchor::max()];
16384 if !buffer.all_diff_hunks_expanded()
16385 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16386 {
16387 buffer.collapse_diff_hunks(ranges, cx);
16388 true
16389 } else {
16390 false
16391 }
16392 })
16393 }
16394
16395 fn toggle_diff_hunks_in_ranges(
16396 &mut self,
16397 ranges: Vec<Range<Anchor>>,
16398 cx: &mut Context<Editor>,
16399 ) {
16400 self.buffer.update(cx, |buffer, cx| {
16401 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16402 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16403 })
16404 }
16405
16406 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16407 self.buffer.update(cx, |buffer, cx| {
16408 let snapshot = buffer.snapshot(cx);
16409 let excerpt_id = range.end.excerpt_id;
16410 let point_range = range.to_point(&snapshot);
16411 let expand = !buffer.single_hunk_is_expanded(range, cx);
16412 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16413 })
16414 }
16415
16416 pub(crate) fn apply_all_diff_hunks(
16417 &mut self,
16418 _: &ApplyAllDiffHunks,
16419 window: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16423
16424 let buffers = self.buffer.read(cx).all_buffers();
16425 for branch_buffer in buffers {
16426 branch_buffer.update(cx, |branch_buffer, cx| {
16427 branch_buffer.merge_into_base(Vec::new(), cx);
16428 });
16429 }
16430
16431 if let Some(project) = self.project.clone() {
16432 self.save(true, project, window, cx).detach_and_log_err(cx);
16433 }
16434 }
16435
16436 pub(crate) fn apply_selected_diff_hunks(
16437 &mut self,
16438 _: &ApplyDiffHunk,
16439 window: &mut Window,
16440 cx: &mut Context<Self>,
16441 ) {
16442 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16443 let snapshot = self.snapshot(window, cx);
16444 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16445 let mut ranges_by_buffer = HashMap::default();
16446 self.transact(window, cx, |editor, _window, cx| {
16447 for hunk in hunks {
16448 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16449 ranges_by_buffer
16450 .entry(buffer.clone())
16451 .or_insert_with(Vec::new)
16452 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16453 }
16454 }
16455
16456 for (buffer, ranges) in ranges_by_buffer {
16457 buffer.update(cx, |buffer, cx| {
16458 buffer.merge_into_base(ranges, cx);
16459 });
16460 }
16461 });
16462
16463 if let Some(project) = self.project.clone() {
16464 self.save(true, project, window, cx).detach_and_log_err(cx);
16465 }
16466 }
16467
16468 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16469 if hovered != self.gutter_hovered {
16470 self.gutter_hovered = hovered;
16471 cx.notify();
16472 }
16473 }
16474
16475 pub fn insert_blocks(
16476 &mut self,
16477 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16478 autoscroll: Option<Autoscroll>,
16479 cx: &mut Context<Self>,
16480 ) -> Vec<CustomBlockId> {
16481 let blocks = self
16482 .display_map
16483 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16484 if let Some(autoscroll) = autoscroll {
16485 self.request_autoscroll(autoscroll, cx);
16486 }
16487 cx.notify();
16488 blocks
16489 }
16490
16491 pub fn resize_blocks(
16492 &mut self,
16493 heights: HashMap<CustomBlockId, u32>,
16494 autoscroll: Option<Autoscroll>,
16495 cx: &mut Context<Self>,
16496 ) {
16497 self.display_map
16498 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16499 if let Some(autoscroll) = autoscroll {
16500 self.request_autoscroll(autoscroll, cx);
16501 }
16502 cx.notify();
16503 }
16504
16505 pub fn replace_blocks(
16506 &mut self,
16507 renderers: HashMap<CustomBlockId, RenderBlock>,
16508 autoscroll: Option<Autoscroll>,
16509 cx: &mut Context<Self>,
16510 ) {
16511 self.display_map
16512 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16513 if let Some(autoscroll) = autoscroll {
16514 self.request_autoscroll(autoscroll, cx);
16515 }
16516 cx.notify();
16517 }
16518
16519 pub fn remove_blocks(
16520 &mut self,
16521 block_ids: HashSet<CustomBlockId>,
16522 autoscroll: Option<Autoscroll>,
16523 cx: &mut Context<Self>,
16524 ) {
16525 self.display_map.update(cx, |display_map, cx| {
16526 display_map.remove_blocks(block_ids, cx)
16527 });
16528 if let Some(autoscroll) = autoscroll {
16529 self.request_autoscroll(autoscroll, cx);
16530 }
16531 cx.notify();
16532 }
16533
16534 pub fn row_for_block(
16535 &self,
16536 block_id: CustomBlockId,
16537 cx: &mut Context<Self>,
16538 ) -> Option<DisplayRow> {
16539 self.display_map
16540 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16541 }
16542
16543 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16544 self.focused_block = Some(focused_block);
16545 }
16546
16547 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16548 self.focused_block.take()
16549 }
16550
16551 pub fn insert_creases(
16552 &mut self,
16553 creases: impl IntoIterator<Item = Crease<Anchor>>,
16554 cx: &mut Context<Self>,
16555 ) -> Vec<CreaseId> {
16556 self.display_map
16557 .update(cx, |map, cx| map.insert_creases(creases, cx))
16558 }
16559
16560 pub fn remove_creases(
16561 &mut self,
16562 ids: impl IntoIterator<Item = CreaseId>,
16563 cx: &mut Context<Self>,
16564 ) -> Vec<(CreaseId, Range<Anchor>)> {
16565 self.display_map
16566 .update(cx, |map, cx| map.remove_creases(ids, cx))
16567 }
16568
16569 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16570 self.display_map
16571 .update(cx, |map, cx| map.snapshot(cx))
16572 .longest_row()
16573 }
16574
16575 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16576 self.display_map
16577 .update(cx, |map, cx| map.snapshot(cx))
16578 .max_point()
16579 }
16580
16581 pub fn text(&self, cx: &App) -> String {
16582 self.buffer.read(cx).read(cx).text()
16583 }
16584
16585 pub fn is_empty(&self, cx: &App) -> bool {
16586 self.buffer.read(cx).read(cx).is_empty()
16587 }
16588
16589 pub fn text_option(&self, cx: &App) -> Option<String> {
16590 let text = self.text(cx);
16591 let text = text.trim();
16592
16593 if text.is_empty() {
16594 return None;
16595 }
16596
16597 Some(text.to_string())
16598 }
16599
16600 pub fn set_text(
16601 &mut self,
16602 text: impl Into<Arc<str>>,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 self.transact(window, cx, |this, _, cx| {
16607 this.buffer
16608 .read(cx)
16609 .as_singleton()
16610 .expect("you can only call set_text on editors for singleton buffers")
16611 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16612 });
16613 }
16614
16615 pub fn display_text(&self, cx: &mut App) -> String {
16616 self.display_map
16617 .update(cx, |map, cx| map.snapshot(cx))
16618 .text()
16619 }
16620
16621 fn create_minimap(
16622 &self,
16623 minimap_settings: MinimapSettings,
16624 window: &mut Window,
16625 cx: &mut Context<Self>,
16626 ) -> Option<Entity<Self>> {
16627 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16628 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16629 }
16630
16631 fn initialize_new_minimap(
16632 &self,
16633 minimap_settings: MinimapSettings,
16634 window: &mut Window,
16635 cx: &mut Context<Self>,
16636 ) -> Entity<Self> {
16637 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16638
16639 let mut minimap = Editor::new_internal(
16640 EditorMode::Minimap {
16641 parent: cx.weak_entity(),
16642 },
16643 self.buffer.clone(),
16644 self.project.clone(),
16645 Some(self.display_map.clone()),
16646 window,
16647 cx,
16648 );
16649 minimap.scroll_manager.clone_state(&self.scroll_manager);
16650 minimap.set_text_style_refinement(TextStyleRefinement {
16651 font_size: Some(MINIMAP_FONT_SIZE),
16652 font_weight: Some(MINIMAP_FONT_WEIGHT),
16653 ..Default::default()
16654 });
16655 minimap.update_minimap_configuration(minimap_settings, cx);
16656 cx.new(|_| minimap)
16657 }
16658
16659 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16660 let current_line_highlight = minimap_settings
16661 .current_line_highlight
16662 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16663 self.set_current_line_highlight(Some(current_line_highlight));
16664 }
16665
16666 pub fn minimap(&self) -> Option<&Entity<Self>> {
16667 self.minimap
16668 .as_ref()
16669 .filter(|_| self.minimap_visibility.visible())
16670 }
16671
16672 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16673 let mut wrap_guides = smallvec::smallvec![];
16674
16675 if self.show_wrap_guides == Some(false) {
16676 return wrap_guides;
16677 }
16678
16679 let settings = self.buffer.read(cx).language_settings(cx);
16680 if settings.show_wrap_guides {
16681 match self.soft_wrap_mode(cx) {
16682 SoftWrap::Column(soft_wrap) => {
16683 wrap_guides.push((soft_wrap as usize, true));
16684 }
16685 SoftWrap::Bounded(soft_wrap) => {
16686 wrap_guides.push((soft_wrap as usize, true));
16687 }
16688 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16689 }
16690 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16691 }
16692
16693 wrap_guides
16694 }
16695
16696 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16697 let settings = self.buffer.read(cx).language_settings(cx);
16698 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16699 match mode {
16700 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16701 SoftWrap::None
16702 }
16703 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16704 language_settings::SoftWrap::PreferredLineLength => {
16705 SoftWrap::Column(settings.preferred_line_length)
16706 }
16707 language_settings::SoftWrap::Bounded => {
16708 SoftWrap::Bounded(settings.preferred_line_length)
16709 }
16710 }
16711 }
16712
16713 pub fn set_soft_wrap_mode(
16714 &mut self,
16715 mode: language_settings::SoftWrap,
16716
16717 cx: &mut Context<Self>,
16718 ) {
16719 self.soft_wrap_mode_override = Some(mode);
16720 cx.notify();
16721 }
16722
16723 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16724 self.hard_wrap = hard_wrap;
16725 cx.notify();
16726 }
16727
16728 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16729 self.text_style_refinement = Some(style);
16730 }
16731
16732 /// called by the Element so we know what style we were most recently rendered with.
16733 pub(crate) fn set_style(
16734 &mut self,
16735 style: EditorStyle,
16736 window: &mut Window,
16737 cx: &mut Context<Self>,
16738 ) {
16739 // We intentionally do not inform the display map about the minimap style
16740 // so that wrapping is not recalculated and stays consistent for the editor
16741 // and its linked minimap.
16742 if !self.mode.is_minimap() {
16743 let rem_size = window.rem_size();
16744 self.display_map.update(cx, |map, cx| {
16745 map.set_font(
16746 style.text.font(),
16747 style.text.font_size.to_pixels(rem_size),
16748 cx,
16749 )
16750 });
16751 }
16752 self.style = Some(style);
16753 }
16754
16755 pub fn style(&self) -> Option<&EditorStyle> {
16756 self.style.as_ref()
16757 }
16758
16759 // Called by the element. This method is not designed to be called outside of the editor
16760 // element's layout code because it does not notify when rewrapping is computed synchronously.
16761 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16762 self.display_map
16763 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16764 }
16765
16766 pub fn set_soft_wrap(&mut self) {
16767 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16768 }
16769
16770 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16771 if self.soft_wrap_mode_override.is_some() {
16772 self.soft_wrap_mode_override.take();
16773 } else {
16774 let soft_wrap = match self.soft_wrap_mode(cx) {
16775 SoftWrap::GitDiff => return,
16776 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16777 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16778 language_settings::SoftWrap::None
16779 }
16780 };
16781 self.soft_wrap_mode_override = Some(soft_wrap);
16782 }
16783 cx.notify();
16784 }
16785
16786 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16787 let Some(workspace) = self.workspace() else {
16788 return;
16789 };
16790 let fs = workspace.read(cx).app_state().fs.clone();
16791 let current_show = TabBarSettings::get_global(cx).show;
16792 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16793 setting.show = Some(!current_show);
16794 });
16795 }
16796
16797 pub fn toggle_indent_guides(
16798 &mut self,
16799 _: &ToggleIndentGuides,
16800 _: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16804 self.buffer
16805 .read(cx)
16806 .language_settings(cx)
16807 .indent_guides
16808 .enabled
16809 });
16810 self.show_indent_guides = Some(!currently_enabled);
16811 cx.notify();
16812 }
16813
16814 fn should_show_indent_guides(&self) -> Option<bool> {
16815 self.show_indent_guides
16816 }
16817
16818 pub fn toggle_line_numbers(
16819 &mut self,
16820 _: &ToggleLineNumbers,
16821 _: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 let mut editor_settings = EditorSettings::get_global(cx).clone();
16825 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16826 EditorSettings::override_global(editor_settings, cx);
16827 }
16828
16829 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16830 if let Some(show_line_numbers) = self.show_line_numbers {
16831 return show_line_numbers;
16832 }
16833 EditorSettings::get_global(cx).gutter.line_numbers
16834 }
16835
16836 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16837 self.use_relative_line_numbers
16838 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16839 }
16840
16841 pub fn toggle_relative_line_numbers(
16842 &mut self,
16843 _: &ToggleRelativeLineNumbers,
16844 _: &mut Window,
16845 cx: &mut Context<Self>,
16846 ) {
16847 let is_relative = self.should_use_relative_line_numbers(cx);
16848 self.set_relative_line_number(Some(!is_relative), cx)
16849 }
16850
16851 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16852 self.use_relative_line_numbers = is_relative;
16853 cx.notify();
16854 }
16855
16856 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16857 self.show_gutter = show_gutter;
16858 cx.notify();
16859 }
16860
16861 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16862 self.show_scrollbars = show_scrollbars;
16863 cx.notify();
16864 }
16865
16866 pub fn set_minimap_visibility(
16867 &mut self,
16868 minimap_visibility: MinimapVisibility,
16869 window: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 if self.minimap_visibility != minimap_visibility {
16873 if minimap_visibility.visible() && self.minimap.is_none() {
16874 let minimap_settings = EditorSettings::get_global(cx).minimap;
16875 self.minimap =
16876 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16877 }
16878 self.minimap_visibility = minimap_visibility;
16879 cx.notify();
16880 }
16881 }
16882
16883 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16884 self.set_show_scrollbars(false, cx);
16885 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16886 }
16887
16888 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16889 self.show_line_numbers = Some(show_line_numbers);
16890 cx.notify();
16891 }
16892
16893 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16894 self.disable_expand_excerpt_buttons = true;
16895 cx.notify();
16896 }
16897
16898 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16899 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16900 cx.notify();
16901 }
16902
16903 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16904 self.show_code_actions = Some(show_code_actions);
16905 cx.notify();
16906 }
16907
16908 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16909 self.show_runnables = Some(show_runnables);
16910 cx.notify();
16911 }
16912
16913 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16914 self.show_breakpoints = Some(show_breakpoints);
16915 cx.notify();
16916 }
16917
16918 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16919 if self.display_map.read(cx).masked != masked {
16920 self.display_map.update(cx, |map, _| map.masked = masked);
16921 }
16922 cx.notify()
16923 }
16924
16925 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16926 self.show_wrap_guides = Some(show_wrap_guides);
16927 cx.notify();
16928 }
16929
16930 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16931 self.show_indent_guides = Some(show_indent_guides);
16932 cx.notify();
16933 }
16934
16935 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16936 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16937 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16938 if let Some(dir) = file.abs_path(cx).parent() {
16939 return Some(dir.to_owned());
16940 }
16941 }
16942
16943 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16944 return Some(project_path.path.to_path_buf());
16945 }
16946 }
16947
16948 None
16949 }
16950
16951 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16952 self.active_excerpt(cx)?
16953 .1
16954 .read(cx)
16955 .file()
16956 .and_then(|f| f.as_local())
16957 }
16958
16959 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16960 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16961 let buffer = buffer.read(cx);
16962 if let Some(project_path) = buffer.project_path(cx) {
16963 let project = self.project.as_ref()?.read(cx);
16964 project.absolute_path(&project_path, cx)
16965 } else {
16966 buffer
16967 .file()
16968 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16969 }
16970 })
16971 }
16972
16973 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16974 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16975 let project_path = buffer.read(cx).project_path(cx)?;
16976 let project = self.project.as_ref()?.read(cx);
16977 let entry = project.entry_for_path(&project_path, cx)?;
16978 let path = entry.path.to_path_buf();
16979 Some(path)
16980 })
16981 }
16982
16983 pub fn reveal_in_finder(
16984 &mut self,
16985 _: &RevealInFileManager,
16986 _window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) {
16989 if let Some(target) = self.target_file(cx) {
16990 cx.reveal_path(&target.abs_path(cx));
16991 }
16992 }
16993
16994 pub fn copy_path(
16995 &mut self,
16996 _: &zed_actions::workspace::CopyPath,
16997 _window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) {
17000 if let Some(path) = self.target_file_abs_path(cx) {
17001 if let Some(path) = path.to_str() {
17002 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17003 }
17004 }
17005 }
17006
17007 pub fn copy_relative_path(
17008 &mut self,
17009 _: &zed_actions::workspace::CopyRelativePath,
17010 _window: &mut Window,
17011 cx: &mut Context<Self>,
17012 ) {
17013 if let Some(path) = self.target_file_path(cx) {
17014 if let Some(path) = path.to_str() {
17015 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17016 }
17017 }
17018 }
17019
17020 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17021 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17022 buffer.read(cx).project_path(cx)
17023 } else {
17024 None
17025 }
17026 }
17027
17028 // Returns true if the editor handled a go-to-line request
17029 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17030 maybe!({
17031 let breakpoint_store = self.breakpoint_store.as_ref()?;
17032
17033 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17034 else {
17035 self.clear_row_highlights::<ActiveDebugLine>();
17036 return None;
17037 };
17038
17039 let position = active_stack_frame.position;
17040 let buffer_id = position.buffer_id?;
17041 let snapshot = self
17042 .project
17043 .as_ref()?
17044 .read(cx)
17045 .buffer_for_id(buffer_id, cx)?
17046 .read(cx)
17047 .snapshot();
17048
17049 let mut handled = false;
17050 for (id, ExcerptRange { context, .. }) in
17051 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17052 {
17053 if context.start.cmp(&position, &snapshot).is_ge()
17054 || context.end.cmp(&position, &snapshot).is_lt()
17055 {
17056 continue;
17057 }
17058 let snapshot = self.buffer.read(cx).snapshot(cx);
17059 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17060
17061 handled = true;
17062 self.clear_row_highlights::<ActiveDebugLine>();
17063
17064 self.go_to_line::<ActiveDebugLine>(
17065 multibuffer_anchor,
17066 Some(cx.theme().colors().editor_debugger_active_line_background),
17067 window,
17068 cx,
17069 );
17070
17071 cx.notify();
17072 }
17073
17074 handled.then_some(())
17075 })
17076 .is_some()
17077 }
17078
17079 pub fn copy_file_name_without_extension(
17080 &mut self,
17081 _: &CopyFileNameWithoutExtension,
17082 _: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 if let Some(file) = self.target_file(cx) {
17086 if let Some(file_stem) = file.path().file_stem() {
17087 if let Some(name) = file_stem.to_str() {
17088 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17089 }
17090 }
17091 }
17092 }
17093
17094 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17095 if let Some(file) = self.target_file(cx) {
17096 if let Some(file_name) = file.path().file_name() {
17097 if let Some(name) = file_name.to_str() {
17098 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17099 }
17100 }
17101 }
17102 }
17103
17104 pub fn toggle_git_blame(
17105 &mut self,
17106 _: &::git::Blame,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) {
17110 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17111
17112 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17113 self.start_git_blame(true, window, cx);
17114 }
17115
17116 cx.notify();
17117 }
17118
17119 pub fn toggle_git_blame_inline(
17120 &mut self,
17121 _: &ToggleGitBlameInline,
17122 window: &mut Window,
17123 cx: &mut Context<Self>,
17124 ) {
17125 self.toggle_git_blame_inline_internal(true, window, cx);
17126 cx.notify();
17127 }
17128
17129 pub fn open_git_blame_commit(
17130 &mut self,
17131 _: &OpenGitBlameCommit,
17132 window: &mut Window,
17133 cx: &mut Context<Self>,
17134 ) {
17135 self.open_git_blame_commit_internal(window, cx);
17136 }
17137
17138 fn open_git_blame_commit_internal(
17139 &mut self,
17140 window: &mut Window,
17141 cx: &mut Context<Self>,
17142 ) -> Option<()> {
17143 let blame = self.blame.as_ref()?;
17144 let snapshot = self.snapshot(window, cx);
17145 let cursor = self.selections.newest::<Point>(cx).head();
17146 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17147 let blame_entry = blame
17148 .update(cx, |blame, cx| {
17149 blame
17150 .blame_for_rows(
17151 &[RowInfo {
17152 buffer_id: Some(buffer.remote_id()),
17153 buffer_row: Some(point.row),
17154 ..Default::default()
17155 }],
17156 cx,
17157 )
17158 .next()
17159 })
17160 .flatten()?;
17161 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17162 let repo = blame.read(cx).repository(cx)?;
17163 let workspace = self.workspace()?.downgrade();
17164 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17165 None
17166 }
17167
17168 pub fn git_blame_inline_enabled(&self) -> bool {
17169 self.git_blame_inline_enabled
17170 }
17171
17172 pub fn toggle_selection_menu(
17173 &mut self,
17174 _: &ToggleSelectionMenu,
17175 _: &mut Window,
17176 cx: &mut Context<Self>,
17177 ) {
17178 self.show_selection_menu = self
17179 .show_selection_menu
17180 .map(|show_selections_menu| !show_selections_menu)
17181 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17182
17183 cx.notify();
17184 }
17185
17186 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17187 self.show_selection_menu
17188 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17189 }
17190
17191 fn start_git_blame(
17192 &mut self,
17193 user_triggered: bool,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) {
17197 if let Some(project) = self.project.as_ref() {
17198 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17199 return;
17200 };
17201
17202 if buffer.read(cx).file().is_none() {
17203 return;
17204 }
17205
17206 let focused = self.focus_handle(cx).contains_focused(window, cx);
17207
17208 let project = project.clone();
17209 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17210 self.blame_subscription =
17211 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17212 self.blame = Some(blame);
17213 }
17214 }
17215
17216 fn toggle_git_blame_inline_internal(
17217 &mut self,
17218 user_triggered: bool,
17219 window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) {
17222 if self.git_blame_inline_enabled {
17223 self.git_blame_inline_enabled = false;
17224 self.show_git_blame_inline = false;
17225 self.show_git_blame_inline_delay_task.take();
17226 } else {
17227 self.git_blame_inline_enabled = true;
17228 self.start_git_blame_inline(user_triggered, window, cx);
17229 }
17230
17231 cx.notify();
17232 }
17233
17234 fn start_git_blame_inline(
17235 &mut self,
17236 user_triggered: bool,
17237 window: &mut Window,
17238 cx: &mut Context<Self>,
17239 ) {
17240 self.start_git_blame(user_triggered, window, cx);
17241
17242 if ProjectSettings::get_global(cx)
17243 .git
17244 .inline_blame_delay()
17245 .is_some()
17246 {
17247 self.start_inline_blame_timer(window, cx);
17248 } else {
17249 self.show_git_blame_inline = true
17250 }
17251 }
17252
17253 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17254 self.blame.as_ref()
17255 }
17256
17257 pub fn show_git_blame_gutter(&self) -> bool {
17258 self.show_git_blame_gutter
17259 }
17260
17261 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17262 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17263 }
17264
17265 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17266 self.show_git_blame_inline
17267 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17268 && !self.newest_selection_head_on_empty_line(cx)
17269 && self.has_blame_entries(cx)
17270 }
17271
17272 fn has_blame_entries(&self, cx: &App) -> bool {
17273 self.blame()
17274 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17275 }
17276
17277 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17278 let cursor_anchor = self.selections.newest_anchor().head();
17279
17280 let snapshot = self.buffer.read(cx).snapshot(cx);
17281 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17282
17283 snapshot.line_len(buffer_row) == 0
17284 }
17285
17286 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17287 let buffer_and_selection = maybe!({
17288 let selection = self.selections.newest::<Point>(cx);
17289 let selection_range = selection.range();
17290
17291 let multi_buffer = self.buffer().read(cx);
17292 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17293 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17294
17295 let (buffer, range, _) = if selection.reversed {
17296 buffer_ranges.first()
17297 } else {
17298 buffer_ranges.last()
17299 }?;
17300
17301 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17302 ..text::ToPoint::to_point(&range.end, &buffer).row;
17303 Some((
17304 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17305 selection,
17306 ))
17307 });
17308
17309 let Some((buffer, selection)) = buffer_and_selection else {
17310 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17311 };
17312
17313 let Some(project) = self.project.as_ref() else {
17314 return Task::ready(Err(anyhow!("editor does not have project")));
17315 };
17316
17317 project.update(cx, |project, cx| {
17318 project.get_permalink_to_line(&buffer, selection, cx)
17319 })
17320 }
17321
17322 pub fn copy_permalink_to_line(
17323 &mut self,
17324 _: &CopyPermalinkToLine,
17325 window: &mut Window,
17326 cx: &mut Context<Self>,
17327 ) {
17328 let permalink_task = self.get_permalink_to_line(cx);
17329 let workspace = self.workspace();
17330
17331 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17332 Ok(permalink) => {
17333 cx.update(|_, cx| {
17334 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17335 })
17336 .ok();
17337 }
17338 Err(err) => {
17339 let message = format!("Failed to copy permalink: {err}");
17340
17341 anyhow::Result::<()>::Err(err).log_err();
17342
17343 if let Some(workspace) = workspace {
17344 workspace
17345 .update_in(cx, |workspace, _, cx| {
17346 struct CopyPermalinkToLine;
17347
17348 workspace.show_toast(
17349 Toast::new(
17350 NotificationId::unique::<CopyPermalinkToLine>(),
17351 message,
17352 ),
17353 cx,
17354 )
17355 })
17356 .ok();
17357 }
17358 }
17359 })
17360 .detach();
17361 }
17362
17363 pub fn copy_file_location(
17364 &mut self,
17365 _: &CopyFileLocation,
17366 _: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) {
17369 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17370 if let Some(file) = self.target_file(cx) {
17371 if let Some(path) = file.path().to_str() {
17372 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17373 }
17374 }
17375 }
17376
17377 pub fn open_permalink_to_line(
17378 &mut self,
17379 _: &OpenPermalinkToLine,
17380 window: &mut Window,
17381 cx: &mut Context<Self>,
17382 ) {
17383 let permalink_task = self.get_permalink_to_line(cx);
17384 let workspace = self.workspace();
17385
17386 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17387 Ok(permalink) => {
17388 cx.update(|_, cx| {
17389 cx.open_url(permalink.as_ref());
17390 })
17391 .ok();
17392 }
17393 Err(err) => {
17394 let message = format!("Failed to open permalink: {err}");
17395
17396 anyhow::Result::<()>::Err(err).log_err();
17397
17398 if let Some(workspace) = workspace {
17399 workspace
17400 .update(cx, |workspace, cx| {
17401 struct OpenPermalinkToLine;
17402
17403 workspace.show_toast(
17404 Toast::new(
17405 NotificationId::unique::<OpenPermalinkToLine>(),
17406 message,
17407 ),
17408 cx,
17409 )
17410 })
17411 .ok();
17412 }
17413 }
17414 })
17415 .detach();
17416 }
17417
17418 pub fn insert_uuid_v4(
17419 &mut self,
17420 _: &InsertUuidV4,
17421 window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) {
17424 self.insert_uuid(UuidVersion::V4, window, cx);
17425 }
17426
17427 pub fn insert_uuid_v7(
17428 &mut self,
17429 _: &InsertUuidV7,
17430 window: &mut Window,
17431 cx: &mut Context<Self>,
17432 ) {
17433 self.insert_uuid(UuidVersion::V7, window, cx);
17434 }
17435
17436 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17438 self.transact(window, cx, |this, window, cx| {
17439 let edits = this
17440 .selections
17441 .all::<Point>(cx)
17442 .into_iter()
17443 .map(|selection| {
17444 let uuid = match version {
17445 UuidVersion::V4 => uuid::Uuid::new_v4(),
17446 UuidVersion::V7 => uuid::Uuid::now_v7(),
17447 };
17448
17449 (selection.range(), uuid.to_string())
17450 });
17451 this.edit(edits, cx);
17452 this.refresh_inline_completion(true, false, window, cx);
17453 });
17454 }
17455
17456 pub fn open_selections_in_multibuffer(
17457 &mut self,
17458 _: &OpenSelectionsInMultibuffer,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 let multibuffer = self.buffer.read(cx);
17463
17464 let Some(buffer) = multibuffer.as_singleton() else {
17465 return;
17466 };
17467
17468 let Some(workspace) = self.workspace() else {
17469 return;
17470 };
17471
17472 let locations = self
17473 .selections
17474 .disjoint_anchors()
17475 .iter()
17476 .map(|range| Location {
17477 buffer: buffer.clone(),
17478 range: range.start.text_anchor..range.end.text_anchor,
17479 })
17480 .collect::<Vec<_>>();
17481
17482 let title = multibuffer.title(cx).to_string();
17483
17484 cx.spawn_in(window, async move |_, cx| {
17485 workspace.update_in(cx, |workspace, window, cx| {
17486 Self::open_locations_in_multibuffer(
17487 workspace,
17488 locations,
17489 format!("Selections for '{title}'"),
17490 false,
17491 MultibufferSelectionMode::All,
17492 window,
17493 cx,
17494 );
17495 })
17496 })
17497 .detach();
17498 }
17499
17500 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17501 /// last highlight added will be used.
17502 ///
17503 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17504 pub fn highlight_rows<T: 'static>(
17505 &mut self,
17506 range: Range<Anchor>,
17507 color: Hsla,
17508 options: RowHighlightOptions,
17509 cx: &mut Context<Self>,
17510 ) {
17511 let snapshot = self.buffer().read(cx).snapshot(cx);
17512 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17513 let ix = row_highlights.binary_search_by(|highlight| {
17514 Ordering::Equal
17515 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17516 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17517 });
17518
17519 if let Err(mut ix) = ix {
17520 let index = post_inc(&mut self.highlight_order);
17521
17522 // If this range intersects with the preceding highlight, then merge it with
17523 // the preceding highlight. Otherwise insert a new highlight.
17524 let mut merged = false;
17525 if ix > 0 {
17526 let prev_highlight = &mut row_highlights[ix - 1];
17527 if prev_highlight
17528 .range
17529 .end
17530 .cmp(&range.start, &snapshot)
17531 .is_ge()
17532 {
17533 ix -= 1;
17534 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17535 prev_highlight.range.end = range.end;
17536 }
17537 merged = true;
17538 prev_highlight.index = index;
17539 prev_highlight.color = color;
17540 prev_highlight.options = options;
17541 }
17542 }
17543
17544 if !merged {
17545 row_highlights.insert(
17546 ix,
17547 RowHighlight {
17548 range: range.clone(),
17549 index,
17550 color,
17551 options,
17552 type_id: TypeId::of::<T>(),
17553 },
17554 );
17555 }
17556
17557 // If any of the following highlights intersect with this one, merge them.
17558 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17559 let highlight = &row_highlights[ix];
17560 if next_highlight
17561 .range
17562 .start
17563 .cmp(&highlight.range.end, &snapshot)
17564 .is_le()
17565 {
17566 if next_highlight
17567 .range
17568 .end
17569 .cmp(&highlight.range.end, &snapshot)
17570 .is_gt()
17571 {
17572 row_highlights[ix].range.end = next_highlight.range.end;
17573 }
17574 row_highlights.remove(ix + 1);
17575 } else {
17576 break;
17577 }
17578 }
17579 }
17580 }
17581
17582 /// Remove any highlighted row ranges of the given type that intersect the
17583 /// given ranges.
17584 pub fn remove_highlighted_rows<T: 'static>(
17585 &mut self,
17586 ranges_to_remove: Vec<Range<Anchor>>,
17587 cx: &mut Context<Self>,
17588 ) {
17589 let snapshot = self.buffer().read(cx).snapshot(cx);
17590 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17591 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17592 row_highlights.retain(|highlight| {
17593 while let Some(range_to_remove) = ranges_to_remove.peek() {
17594 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17595 Ordering::Less | Ordering::Equal => {
17596 ranges_to_remove.next();
17597 }
17598 Ordering::Greater => {
17599 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17600 Ordering::Less | Ordering::Equal => {
17601 return false;
17602 }
17603 Ordering::Greater => break,
17604 }
17605 }
17606 }
17607 }
17608
17609 true
17610 })
17611 }
17612
17613 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17614 pub fn clear_row_highlights<T: 'static>(&mut self) {
17615 self.highlighted_rows.remove(&TypeId::of::<T>());
17616 }
17617
17618 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17619 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17620 self.highlighted_rows
17621 .get(&TypeId::of::<T>())
17622 .map_or(&[] as &[_], |vec| vec.as_slice())
17623 .iter()
17624 .map(|highlight| (highlight.range.clone(), highlight.color))
17625 }
17626
17627 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17628 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17629 /// Allows to ignore certain kinds of highlights.
17630 pub fn highlighted_display_rows(
17631 &self,
17632 window: &mut Window,
17633 cx: &mut App,
17634 ) -> BTreeMap<DisplayRow, LineHighlight> {
17635 let snapshot = self.snapshot(window, cx);
17636 let mut used_highlight_orders = HashMap::default();
17637 self.highlighted_rows
17638 .iter()
17639 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17640 .fold(
17641 BTreeMap::<DisplayRow, LineHighlight>::new(),
17642 |mut unique_rows, highlight| {
17643 let start = highlight.range.start.to_display_point(&snapshot);
17644 let end = highlight.range.end.to_display_point(&snapshot);
17645 let start_row = start.row().0;
17646 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17647 && end.column() == 0
17648 {
17649 end.row().0.saturating_sub(1)
17650 } else {
17651 end.row().0
17652 };
17653 for row in start_row..=end_row {
17654 let used_index =
17655 used_highlight_orders.entry(row).or_insert(highlight.index);
17656 if highlight.index >= *used_index {
17657 *used_index = highlight.index;
17658 unique_rows.insert(
17659 DisplayRow(row),
17660 LineHighlight {
17661 include_gutter: highlight.options.include_gutter,
17662 border: None,
17663 background: highlight.color.into(),
17664 type_id: Some(highlight.type_id),
17665 },
17666 );
17667 }
17668 }
17669 unique_rows
17670 },
17671 )
17672 }
17673
17674 pub fn highlighted_display_row_for_autoscroll(
17675 &self,
17676 snapshot: &DisplaySnapshot,
17677 ) -> Option<DisplayRow> {
17678 self.highlighted_rows
17679 .values()
17680 .flat_map(|highlighted_rows| highlighted_rows.iter())
17681 .filter_map(|highlight| {
17682 if highlight.options.autoscroll {
17683 Some(highlight.range.start.to_display_point(snapshot).row())
17684 } else {
17685 None
17686 }
17687 })
17688 .min()
17689 }
17690
17691 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17692 self.highlight_background::<SearchWithinRange>(
17693 ranges,
17694 |colors| colors.editor_document_highlight_read_background,
17695 cx,
17696 )
17697 }
17698
17699 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17700 self.breadcrumb_header = Some(new_header);
17701 }
17702
17703 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17704 self.clear_background_highlights::<SearchWithinRange>(cx);
17705 }
17706
17707 pub fn highlight_background<T: 'static>(
17708 &mut self,
17709 ranges: &[Range<Anchor>],
17710 color_fetcher: fn(&ThemeColors) -> Hsla,
17711 cx: &mut Context<Self>,
17712 ) {
17713 self.background_highlights
17714 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17715 self.scrollbar_marker_state.dirty = true;
17716 cx.notify();
17717 }
17718
17719 pub fn clear_background_highlights<T: 'static>(
17720 &mut self,
17721 cx: &mut Context<Self>,
17722 ) -> Option<BackgroundHighlight> {
17723 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17724 if !text_highlights.1.is_empty() {
17725 self.scrollbar_marker_state.dirty = true;
17726 cx.notify();
17727 }
17728 Some(text_highlights)
17729 }
17730
17731 pub fn highlight_gutter<T: 'static>(
17732 &mut self,
17733 ranges: &[Range<Anchor>],
17734 color_fetcher: fn(&App) -> Hsla,
17735 cx: &mut Context<Self>,
17736 ) {
17737 self.gutter_highlights
17738 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17739 cx.notify();
17740 }
17741
17742 pub fn clear_gutter_highlights<T: 'static>(
17743 &mut self,
17744 cx: &mut Context<Self>,
17745 ) -> Option<GutterHighlight> {
17746 cx.notify();
17747 self.gutter_highlights.remove(&TypeId::of::<T>())
17748 }
17749
17750 #[cfg(feature = "test-support")]
17751 pub fn all_text_background_highlights(
17752 &self,
17753 window: &mut Window,
17754 cx: &mut Context<Self>,
17755 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17756 let snapshot = self.snapshot(window, cx);
17757 let buffer = &snapshot.buffer_snapshot;
17758 let start = buffer.anchor_before(0);
17759 let end = buffer.anchor_after(buffer.len());
17760 let theme = cx.theme().colors();
17761 self.background_highlights_in_range(start..end, &snapshot, theme)
17762 }
17763
17764 #[cfg(feature = "test-support")]
17765 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17766 let snapshot = self.buffer().read(cx).snapshot(cx);
17767
17768 let highlights = self
17769 .background_highlights
17770 .get(&TypeId::of::<items::BufferSearchHighlights>());
17771
17772 if let Some((_color, ranges)) = highlights {
17773 ranges
17774 .iter()
17775 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17776 .collect_vec()
17777 } else {
17778 vec![]
17779 }
17780 }
17781
17782 fn document_highlights_for_position<'a>(
17783 &'a self,
17784 position: Anchor,
17785 buffer: &'a MultiBufferSnapshot,
17786 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17787 let read_highlights = self
17788 .background_highlights
17789 .get(&TypeId::of::<DocumentHighlightRead>())
17790 .map(|h| &h.1);
17791 let write_highlights = self
17792 .background_highlights
17793 .get(&TypeId::of::<DocumentHighlightWrite>())
17794 .map(|h| &h.1);
17795 let left_position = position.bias_left(buffer);
17796 let right_position = position.bias_right(buffer);
17797 read_highlights
17798 .into_iter()
17799 .chain(write_highlights)
17800 .flat_map(move |ranges| {
17801 let start_ix = match ranges.binary_search_by(|probe| {
17802 let cmp = probe.end.cmp(&left_position, buffer);
17803 if cmp.is_ge() {
17804 Ordering::Greater
17805 } else {
17806 Ordering::Less
17807 }
17808 }) {
17809 Ok(i) | Err(i) => i,
17810 };
17811
17812 ranges[start_ix..]
17813 .iter()
17814 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17815 })
17816 }
17817
17818 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17819 self.background_highlights
17820 .get(&TypeId::of::<T>())
17821 .map_or(false, |(_, highlights)| !highlights.is_empty())
17822 }
17823
17824 pub fn background_highlights_in_range(
17825 &self,
17826 search_range: Range<Anchor>,
17827 display_snapshot: &DisplaySnapshot,
17828 theme: &ThemeColors,
17829 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17830 let mut results = Vec::new();
17831 for (color_fetcher, ranges) in self.background_highlights.values() {
17832 let color = color_fetcher(theme);
17833 let start_ix = match ranges.binary_search_by(|probe| {
17834 let cmp = probe
17835 .end
17836 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17837 if cmp.is_gt() {
17838 Ordering::Greater
17839 } else {
17840 Ordering::Less
17841 }
17842 }) {
17843 Ok(i) | Err(i) => i,
17844 };
17845 for range in &ranges[start_ix..] {
17846 if range
17847 .start
17848 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17849 .is_ge()
17850 {
17851 break;
17852 }
17853
17854 let start = range.start.to_display_point(display_snapshot);
17855 let end = range.end.to_display_point(display_snapshot);
17856 results.push((start..end, color))
17857 }
17858 }
17859 results
17860 }
17861
17862 pub fn background_highlight_row_ranges<T: 'static>(
17863 &self,
17864 search_range: Range<Anchor>,
17865 display_snapshot: &DisplaySnapshot,
17866 count: usize,
17867 ) -> Vec<RangeInclusive<DisplayPoint>> {
17868 let mut results = Vec::new();
17869 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17870 return vec![];
17871 };
17872
17873 let start_ix = match ranges.binary_search_by(|probe| {
17874 let cmp = probe
17875 .end
17876 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17877 if cmp.is_gt() {
17878 Ordering::Greater
17879 } else {
17880 Ordering::Less
17881 }
17882 }) {
17883 Ok(i) | Err(i) => i,
17884 };
17885 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17886 if let (Some(start_display), Some(end_display)) = (start, end) {
17887 results.push(
17888 start_display.to_display_point(display_snapshot)
17889 ..=end_display.to_display_point(display_snapshot),
17890 );
17891 }
17892 };
17893 let mut start_row: Option<Point> = None;
17894 let mut end_row: Option<Point> = None;
17895 if ranges.len() > count {
17896 return Vec::new();
17897 }
17898 for range in &ranges[start_ix..] {
17899 if range
17900 .start
17901 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17902 .is_ge()
17903 {
17904 break;
17905 }
17906 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17907 if let Some(current_row) = &end_row {
17908 if end.row == current_row.row {
17909 continue;
17910 }
17911 }
17912 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17913 if start_row.is_none() {
17914 assert_eq!(end_row, None);
17915 start_row = Some(start);
17916 end_row = Some(end);
17917 continue;
17918 }
17919 if let Some(current_end) = end_row.as_mut() {
17920 if start.row > current_end.row + 1 {
17921 push_region(start_row, end_row);
17922 start_row = Some(start);
17923 end_row = Some(end);
17924 } else {
17925 // Merge two hunks.
17926 *current_end = end;
17927 }
17928 } else {
17929 unreachable!();
17930 }
17931 }
17932 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17933 push_region(start_row, end_row);
17934 results
17935 }
17936
17937 pub fn gutter_highlights_in_range(
17938 &self,
17939 search_range: Range<Anchor>,
17940 display_snapshot: &DisplaySnapshot,
17941 cx: &App,
17942 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17943 let mut results = Vec::new();
17944 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17945 let color = color_fetcher(cx);
17946 let start_ix = match ranges.binary_search_by(|probe| {
17947 let cmp = probe
17948 .end
17949 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17950 if cmp.is_gt() {
17951 Ordering::Greater
17952 } else {
17953 Ordering::Less
17954 }
17955 }) {
17956 Ok(i) | Err(i) => i,
17957 };
17958 for range in &ranges[start_ix..] {
17959 if range
17960 .start
17961 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17962 .is_ge()
17963 {
17964 break;
17965 }
17966
17967 let start = range.start.to_display_point(display_snapshot);
17968 let end = range.end.to_display_point(display_snapshot);
17969 results.push((start..end, color))
17970 }
17971 }
17972 results
17973 }
17974
17975 /// Get the text ranges corresponding to the redaction query
17976 pub fn redacted_ranges(
17977 &self,
17978 search_range: Range<Anchor>,
17979 display_snapshot: &DisplaySnapshot,
17980 cx: &App,
17981 ) -> Vec<Range<DisplayPoint>> {
17982 display_snapshot
17983 .buffer_snapshot
17984 .redacted_ranges(search_range, |file| {
17985 if let Some(file) = file {
17986 file.is_private()
17987 && EditorSettings::get(
17988 Some(SettingsLocation {
17989 worktree_id: file.worktree_id(cx),
17990 path: file.path().as_ref(),
17991 }),
17992 cx,
17993 )
17994 .redact_private_values
17995 } else {
17996 false
17997 }
17998 })
17999 .map(|range| {
18000 range.start.to_display_point(display_snapshot)
18001 ..range.end.to_display_point(display_snapshot)
18002 })
18003 .collect()
18004 }
18005
18006 pub fn highlight_text<T: 'static>(
18007 &mut self,
18008 ranges: Vec<Range<Anchor>>,
18009 style: HighlightStyle,
18010 cx: &mut Context<Self>,
18011 ) {
18012 self.display_map.update(cx, |map, _| {
18013 map.highlight_text(TypeId::of::<T>(), ranges, style)
18014 });
18015 cx.notify();
18016 }
18017
18018 pub(crate) fn highlight_inlays<T: 'static>(
18019 &mut self,
18020 highlights: Vec<InlayHighlight>,
18021 style: HighlightStyle,
18022 cx: &mut Context<Self>,
18023 ) {
18024 self.display_map.update(cx, |map, _| {
18025 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18026 });
18027 cx.notify();
18028 }
18029
18030 pub fn text_highlights<'a, T: 'static>(
18031 &'a self,
18032 cx: &'a App,
18033 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18034 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18035 }
18036
18037 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18038 let cleared = self
18039 .display_map
18040 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18041 if cleared {
18042 cx.notify();
18043 }
18044 }
18045
18046 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18047 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18048 && self.focus_handle.is_focused(window)
18049 }
18050
18051 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18052 self.show_cursor_when_unfocused = is_enabled;
18053 cx.notify();
18054 }
18055
18056 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18057 cx.notify();
18058 }
18059
18060 fn on_debug_session_event(
18061 &mut self,
18062 _session: Entity<Session>,
18063 event: &SessionEvent,
18064 cx: &mut Context<Self>,
18065 ) {
18066 match event {
18067 SessionEvent::InvalidateInlineValue => {
18068 self.refresh_inline_values(cx);
18069 }
18070 _ => {}
18071 }
18072 }
18073
18074 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18075 let Some(project) = self.project.clone() else {
18076 return;
18077 };
18078
18079 if !self.inline_value_cache.enabled {
18080 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18081 self.splice_inlays(&inlays, Vec::new(), cx);
18082 return;
18083 }
18084
18085 let current_execution_position = self
18086 .highlighted_rows
18087 .get(&TypeId::of::<ActiveDebugLine>())
18088 .and_then(|lines| lines.last().map(|line| line.range.start));
18089
18090 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18091 let inline_values = editor
18092 .update(cx, |editor, cx| {
18093 let Some(current_execution_position) = current_execution_position else {
18094 return Some(Task::ready(Ok(Vec::new())));
18095 };
18096
18097 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18098 let snapshot = buffer.snapshot(cx);
18099
18100 let excerpt = snapshot.excerpt_containing(
18101 current_execution_position..current_execution_position,
18102 )?;
18103
18104 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18105 })?;
18106
18107 let range =
18108 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18109
18110 project.inline_values(buffer, range, cx)
18111 })
18112 .ok()
18113 .flatten()?
18114 .await
18115 .context("refreshing debugger inlays")
18116 .log_err()?;
18117
18118 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18119
18120 for (buffer_id, inline_value) in inline_values
18121 .into_iter()
18122 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18123 {
18124 buffer_inline_values
18125 .entry(buffer_id)
18126 .or_default()
18127 .push(inline_value);
18128 }
18129
18130 editor
18131 .update(cx, |editor, cx| {
18132 let snapshot = editor.buffer.read(cx).snapshot(cx);
18133 let mut new_inlays = Vec::default();
18134
18135 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18136 let buffer_id = buffer_snapshot.remote_id();
18137 buffer_inline_values
18138 .get(&buffer_id)
18139 .into_iter()
18140 .flatten()
18141 .for_each(|hint| {
18142 let inlay = Inlay::debugger_hint(
18143 post_inc(&mut editor.next_inlay_id),
18144 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18145 hint.text(),
18146 );
18147
18148 new_inlays.push(inlay);
18149 });
18150 }
18151
18152 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18153 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18154
18155 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18156 })
18157 .ok()?;
18158 Some(())
18159 });
18160 }
18161
18162 fn on_buffer_event(
18163 &mut self,
18164 multibuffer: &Entity<MultiBuffer>,
18165 event: &multi_buffer::Event,
18166 window: &mut Window,
18167 cx: &mut Context<Self>,
18168 ) {
18169 match event {
18170 multi_buffer::Event::Edited {
18171 singleton_buffer_edited,
18172 edited_buffer: buffer_edited,
18173 } => {
18174 self.scrollbar_marker_state.dirty = true;
18175 self.active_indent_guides_state.dirty = true;
18176 self.refresh_active_diagnostics(cx);
18177 self.refresh_code_actions(window, cx);
18178 self.refresh_selected_text_highlights(true, window, cx);
18179 refresh_matching_bracket_highlights(self, window, cx);
18180 if self.has_active_inline_completion() {
18181 self.update_visible_inline_completion(window, cx);
18182 }
18183 if let Some(buffer) = buffer_edited {
18184 let buffer_id = buffer.read(cx).remote_id();
18185 if !self.registered_buffers.contains_key(&buffer_id) {
18186 if let Some(project) = self.project.as_ref() {
18187 project.update(cx, |project, cx| {
18188 self.registered_buffers.insert(
18189 buffer_id,
18190 project.register_buffer_with_language_servers(&buffer, cx),
18191 );
18192 })
18193 }
18194 }
18195 }
18196 cx.emit(EditorEvent::BufferEdited);
18197 cx.emit(SearchEvent::MatchesInvalidated);
18198 if *singleton_buffer_edited {
18199 if let Some(project) = &self.project {
18200 #[allow(clippy::mutable_key_type)]
18201 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18202 multibuffer
18203 .all_buffers()
18204 .into_iter()
18205 .filter_map(|buffer| {
18206 buffer.update(cx, |buffer, cx| {
18207 let language = buffer.language()?;
18208 let should_discard = project.update(cx, |project, cx| {
18209 project.is_local()
18210 && !project.has_language_servers_for(buffer, cx)
18211 });
18212 should_discard.not().then_some(language.clone())
18213 })
18214 })
18215 .collect::<HashSet<_>>()
18216 });
18217 if !languages_affected.is_empty() {
18218 self.refresh_inlay_hints(
18219 InlayHintRefreshReason::BufferEdited(languages_affected),
18220 cx,
18221 );
18222 }
18223 }
18224 }
18225
18226 let Some(project) = &self.project else { return };
18227 let (telemetry, is_via_ssh) = {
18228 let project = project.read(cx);
18229 let telemetry = project.client().telemetry().clone();
18230 let is_via_ssh = project.is_via_ssh();
18231 (telemetry, is_via_ssh)
18232 };
18233 refresh_linked_ranges(self, window, cx);
18234 telemetry.log_edit_event("editor", is_via_ssh);
18235 }
18236 multi_buffer::Event::ExcerptsAdded {
18237 buffer,
18238 predecessor,
18239 excerpts,
18240 } => {
18241 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18242 let buffer_id = buffer.read(cx).remote_id();
18243 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18244 if let Some(project) = &self.project {
18245 update_uncommitted_diff_for_buffer(
18246 cx.entity(),
18247 project,
18248 [buffer.clone()],
18249 self.buffer.clone(),
18250 cx,
18251 )
18252 .detach();
18253 }
18254 }
18255 cx.emit(EditorEvent::ExcerptsAdded {
18256 buffer: buffer.clone(),
18257 predecessor: *predecessor,
18258 excerpts: excerpts.clone(),
18259 });
18260 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18261 }
18262 multi_buffer::Event::ExcerptsRemoved {
18263 ids,
18264 removed_buffer_ids,
18265 } => {
18266 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18267 let buffer = self.buffer.read(cx);
18268 self.registered_buffers
18269 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18270 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18271 cx.emit(EditorEvent::ExcerptsRemoved {
18272 ids: ids.clone(),
18273 removed_buffer_ids: removed_buffer_ids.clone(),
18274 })
18275 }
18276 multi_buffer::Event::ExcerptsEdited {
18277 excerpt_ids,
18278 buffer_ids,
18279 } => {
18280 self.display_map.update(cx, |map, cx| {
18281 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18282 });
18283 cx.emit(EditorEvent::ExcerptsEdited {
18284 ids: excerpt_ids.clone(),
18285 })
18286 }
18287 multi_buffer::Event::ExcerptsExpanded { ids } => {
18288 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18289 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18290 }
18291 multi_buffer::Event::Reparsed(buffer_id) => {
18292 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18293 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18294
18295 cx.emit(EditorEvent::Reparsed(*buffer_id));
18296 }
18297 multi_buffer::Event::DiffHunksToggled => {
18298 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18299 }
18300 multi_buffer::Event::LanguageChanged(buffer_id) => {
18301 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18302 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18303 cx.emit(EditorEvent::Reparsed(*buffer_id));
18304 cx.notify();
18305 }
18306 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18307 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18308 multi_buffer::Event::FileHandleChanged
18309 | multi_buffer::Event::Reloaded
18310 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18311 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18312 multi_buffer::Event::DiagnosticsUpdated => {
18313 self.refresh_active_diagnostics(cx);
18314 self.refresh_inline_diagnostics(true, window, cx);
18315 self.scrollbar_marker_state.dirty = true;
18316 cx.notify();
18317 }
18318 _ => {}
18319 };
18320 }
18321
18322 pub fn start_temporary_diff_override(&mut self) {
18323 self.load_diff_task.take();
18324 self.temporary_diff_override = true;
18325 }
18326
18327 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18328 self.temporary_diff_override = false;
18329 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18330 self.buffer.update(cx, |buffer, cx| {
18331 buffer.set_all_diff_hunks_collapsed(cx);
18332 });
18333
18334 if let Some(project) = self.project.clone() {
18335 self.load_diff_task = Some(
18336 update_uncommitted_diff_for_buffer(
18337 cx.entity(),
18338 &project,
18339 self.buffer.read(cx).all_buffers(),
18340 self.buffer.clone(),
18341 cx,
18342 )
18343 .shared(),
18344 );
18345 }
18346 }
18347
18348 fn on_display_map_changed(
18349 &mut self,
18350 _: Entity<DisplayMap>,
18351 _: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 cx.notify();
18355 }
18356
18357 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18358 let new_severity = if self.diagnostics_enabled() {
18359 EditorSettings::get_global(cx)
18360 .diagnostics_max_severity
18361 .unwrap_or(DiagnosticSeverity::Hint)
18362 } else {
18363 DiagnosticSeverity::Off
18364 };
18365 self.set_max_diagnostics_severity(new_severity, cx);
18366 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18367 self.update_edit_prediction_settings(cx);
18368 self.refresh_inline_completion(true, false, window, cx);
18369 self.refresh_inlay_hints(
18370 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18371 self.selections.newest_anchor().head(),
18372 &self.buffer.read(cx).snapshot(cx),
18373 cx,
18374 )),
18375 cx,
18376 );
18377
18378 let old_cursor_shape = self.cursor_shape;
18379
18380 {
18381 let editor_settings = EditorSettings::get_global(cx);
18382 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18383 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18384 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18385 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18386 }
18387
18388 if old_cursor_shape != self.cursor_shape {
18389 cx.emit(EditorEvent::CursorShapeChanged);
18390 }
18391
18392 let project_settings = ProjectSettings::get_global(cx);
18393 self.serialize_dirty_buffers =
18394 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18395
18396 if self.mode.is_full() {
18397 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18398 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18399 if self.show_inline_diagnostics != show_inline_diagnostics {
18400 self.show_inline_diagnostics = show_inline_diagnostics;
18401 self.refresh_inline_diagnostics(false, window, cx);
18402 }
18403
18404 if self.git_blame_inline_enabled != inline_blame_enabled {
18405 self.toggle_git_blame_inline_internal(false, window, cx);
18406 }
18407
18408 let minimap_settings = EditorSettings::get_global(cx).minimap;
18409 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18410 self.set_minimap_visibility(
18411 self.minimap_visibility.toggle_visibility(),
18412 window,
18413 cx,
18414 );
18415 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18416 minimap_entity.update(cx, |minimap_editor, cx| {
18417 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18418 })
18419 }
18420 }
18421
18422 cx.notify();
18423 }
18424
18425 pub fn set_searchable(&mut self, searchable: bool) {
18426 self.searchable = searchable;
18427 }
18428
18429 pub fn searchable(&self) -> bool {
18430 self.searchable
18431 }
18432
18433 fn open_proposed_changes_editor(
18434 &mut self,
18435 _: &OpenProposedChangesEditor,
18436 window: &mut Window,
18437 cx: &mut Context<Self>,
18438 ) {
18439 let Some(workspace) = self.workspace() else {
18440 cx.propagate();
18441 return;
18442 };
18443
18444 let selections = self.selections.all::<usize>(cx);
18445 let multi_buffer = self.buffer.read(cx);
18446 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18447 let mut new_selections_by_buffer = HashMap::default();
18448 for selection in selections {
18449 for (buffer, range, _) in
18450 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18451 {
18452 let mut range = range.to_point(buffer);
18453 range.start.column = 0;
18454 range.end.column = buffer.line_len(range.end.row);
18455 new_selections_by_buffer
18456 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18457 .or_insert(Vec::new())
18458 .push(range)
18459 }
18460 }
18461
18462 let proposed_changes_buffers = new_selections_by_buffer
18463 .into_iter()
18464 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18465 .collect::<Vec<_>>();
18466 let proposed_changes_editor = cx.new(|cx| {
18467 ProposedChangesEditor::new(
18468 "Proposed changes",
18469 proposed_changes_buffers,
18470 self.project.clone(),
18471 window,
18472 cx,
18473 )
18474 });
18475
18476 window.defer(cx, move |window, cx| {
18477 workspace.update(cx, |workspace, cx| {
18478 workspace.active_pane().update(cx, |pane, cx| {
18479 pane.add_item(
18480 Box::new(proposed_changes_editor),
18481 true,
18482 true,
18483 None,
18484 window,
18485 cx,
18486 );
18487 });
18488 });
18489 });
18490 }
18491
18492 pub fn open_excerpts_in_split(
18493 &mut self,
18494 _: &OpenExcerptsSplit,
18495 window: &mut Window,
18496 cx: &mut Context<Self>,
18497 ) {
18498 self.open_excerpts_common(None, true, window, cx)
18499 }
18500
18501 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18502 self.open_excerpts_common(None, false, window, cx)
18503 }
18504
18505 fn open_excerpts_common(
18506 &mut self,
18507 jump_data: Option<JumpData>,
18508 split: bool,
18509 window: &mut Window,
18510 cx: &mut Context<Self>,
18511 ) {
18512 let Some(workspace) = self.workspace() else {
18513 cx.propagate();
18514 return;
18515 };
18516
18517 if self.buffer.read(cx).is_singleton() {
18518 cx.propagate();
18519 return;
18520 }
18521
18522 let mut new_selections_by_buffer = HashMap::default();
18523 match &jump_data {
18524 Some(JumpData::MultiBufferPoint {
18525 excerpt_id,
18526 position,
18527 anchor,
18528 line_offset_from_top,
18529 }) => {
18530 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18531 if let Some(buffer) = multi_buffer_snapshot
18532 .buffer_id_for_excerpt(*excerpt_id)
18533 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18534 {
18535 let buffer_snapshot = buffer.read(cx).snapshot();
18536 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18537 language::ToPoint::to_point(anchor, &buffer_snapshot)
18538 } else {
18539 buffer_snapshot.clip_point(*position, Bias::Left)
18540 };
18541 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18542 new_selections_by_buffer.insert(
18543 buffer,
18544 (
18545 vec![jump_to_offset..jump_to_offset],
18546 Some(*line_offset_from_top),
18547 ),
18548 );
18549 }
18550 }
18551 Some(JumpData::MultiBufferRow {
18552 row,
18553 line_offset_from_top,
18554 }) => {
18555 let point = MultiBufferPoint::new(row.0, 0);
18556 if let Some((buffer, buffer_point, _)) =
18557 self.buffer.read(cx).point_to_buffer_point(point, cx)
18558 {
18559 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18560 new_selections_by_buffer
18561 .entry(buffer)
18562 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18563 .0
18564 .push(buffer_offset..buffer_offset)
18565 }
18566 }
18567 None => {
18568 let selections = self.selections.all::<usize>(cx);
18569 let multi_buffer = self.buffer.read(cx);
18570 for selection in selections {
18571 for (snapshot, range, _, anchor) in multi_buffer
18572 .snapshot(cx)
18573 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18574 {
18575 if let Some(anchor) = anchor {
18576 // selection is in a deleted hunk
18577 let Some(buffer_id) = anchor.buffer_id else {
18578 continue;
18579 };
18580 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18581 continue;
18582 };
18583 let offset = text::ToOffset::to_offset(
18584 &anchor.text_anchor,
18585 &buffer_handle.read(cx).snapshot(),
18586 );
18587 let range = offset..offset;
18588 new_selections_by_buffer
18589 .entry(buffer_handle)
18590 .or_insert((Vec::new(), None))
18591 .0
18592 .push(range)
18593 } else {
18594 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18595 else {
18596 continue;
18597 };
18598 new_selections_by_buffer
18599 .entry(buffer_handle)
18600 .or_insert((Vec::new(), None))
18601 .0
18602 .push(range)
18603 }
18604 }
18605 }
18606 }
18607 }
18608
18609 new_selections_by_buffer
18610 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18611
18612 if new_selections_by_buffer.is_empty() {
18613 return;
18614 }
18615
18616 // We defer the pane interaction because we ourselves are a workspace item
18617 // and activating a new item causes the pane to call a method on us reentrantly,
18618 // which panics if we're on the stack.
18619 window.defer(cx, move |window, cx| {
18620 workspace.update(cx, |workspace, cx| {
18621 let pane = if split {
18622 workspace.adjacent_pane(window, cx)
18623 } else {
18624 workspace.active_pane().clone()
18625 };
18626
18627 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18628 let editor = buffer
18629 .read(cx)
18630 .file()
18631 .is_none()
18632 .then(|| {
18633 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18634 // so `workspace.open_project_item` will never find them, always opening a new editor.
18635 // Instead, we try to activate the existing editor in the pane first.
18636 let (editor, pane_item_index) =
18637 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18638 let editor = item.downcast::<Editor>()?;
18639 let singleton_buffer =
18640 editor.read(cx).buffer().read(cx).as_singleton()?;
18641 if singleton_buffer == buffer {
18642 Some((editor, i))
18643 } else {
18644 None
18645 }
18646 })?;
18647 pane.update(cx, |pane, cx| {
18648 pane.activate_item(pane_item_index, true, true, window, cx)
18649 });
18650 Some(editor)
18651 })
18652 .flatten()
18653 .unwrap_or_else(|| {
18654 workspace.open_project_item::<Self>(
18655 pane.clone(),
18656 buffer,
18657 true,
18658 true,
18659 window,
18660 cx,
18661 )
18662 });
18663
18664 editor.update(cx, |editor, cx| {
18665 let autoscroll = match scroll_offset {
18666 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18667 None => Autoscroll::newest(),
18668 };
18669 let nav_history = editor.nav_history.take();
18670 editor.change_selections(Some(autoscroll), window, cx, |s| {
18671 s.select_ranges(ranges);
18672 });
18673 editor.nav_history = nav_history;
18674 });
18675 }
18676 })
18677 });
18678 }
18679
18680 // For now, don't allow opening excerpts in buffers that aren't backed by
18681 // regular project files.
18682 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18683 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18684 }
18685
18686 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18687 let snapshot = self.buffer.read(cx).read(cx);
18688 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18689 Some(
18690 ranges
18691 .iter()
18692 .map(move |range| {
18693 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18694 })
18695 .collect(),
18696 )
18697 }
18698
18699 fn selection_replacement_ranges(
18700 &self,
18701 range: Range<OffsetUtf16>,
18702 cx: &mut App,
18703 ) -> Vec<Range<OffsetUtf16>> {
18704 let selections = self.selections.all::<OffsetUtf16>(cx);
18705 let newest_selection = selections
18706 .iter()
18707 .max_by_key(|selection| selection.id)
18708 .unwrap();
18709 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18710 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18711 let snapshot = self.buffer.read(cx).read(cx);
18712 selections
18713 .into_iter()
18714 .map(|mut selection| {
18715 selection.start.0 =
18716 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18717 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18718 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18719 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18720 })
18721 .collect()
18722 }
18723
18724 fn report_editor_event(
18725 &self,
18726 event_type: &'static str,
18727 file_extension: Option<String>,
18728 cx: &App,
18729 ) {
18730 if cfg!(any(test, feature = "test-support")) {
18731 return;
18732 }
18733
18734 let Some(project) = &self.project else { return };
18735
18736 // If None, we are in a file without an extension
18737 let file = self
18738 .buffer
18739 .read(cx)
18740 .as_singleton()
18741 .and_then(|b| b.read(cx).file());
18742 let file_extension = file_extension.or(file
18743 .as_ref()
18744 .and_then(|file| Path::new(file.file_name(cx)).extension())
18745 .and_then(|e| e.to_str())
18746 .map(|a| a.to_string()));
18747
18748 let vim_mode = vim_enabled(cx);
18749
18750 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18751 let copilot_enabled = edit_predictions_provider
18752 == language::language_settings::EditPredictionProvider::Copilot;
18753 let copilot_enabled_for_language = self
18754 .buffer
18755 .read(cx)
18756 .language_settings(cx)
18757 .show_edit_predictions;
18758
18759 let project = project.read(cx);
18760 telemetry::event!(
18761 event_type,
18762 file_extension,
18763 vim_mode,
18764 copilot_enabled,
18765 copilot_enabled_for_language,
18766 edit_predictions_provider,
18767 is_via_ssh = project.is_via_ssh(),
18768 );
18769 }
18770
18771 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18772 /// with each line being an array of {text, highlight} objects.
18773 fn copy_highlight_json(
18774 &mut self,
18775 _: &CopyHighlightJson,
18776 window: &mut Window,
18777 cx: &mut Context<Self>,
18778 ) {
18779 #[derive(Serialize)]
18780 struct Chunk<'a> {
18781 text: String,
18782 highlight: Option<&'a str>,
18783 }
18784
18785 let snapshot = self.buffer.read(cx).snapshot(cx);
18786 let range = self
18787 .selected_text_range(false, window, cx)
18788 .and_then(|selection| {
18789 if selection.range.is_empty() {
18790 None
18791 } else {
18792 Some(selection.range)
18793 }
18794 })
18795 .unwrap_or_else(|| 0..snapshot.len());
18796
18797 let chunks = snapshot.chunks(range, true);
18798 let mut lines = Vec::new();
18799 let mut line: VecDeque<Chunk> = VecDeque::new();
18800
18801 let Some(style) = self.style.as_ref() else {
18802 return;
18803 };
18804
18805 for chunk in chunks {
18806 let highlight = chunk
18807 .syntax_highlight_id
18808 .and_then(|id| id.name(&style.syntax));
18809 let mut chunk_lines = chunk.text.split('\n').peekable();
18810 while let Some(text) = chunk_lines.next() {
18811 let mut merged_with_last_token = false;
18812 if let Some(last_token) = line.back_mut() {
18813 if last_token.highlight == highlight {
18814 last_token.text.push_str(text);
18815 merged_with_last_token = true;
18816 }
18817 }
18818
18819 if !merged_with_last_token {
18820 line.push_back(Chunk {
18821 text: text.into(),
18822 highlight,
18823 });
18824 }
18825
18826 if chunk_lines.peek().is_some() {
18827 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18828 line.pop_front();
18829 }
18830 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18831 line.pop_back();
18832 }
18833
18834 lines.push(mem::take(&mut line));
18835 }
18836 }
18837 }
18838
18839 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18840 return;
18841 };
18842 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18843 }
18844
18845 pub fn open_context_menu(
18846 &mut self,
18847 _: &OpenContextMenu,
18848 window: &mut Window,
18849 cx: &mut Context<Self>,
18850 ) {
18851 self.request_autoscroll(Autoscroll::newest(), cx);
18852 let position = self.selections.newest_display(cx).start;
18853 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18854 }
18855
18856 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18857 &self.inlay_hint_cache
18858 }
18859
18860 pub fn replay_insert_event(
18861 &mut self,
18862 text: &str,
18863 relative_utf16_range: Option<Range<isize>>,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 if !self.input_enabled {
18868 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18869 return;
18870 }
18871 if let Some(relative_utf16_range) = relative_utf16_range {
18872 let selections = self.selections.all::<OffsetUtf16>(cx);
18873 self.change_selections(None, window, cx, |s| {
18874 let new_ranges = selections.into_iter().map(|range| {
18875 let start = OffsetUtf16(
18876 range
18877 .head()
18878 .0
18879 .saturating_add_signed(relative_utf16_range.start),
18880 );
18881 let end = OffsetUtf16(
18882 range
18883 .head()
18884 .0
18885 .saturating_add_signed(relative_utf16_range.end),
18886 );
18887 start..end
18888 });
18889 s.select_ranges(new_ranges);
18890 });
18891 }
18892
18893 self.handle_input(text, window, cx);
18894 }
18895
18896 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18897 let Some(provider) = self.semantics_provider.as_ref() else {
18898 return false;
18899 };
18900
18901 let mut supports = false;
18902 self.buffer().update(cx, |this, cx| {
18903 this.for_each_buffer(|buffer| {
18904 supports |= provider.supports_inlay_hints(buffer, cx);
18905 });
18906 });
18907
18908 supports
18909 }
18910
18911 pub fn is_focused(&self, window: &Window) -> bool {
18912 self.focus_handle.is_focused(window)
18913 }
18914
18915 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18916 cx.emit(EditorEvent::Focused);
18917
18918 if let Some(descendant) = self
18919 .last_focused_descendant
18920 .take()
18921 .and_then(|descendant| descendant.upgrade())
18922 {
18923 window.focus(&descendant);
18924 } else {
18925 if let Some(blame) = self.blame.as_ref() {
18926 blame.update(cx, GitBlame::focus)
18927 }
18928
18929 self.blink_manager.update(cx, BlinkManager::enable);
18930 self.show_cursor_names(window, cx);
18931 self.buffer.update(cx, |buffer, cx| {
18932 buffer.finalize_last_transaction(cx);
18933 if self.leader_id.is_none() {
18934 buffer.set_active_selections(
18935 &self.selections.disjoint_anchors(),
18936 self.selections.line_mode,
18937 self.cursor_shape,
18938 cx,
18939 );
18940 }
18941 });
18942 }
18943 }
18944
18945 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18946 cx.emit(EditorEvent::FocusedIn)
18947 }
18948
18949 fn handle_focus_out(
18950 &mut self,
18951 event: FocusOutEvent,
18952 _window: &mut Window,
18953 cx: &mut Context<Self>,
18954 ) {
18955 if event.blurred != self.focus_handle {
18956 self.last_focused_descendant = Some(event.blurred);
18957 }
18958 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18959 }
18960
18961 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18962 self.blink_manager.update(cx, BlinkManager::disable);
18963 self.buffer
18964 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18965
18966 if let Some(blame) = self.blame.as_ref() {
18967 blame.update(cx, GitBlame::blur)
18968 }
18969 if !self.hover_state.focused(window, cx) {
18970 hide_hover(self, cx);
18971 }
18972 if !self
18973 .context_menu
18974 .borrow()
18975 .as_ref()
18976 .is_some_and(|context_menu| context_menu.focused(window, cx))
18977 {
18978 self.hide_context_menu(window, cx);
18979 }
18980 self.discard_inline_completion(false, cx);
18981 cx.emit(EditorEvent::Blurred);
18982 cx.notify();
18983 }
18984
18985 pub fn register_action<A: Action>(
18986 &mut self,
18987 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18988 ) -> Subscription {
18989 let id = self.next_editor_action_id.post_inc();
18990 let listener = Arc::new(listener);
18991 self.editor_actions.borrow_mut().insert(
18992 id,
18993 Box::new(move |window, _| {
18994 let listener = listener.clone();
18995 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18996 let action = action.downcast_ref().unwrap();
18997 if phase == DispatchPhase::Bubble {
18998 listener(action, window, cx)
18999 }
19000 })
19001 }),
19002 );
19003
19004 let editor_actions = self.editor_actions.clone();
19005 Subscription::new(move || {
19006 editor_actions.borrow_mut().remove(&id);
19007 })
19008 }
19009
19010 pub fn file_header_size(&self) -> u32 {
19011 FILE_HEADER_HEIGHT
19012 }
19013
19014 pub fn restore(
19015 &mut self,
19016 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19017 window: &mut Window,
19018 cx: &mut Context<Self>,
19019 ) {
19020 let workspace = self.workspace();
19021 let project = self.project.as_ref();
19022 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19023 let mut tasks = Vec::new();
19024 for (buffer_id, changes) in revert_changes {
19025 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19026 buffer.update(cx, |buffer, cx| {
19027 buffer.edit(
19028 changes
19029 .into_iter()
19030 .map(|(range, text)| (range, text.to_string())),
19031 None,
19032 cx,
19033 );
19034 });
19035
19036 if let Some(project) =
19037 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19038 {
19039 project.update(cx, |project, cx| {
19040 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19041 })
19042 }
19043 }
19044 }
19045 tasks
19046 });
19047 cx.spawn_in(window, async move |_, cx| {
19048 for (buffer, task) in save_tasks {
19049 let result = task.await;
19050 if result.is_err() {
19051 let Some(path) = buffer
19052 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19053 .ok()
19054 else {
19055 continue;
19056 };
19057 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19058 let Some(task) = cx
19059 .update_window_entity(&workspace, |workspace, window, cx| {
19060 workspace
19061 .open_path_preview(path, None, false, false, false, window, cx)
19062 })
19063 .ok()
19064 else {
19065 continue;
19066 };
19067 task.await.log_err();
19068 }
19069 }
19070 }
19071 })
19072 .detach();
19073 self.change_selections(None, window, cx, |selections| selections.refresh());
19074 }
19075
19076 pub fn to_pixel_point(
19077 &self,
19078 source: multi_buffer::Anchor,
19079 editor_snapshot: &EditorSnapshot,
19080 window: &mut Window,
19081 ) -> Option<gpui::Point<Pixels>> {
19082 let source_point = source.to_display_point(editor_snapshot);
19083 self.display_to_pixel_point(source_point, editor_snapshot, window)
19084 }
19085
19086 pub fn display_to_pixel_point(
19087 &self,
19088 source: DisplayPoint,
19089 editor_snapshot: &EditorSnapshot,
19090 window: &mut Window,
19091 ) -> Option<gpui::Point<Pixels>> {
19092 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19093 let text_layout_details = self.text_layout_details(window);
19094 let scroll_top = text_layout_details
19095 .scroll_anchor
19096 .scroll_position(editor_snapshot)
19097 .y;
19098
19099 if source.row().as_f32() < scroll_top.floor() {
19100 return None;
19101 }
19102 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19103 let source_y = line_height * (source.row().as_f32() - scroll_top);
19104 Some(gpui::Point::new(source_x, source_y))
19105 }
19106
19107 pub fn has_visible_completions_menu(&self) -> bool {
19108 !self.edit_prediction_preview_is_active()
19109 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19110 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19111 })
19112 }
19113
19114 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19115 if self.mode.is_minimap() {
19116 return;
19117 }
19118 self.addons
19119 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19120 }
19121
19122 pub fn unregister_addon<T: Addon>(&mut self) {
19123 self.addons.remove(&std::any::TypeId::of::<T>());
19124 }
19125
19126 pub fn addon<T: Addon>(&self) -> Option<&T> {
19127 let type_id = std::any::TypeId::of::<T>();
19128 self.addons
19129 .get(&type_id)
19130 .and_then(|item| item.to_any().downcast_ref::<T>())
19131 }
19132
19133 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19134 let type_id = std::any::TypeId::of::<T>();
19135 self.addons
19136 .get_mut(&type_id)
19137 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19138 }
19139
19140 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19141 let text_layout_details = self.text_layout_details(window);
19142 let style = &text_layout_details.editor_style;
19143 let font_id = window.text_system().resolve_font(&style.text.font());
19144 let font_size = style.text.font_size.to_pixels(window.rem_size());
19145 let line_height = style.text.line_height_in_pixels(window.rem_size());
19146 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19147
19148 gpui::Size::new(em_width, line_height)
19149 }
19150
19151 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19152 self.load_diff_task.clone()
19153 }
19154
19155 fn read_metadata_from_db(
19156 &mut self,
19157 item_id: u64,
19158 workspace_id: WorkspaceId,
19159 window: &mut Window,
19160 cx: &mut Context<Editor>,
19161 ) {
19162 if self.is_singleton(cx)
19163 && !self.mode.is_minimap()
19164 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19165 {
19166 let buffer_snapshot = OnceCell::new();
19167
19168 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19169 if !folds.is_empty() {
19170 let snapshot =
19171 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19172 self.fold_ranges(
19173 folds
19174 .into_iter()
19175 .map(|(start, end)| {
19176 snapshot.clip_offset(start, Bias::Left)
19177 ..snapshot.clip_offset(end, Bias::Right)
19178 })
19179 .collect(),
19180 false,
19181 window,
19182 cx,
19183 );
19184 }
19185 }
19186
19187 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19188 if !selections.is_empty() {
19189 let snapshot =
19190 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19191 self.change_selections(None, window, cx, |s| {
19192 s.select_ranges(selections.into_iter().map(|(start, end)| {
19193 snapshot.clip_offset(start, Bias::Left)
19194 ..snapshot.clip_offset(end, Bias::Right)
19195 }));
19196 });
19197 }
19198 };
19199 }
19200
19201 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19202 }
19203}
19204
19205fn vim_enabled(cx: &App) -> bool {
19206 cx.global::<SettingsStore>()
19207 .raw_user_settings()
19208 .get("vim_mode")
19209 == Some(&serde_json::Value::Bool(true))
19210}
19211
19212// Consider user intent and default settings
19213fn choose_completion_range(
19214 completion: &Completion,
19215 intent: CompletionIntent,
19216 buffer: &Entity<Buffer>,
19217 cx: &mut Context<Editor>,
19218) -> Range<usize> {
19219 fn should_replace(
19220 completion: &Completion,
19221 insert_range: &Range<text::Anchor>,
19222 intent: CompletionIntent,
19223 completion_mode_setting: LspInsertMode,
19224 buffer: &Buffer,
19225 ) -> bool {
19226 // specific actions take precedence over settings
19227 match intent {
19228 CompletionIntent::CompleteWithInsert => return false,
19229 CompletionIntent::CompleteWithReplace => return true,
19230 CompletionIntent::Complete | CompletionIntent::Compose => {}
19231 }
19232
19233 match completion_mode_setting {
19234 LspInsertMode::Insert => false,
19235 LspInsertMode::Replace => true,
19236 LspInsertMode::ReplaceSubsequence => {
19237 let mut text_to_replace = buffer.chars_for_range(
19238 buffer.anchor_before(completion.replace_range.start)
19239 ..buffer.anchor_after(completion.replace_range.end),
19240 );
19241 let mut completion_text = completion.new_text.chars();
19242
19243 // is `text_to_replace` a subsequence of `completion_text`
19244 text_to_replace
19245 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19246 }
19247 LspInsertMode::ReplaceSuffix => {
19248 let range_after_cursor = insert_range.end..completion.replace_range.end;
19249
19250 let text_after_cursor = buffer
19251 .text_for_range(
19252 buffer.anchor_before(range_after_cursor.start)
19253 ..buffer.anchor_after(range_after_cursor.end),
19254 )
19255 .collect::<String>();
19256 completion.new_text.ends_with(&text_after_cursor)
19257 }
19258 }
19259 }
19260
19261 let buffer = buffer.read(cx);
19262
19263 if let CompletionSource::Lsp {
19264 insert_range: Some(insert_range),
19265 ..
19266 } = &completion.source
19267 {
19268 let completion_mode_setting =
19269 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19270 .completions
19271 .lsp_insert_mode;
19272
19273 if !should_replace(
19274 completion,
19275 &insert_range,
19276 intent,
19277 completion_mode_setting,
19278 buffer,
19279 ) {
19280 return insert_range.to_offset(buffer);
19281 }
19282 }
19283
19284 completion.replace_range.to_offset(buffer)
19285}
19286
19287fn insert_extra_newline_brackets(
19288 buffer: &MultiBufferSnapshot,
19289 range: Range<usize>,
19290 language: &language::LanguageScope,
19291) -> bool {
19292 let leading_whitespace_len = buffer
19293 .reversed_chars_at(range.start)
19294 .take_while(|c| c.is_whitespace() && *c != '\n')
19295 .map(|c| c.len_utf8())
19296 .sum::<usize>();
19297 let trailing_whitespace_len = buffer
19298 .chars_at(range.end)
19299 .take_while(|c| c.is_whitespace() && *c != '\n')
19300 .map(|c| c.len_utf8())
19301 .sum::<usize>();
19302 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19303
19304 language.brackets().any(|(pair, enabled)| {
19305 let pair_start = pair.start.trim_end();
19306 let pair_end = pair.end.trim_start();
19307
19308 enabled
19309 && pair.newline
19310 && buffer.contains_str_at(range.end, pair_end)
19311 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19312 })
19313}
19314
19315fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19316 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19317 [(buffer, range, _)] => (*buffer, range.clone()),
19318 _ => return false,
19319 };
19320 let pair = {
19321 let mut result: Option<BracketMatch> = None;
19322
19323 for pair in buffer
19324 .all_bracket_ranges(range.clone())
19325 .filter(move |pair| {
19326 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19327 })
19328 {
19329 let len = pair.close_range.end - pair.open_range.start;
19330
19331 if let Some(existing) = &result {
19332 let existing_len = existing.close_range.end - existing.open_range.start;
19333 if len > existing_len {
19334 continue;
19335 }
19336 }
19337
19338 result = Some(pair);
19339 }
19340
19341 result
19342 };
19343 let Some(pair) = pair else {
19344 return false;
19345 };
19346 pair.newline_only
19347 && buffer
19348 .chars_for_range(pair.open_range.end..range.start)
19349 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19350 .all(|c| c.is_whitespace() && c != '\n')
19351}
19352
19353fn update_uncommitted_diff_for_buffer(
19354 editor: Entity<Editor>,
19355 project: &Entity<Project>,
19356 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19357 buffer: Entity<MultiBuffer>,
19358 cx: &mut App,
19359) -> Task<()> {
19360 let mut tasks = Vec::new();
19361 project.update(cx, |project, cx| {
19362 for buffer in buffers {
19363 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19364 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19365 }
19366 }
19367 });
19368 cx.spawn(async move |cx| {
19369 let diffs = future::join_all(tasks).await;
19370 if editor
19371 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19372 .unwrap_or(false)
19373 {
19374 return;
19375 }
19376
19377 buffer
19378 .update(cx, |buffer, cx| {
19379 for diff in diffs.into_iter().flatten() {
19380 buffer.add_diff(diff, cx);
19381 }
19382 })
19383 .ok();
19384 })
19385}
19386
19387fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19388 let tab_size = tab_size.get() as usize;
19389 let mut width = offset;
19390
19391 for ch in text.chars() {
19392 width += if ch == '\t' {
19393 tab_size - (width % tab_size)
19394 } else {
19395 1
19396 };
19397 }
19398
19399 width - offset
19400}
19401
19402#[cfg(test)]
19403mod tests {
19404 use super::*;
19405
19406 #[test]
19407 fn test_string_size_with_expanded_tabs() {
19408 let nz = |val| NonZeroU32::new(val).unwrap();
19409 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19410 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19411 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19412 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19413 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19414 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19415 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19416 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19417 }
19418}
19419
19420/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19421struct WordBreakingTokenizer<'a> {
19422 input: &'a str,
19423}
19424
19425impl<'a> WordBreakingTokenizer<'a> {
19426 fn new(input: &'a str) -> Self {
19427 Self { input }
19428 }
19429}
19430
19431fn is_char_ideographic(ch: char) -> bool {
19432 use unicode_script::Script::*;
19433 use unicode_script::UnicodeScript;
19434 matches!(ch.script(), Han | Tangut | Yi)
19435}
19436
19437fn is_grapheme_ideographic(text: &str) -> bool {
19438 text.chars().any(is_char_ideographic)
19439}
19440
19441fn is_grapheme_whitespace(text: &str) -> bool {
19442 text.chars().any(|x| x.is_whitespace())
19443}
19444
19445fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19446 text.chars().next().map_or(false, |ch| {
19447 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19448 })
19449}
19450
19451#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19452enum WordBreakToken<'a> {
19453 Word { token: &'a str, grapheme_len: usize },
19454 InlineWhitespace { token: &'a str, grapheme_len: usize },
19455 Newline,
19456}
19457
19458impl<'a> Iterator for WordBreakingTokenizer<'a> {
19459 /// Yields a span, the count of graphemes in the token, and whether it was
19460 /// whitespace. Note that it also breaks at word boundaries.
19461 type Item = WordBreakToken<'a>;
19462
19463 fn next(&mut self) -> Option<Self::Item> {
19464 use unicode_segmentation::UnicodeSegmentation;
19465 if self.input.is_empty() {
19466 return None;
19467 }
19468
19469 let mut iter = self.input.graphemes(true).peekable();
19470 let mut offset = 0;
19471 let mut grapheme_len = 0;
19472 if let Some(first_grapheme) = iter.next() {
19473 let is_newline = first_grapheme == "\n";
19474 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19475 offset += first_grapheme.len();
19476 grapheme_len += 1;
19477 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19478 if let Some(grapheme) = iter.peek().copied() {
19479 if should_stay_with_preceding_ideograph(grapheme) {
19480 offset += grapheme.len();
19481 grapheme_len += 1;
19482 }
19483 }
19484 } else {
19485 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19486 let mut next_word_bound = words.peek().copied();
19487 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19488 next_word_bound = words.next();
19489 }
19490 while let Some(grapheme) = iter.peek().copied() {
19491 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19492 break;
19493 };
19494 if is_grapheme_whitespace(grapheme) != is_whitespace
19495 || (grapheme == "\n") != is_newline
19496 {
19497 break;
19498 };
19499 offset += grapheme.len();
19500 grapheme_len += 1;
19501 iter.next();
19502 }
19503 }
19504 let token = &self.input[..offset];
19505 self.input = &self.input[offset..];
19506 if token == "\n" {
19507 Some(WordBreakToken::Newline)
19508 } else if is_whitespace {
19509 Some(WordBreakToken::InlineWhitespace {
19510 token,
19511 grapheme_len,
19512 })
19513 } else {
19514 Some(WordBreakToken::Word {
19515 token,
19516 grapheme_len,
19517 })
19518 }
19519 } else {
19520 None
19521 }
19522 }
19523}
19524
19525#[test]
19526fn test_word_breaking_tokenizer() {
19527 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19528 ("", &[]),
19529 (" ", &[whitespace(" ", 2)]),
19530 ("Ʒ", &[word("Ʒ", 1)]),
19531 ("Ǽ", &[word("Ǽ", 1)]),
19532 ("⋑", &[word("⋑", 1)]),
19533 ("⋑⋑", &[word("⋑⋑", 2)]),
19534 (
19535 "原理,进而",
19536 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19537 ),
19538 (
19539 "hello world",
19540 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19541 ),
19542 (
19543 "hello, world",
19544 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19545 ),
19546 (
19547 " hello world",
19548 &[
19549 whitespace(" ", 2),
19550 word("hello", 5),
19551 whitespace(" ", 1),
19552 word("world", 5),
19553 ],
19554 ),
19555 (
19556 "这是什么 \n 钢笔",
19557 &[
19558 word("这", 1),
19559 word("是", 1),
19560 word("什", 1),
19561 word("么", 1),
19562 whitespace(" ", 1),
19563 newline(),
19564 whitespace(" ", 1),
19565 word("钢", 1),
19566 word("笔", 1),
19567 ],
19568 ),
19569 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19570 ];
19571
19572 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19573 WordBreakToken::Word {
19574 token,
19575 grapheme_len,
19576 }
19577 }
19578
19579 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19580 WordBreakToken::InlineWhitespace {
19581 token,
19582 grapheme_len,
19583 }
19584 }
19585
19586 fn newline() -> WordBreakToken<'static> {
19587 WordBreakToken::Newline
19588 }
19589
19590 for (input, result) in tests {
19591 assert_eq!(
19592 WordBreakingTokenizer::new(input)
19593 .collect::<Vec<_>>()
19594 .as_slice(),
19595 *result,
19596 );
19597 }
19598}
19599
19600fn wrap_with_prefix(
19601 line_prefix: String,
19602 unwrapped_text: String,
19603 wrap_column: usize,
19604 tab_size: NonZeroU32,
19605 preserve_existing_whitespace: bool,
19606) -> String {
19607 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19608 let mut wrapped_text = String::new();
19609 let mut current_line = line_prefix.clone();
19610
19611 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19612 let mut current_line_len = line_prefix_len;
19613 let mut in_whitespace = false;
19614 for token in tokenizer {
19615 let have_preceding_whitespace = in_whitespace;
19616 match token {
19617 WordBreakToken::Word {
19618 token,
19619 grapheme_len,
19620 } => {
19621 in_whitespace = false;
19622 if current_line_len + grapheme_len > wrap_column
19623 && current_line_len != line_prefix_len
19624 {
19625 wrapped_text.push_str(current_line.trim_end());
19626 wrapped_text.push('\n');
19627 current_line.truncate(line_prefix.len());
19628 current_line_len = line_prefix_len;
19629 }
19630 current_line.push_str(token);
19631 current_line_len += grapheme_len;
19632 }
19633 WordBreakToken::InlineWhitespace {
19634 mut token,
19635 mut grapheme_len,
19636 } => {
19637 in_whitespace = true;
19638 if have_preceding_whitespace && !preserve_existing_whitespace {
19639 continue;
19640 }
19641 if !preserve_existing_whitespace {
19642 token = " ";
19643 grapheme_len = 1;
19644 }
19645 if current_line_len + grapheme_len > wrap_column {
19646 wrapped_text.push_str(current_line.trim_end());
19647 wrapped_text.push('\n');
19648 current_line.truncate(line_prefix.len());
19649 current_line_len = line_prefix_len;
19650 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19651 current_line.push_str(token);
19652 current_line_len += grapheme_len;
19653 }
19654 }
19655 WordBreakToken::Newline => {
19656 in_whitespace = true;
19657 if preserve_existing_whitespace {
19658 wrapped_text.push_str(current_line.trim_end());
19659 wrapped_text.push('\n');
19660 current_line.truncate(line_prefix.len());
19661 current_line_len = line_prefix_len;
19662 } else if have_preceding_whitespace {
19663 continue;
19664 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19665 {
19666 wrapped_text.push_str(current_line.trim_end());
19667 wrapped_text.push('\n');
19668 current_line.truncate(line_prefix.len());
19669 current_line_len = line_prefix_len;
19670 } else if current_line_len != line_prefix_len {
19671 current_line.push(' ');
19672 current_line_len += 1;
19673 }
19674 }
19675 }
19676 }
19677
19678 if !current_line.is_empty() {
19679 wrapped_text.push_str(¤t_line);
19680 }
19681 wrapped_text
19682}
19683
19684#[test]
19685fn test_wrap_with_prefix() {
19686 assert_eq!(
19687 wrap_with_prefix(
19688 "# ".to_string(),
19689 "abcdefg".to_string(),
19690 4,
19691 NonZeroU32::new(4).unwrap(),
19692 false,
19693 ),
19694 "# abcdefg"
19695 );
19696 assert_eq!(
19697 wrap_with_prefix(
19698 "".to_string(),
19699 "\thello world".to_string(),
19700 8,
19701 NonZeroU32::new(4).unwrap(),
19702 false,
19703 ),
19704 "hello\nworld"
19705 );
19706 assert_eq!(
19707 wrap_with_prefix(
19708 "// ".to_string(),
19709 "xx \nyy zz aa bb cc".to_string(),
19710 12,
19711 NonZeroU32::new(4).unwrap(),
19712 false,
19713 ),
19714 "// xx yy zz\n// aa bb cc"
19715 );
19716 assert_eq!(
19717 wrap_with_prefix(
19718 String::new(),
19719 "这是什么 \n 钢笔".to_string(),
19720 3,
19721 NonZeroU32::new(4).unwrap(),
19722 false,
19723 ),
19724 "这是什\n么 钢\n笔"
19725 );
19726}
19727
19728pub trait CollaborationHub {
19729 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19730 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19731 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19732}
19733
19734impl CollaborationHub for Entity<Project> {
19735 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19736 self.read(cx).collaborators()
19737 }
19738
19739 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19740 self.read(cx).user_store().read(cx).participant_indices()
19741 }
19742
19743 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19744 let this = self.read(cx);
19745 let user_ids = this.collaborators().values().map(|c| c.user_id);
19746 this.user_store().read_with(cx, |user_store, cx| {
19747 user_store.participant_names(user_ids, cx)
19748 })
19749 }
19750}
19751
19752pub trait SemanticsProvider {
19753 fn hover(
19754 &self,
19755 buffer: &Entity<Buffer>,
19756 position: text::Anchor,
19757 cx: &mut App,
19758 ) -> Option<Task<Vec<project::Hover>>>;
19759
19760 fn inline_values(
19761 &self,
19762 buffer_handle: Entity<Buffer>,
19763 range: Range<text::Anchor>,
19764 cx: &mut App,
19765 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19766
19767 fn inlay_hints(
19768 &self,
19769 buffer_handle: Entity<Buffer>,
19770 range: Range<text::Anchor>,
19771 cx: &mut App,
19772 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19773
19774 fn resolve_inlay_hint(
19775 &self,
19776 hint: InlayHint,
19777 buffer_handle: Entity<Buffer>,
19778 server_id: LanguageServerId,
19779 cx: &mut App,
19780 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19781
19782 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19783
19784 fn document_highlights(
19785 &self,
19786 buffer: &Entity<Buffer>,
19787 position: text::Anchor,
19788 cx: &mut App,
19789 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19790
19791 fn definitions(
19792 &self,
19793 buffer: &Entity<Buffer>,
19794 position: text::Anchor,
19795 kind: GotoDefinitionKind,
19796 cx: &mut App,
19797 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19798
19799 fn range_for_rename(
19800 &self,
19801 buffer: &Entity<Buffer>,
19802 position: text::Anchor,
19803 cx: &mut App,
19804 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19805
19806 fn perform_rename(
19807 &self,
19808 buffer: &Entity<Buffer>,
19809 position: text::Anchor,
19810 new_name: String,
19811 cx: &mut App,
19812 ) -> Option<Task<Result<ProjectTransaction>>>;
19813}
19814
19815pub trait CompletionProvider {
19816 fn completions(
19817 &self,
19818 excerpt_id: ExcerptId,
19819 buffer: &Entity<Buffer>,
19820 buffer_position: text::Anchor,
19821 trigger: CompletionContext,
19822 window: &mut Window,
19823 cx: &mut Context<Editor>,
19824 ) -> Task<Result<Option<Vec<Completion>>>>;
19825
19826 fn resolve_completions(
19827 &self,
19828 buffer: Entity<Buffer>,
19829 completion_indices: Vec<usize>,
19830 completions: Rc<RefCell<Box<[Completion]>>>,
19831 cx: &mut Context<Editor>,
19832 ) -> Task<Result<bool>>;
19833
19834 fn apply_additional_edits_for_completion(
19835 &self,
19836 _buffer: Entity<Buffer>,
19837 _completions: Rc<RefCell<Box<[Completion]>>>,
19838 _completion_index: usize,
19839 _push_to_history: bool,
19840 _cx: &mut Context<Editor>,
19841 ) -> Task<Result<Option<language::Transaction>>> {
19842 Task::ready(Ok(None))
19843 }
19844
19845 fn is_completion_trigger(
19846 &self,
19847 buffer: &Entity<Buffer>,
19848 position: language::Anchor,
19849 text: &str,
19850 trigger_in_words: bool,
19851 cx: &mut Context<Editor>,
19852 ) -> bool;
19853
19854 fn sort_completions(&self) -> bool {
19855 true
19856 }
19857
19858 fn filter_completions(&self) -> bool {
19859 true
19860 }
19861}
19862
19863pub trait CodeActionProvider {
19864 fn id(&self) -> Arc<str>;
19865
19866 fn code_actions(
19867 &self,
19868 buffer: &Entity<Buffer>,
19869 range: Range<text::Anchor>,
19870 window: &mut Window,
19871 cx: &mut App,
19872 ) -> Task<Result<Vec<CodeAction>>>;
19873
19874 fn apply_code_action(
19875 &self,
19876 buffer_handle: Entity<Buffer>,
19877 action: CodeAction,
19878 excerpt_id: ExcerptId,
19879 push_to_history: bool,
19880 window: &mut Window,
19881 cx: &mut App,
19882 ) -> Task<Result<ProjectTransaction>>;
19883}
19884
19885impl CodeActionProvider for Entity<Project> {
19886 fn id(&self) -> Arc<str> {
19887 "project".into()
19888 }
19889
19890 fn code_actions(
19891 &self,
19892 buffer: &Entity<Buffer>,
19893 range: Range<text::Anchor>,
19894 _window: &mut Window,
19895 cx: &mut App,
19896 ) -> Task<Result<Vec<CodeAction>>> {
19897 self.update(cx, |project, cx| {
19898 let code_lens = project.code_lens(buffer, range.clone(), cx);
19899 let code_actions = project.code_actions(buffer, range, None, cx);
19900 cx.background_spawn(async move {
19901 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19902 Ok(code_lens
19903 .context("code lens fetch")?
19904 .into_iter()
19905 .chain(code_actions.context("code action fetch")?)
19906 .collect())
19907 })
19908 })
19909 }
19910
19911 fn apply_code_action(
19912 &self,
19913 buffer_handle: Entity<Buffer>,
19914 action: CodeAction,
19915 _excerpt_id: ExcerptId,
19916 push_to_history: bool,
19917 _window: &mut Window,
19918 cx: &mut App,
19919 ) -> Task<Result<ProjectTransaction>> {
19920 self.update(cx, |project, cx| {
19921 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19922 })
19923 }
19924}
19925
19926fn snippet_completions(
19927 project: &Project,
19928 buffer: &Entity<Buffer>,
19929 buffer_position: text::Anchor,
19930 cx: &mut App,
19931) -> Task<Result<Vec<Completion>>> {
19932 let languages = buffer.read(cx).languages_at(buffer_position);
19933 let snippet_store = project.snippets().read(cx);
19934
19935 let scopes: Vec<_> = languages
19936 .iter()
19937 .filter_map(|language| {
19938 let language_name = language.lsp_id();
19939 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19940
19941 if snippets.is_empty() {
19942 None
19943 } else {
19944 Some((language.default_scope(), snippets))
19945 }
19946 })
19947 .collect();
19948
19949 if scopes.is_empty() {
19950 return Task::ready(Ok(vec![]));
19951 }
19952
19953 let snapshot = buffer.read(cx).text_snapshot();
19954 let chars: String = snapshot
19955 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19956 .collect();
19957 let executor = cx.background_executor().clone();
19958
19959 cx.background_spawn(async move {
19960 let mut all_results: Vec<Completion> = Vec::new();
19961 for (scope, snippets) in scopes.into_iter() {
19962 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19963 let mut last_word = chars
19964 .chars()
19965 .take_while(|c| classifier.is_word(*c))
19966 .collect::<String>();
19967 last_word = last_word.chars().rev().collect();
19968
19969 if last_word.is_empty() {
19970 return Ok(vec![]);
19971 }
19972
19973 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19974 let to_lsp = |point: &text::Anchor| {
19975 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19976 point_to_lsp(end)
19977 };
19978 let lsp_end = to_lsp(&buffer_position);
19979
19980 let candidates = snippets
19981 .iter()
19982 .enumerate()
19983 .flat_map(|(ix, snippet)| {
19984 snippet
19985 .prefix
19986 .iter()
19987 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19988 })
19989 .collect::<Vec<StringMatchCandidate>>();
19990
19991 let mut matches = fuzzy::match_strings(
19992 &candidates,
19993 &last_word,
19994 last_word.chars().any(|c| c.is_uppercase()),
19995 100,
19996 &Default::default(),
19997 executor.clone(),
19998 )
19999 .await;
20000
20001 // Remove all candidates where the query's start does not match the start of any word in the candidate
20002 if let Some(query_start) = last_word.chars().next() {
20003 matches.retain(|string_match| {
20004 split_words(&string_match.string).any(|word| {
20005 // Check that the first codepoint of the word as lowercase matches the first
20006 // codepoint of the query as lowercase
20007 word.chars()
20008 .flat_map(|codepoint| codepoint.to_lowercase())
20009 .zip(query_start.to_lowercase())
20010 .all(|(word_cp, query_cp)| word_cp == query_cp)
20011 })
20012 });
20013 }
20014
20015 let matched_strings = matches
20016 .into_iter()
20017 .map(|m| m.string)
20018 .collect::<HashSet<_>>();
20019
20020 let mut result: Vec<Completion> = snippets
20021 .iter()
20022 .filter_map(|snippet| {
20023 let matching_prefix = snippet
20024 .prefix
20025 .iter()
20026 .find(|prefix| matched_strings.contains(*prefix))?;
20027 let start = as_offset - last_word.len();
20028 let start = snapshot.anchor_before(start);
20029 let range = start..buffer_position;
20030 let lsp_start = to_lsp(&start);
20031 let lsp_range = lsp::Range {
20032 start: lsp_start,
20033 end: lsp_end,
20034 };
20035 Some(Completion {
20036 replace_range: range,
20037 new_text: snippet.body.clone(),
20038 source: CompletionSource::Lsp {
20039 insert_range: None,
20040 server_id: LanguageServerId(usize::MAX),
20041 resolved: true,
20042 lsp_completion: Box::new(lsp::CompletionItem {
20043 label: snippet.prefix.first().unwrap().clone(),
20044 kind: Some(CompletionItemKind::SNIPPET),
20045 label_details: snippet.description.as_ref().map(|description| {
20046 lsp::CompletionItemLabelDetails {
20047 detail: Some(description.clone()),
20048 description: None,
20049 }
20050 }),
20051 insert_text_format: Some(InsertTextFormat::SNIPPET),
20052 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20053 lsp::InsertReplaceEdit {
20054 new_text: snippet.body.clone(),
20055 insert: lsp_range,
20056 replace: lsp_range,
20057 },
20058 )),
20059 filter_text: Some(snippet.body.clone()),
20060 sort_text: Some(char::MAX.to_string()),
20061 ..lsp::CompletionItem::default()
20062 }),
20063 lsp_defaults: None,
20064 },
20065 label: CodeLabel {
20066 text: matching_prefix.clone(),
20067 runs: Vec::new(),
20068 filter_range: 0..matching_prefix.len(),
20069 },
20070 icon_path: None,
20071 documentation: Some(
20072 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20073 single_line: snippet.name.clone().into(),
20074 plain_text: snippet
20075 .description
20076 .clone()
20077 .map(|description| description.into()),
20078 },
20079 ),
20080 insert_text_mode: None,
20081 confirm: None,
20082 })
20083 })
20084 .collect();
20085
20086 all_results.append(&mut result);
20087 }
20088
20089 Ok(all_results)
20090 })
20091}
20092
20093impl CompletionProvider for Entity<Project> {
20094 fn completions(
20095 &self,
20096 _excerpt_id: ExcerptId,
20097 buffer: &Entity<Buffer>,
20098 buffer_position: text::Anchor,
20099 options: CompletionContext,
20100 _window: &mut Window,
20101 cx: &mut Context<Editor>,
20102 ) -> Task<Result<Option<Vec<Completion>>>> {
20103 self.update(cx, |project, cx| {
20104 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20105 let project_completions = project.completions(buffer, buffer_position, options, cx);
20106 cx.background_spawn(async move {
20107 let snippets_completions = snippets.await?;
20108 match project_completions.await? {
20109 Some(mut completions) => {
20110 completions.extend(snippets_completions);
20111 Ok(Some(completions))
20112 }
20113 None => {
20114 if snippets_completions.is_empty() {
20115 Ok(None)
20116 } else {
20117 Ok(Some(snippets_completions))
20118 }
20119 }
20120 }
20121 })
20122 })
20123 }
20124
20125 fn resolve_completions(
20126 &self,
20127 buffer: Entity<Buffer>,
20128 completion_indices: Vec<usize>,
20129 completions: Rc<RefCell<Box<[Completion]>>>,
20130 cx: &mut Context<Editor>,
20131 ) -> Task<Result<bool>> {
20132 self.update(cx, |project, cx| {
20133 project.lsp_store().update(cx, |lsp_store, cx| {
20134 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20135 })
20136 })
20137 }
20138
20139 fn apply_additional_edits_for_completion(
20140 &self,
20141 buffer: Entity<Buffer>,
20142 completions: Rc<RefCell<Box<[Completion]>>>,
20143 completion_index: usize,
20144 push_to_history: bool,
20145 cx: &mut Context<Editor>,
20146 ) -> Task<Result<Option<language::Transaction>>> {
20147 self.update(cx, |project, cx| {
20148 project.lsp_store().update(cx, |lsp_store, cx| {
20149 lsp_store.apply_additional_edits_for_completion(
20150 buffer,
20151 completions,
20152 completion_index,
20153 push_to_history,
20154 cx,
20155 )
20156 })
20157 })
20158 }
20159
20160 fn is_completion_trigger(
20161 &self,
20162 buffer: &Entity<Buffer>,
20163 position: language::Anchor,
20164 text: &str,
20165 trigger_in_words: bool,
20166 cx: &mut Context<Editor>,
20167 ) -> bool {
20168 let mut chars = text.chars();
20169 let char = if let Some(char) = chars.next() {
20170 char
20171 } else {
20172 return false;
20173 };
20174 if chars.next().is_some() {
20175 return false;
20176 }
20177
20178 let buffer = buffer.read(cx);
20179 let snapshot = buffer.snapshot();
20180 if !snapshot.settings_at(position, cx).show_completions_on_input {
20181 return false;
20182 }
20183 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20184 if trigger_in_words && classifier.is_word(char) {
20185 return true;
20186 }
20187
20188 buffer.completion_triggers().contains(text)
20189 }
20190}
20191
20192impl SemanticsProvider for Entity<Project> {
20193 fn hover(
20194 &self,
20195 buffer: &Entity<Buffer>,
20196 position: text::Anchor,
20197 cx: &mut App,
20198 ) -> Option<Task<Vec<project::Hover>>> {
20199 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20200 }
20201
20202 fn document_highlights(
20203 &self,
20204 buffer: &Entity<Buffer>,
20205 position: text::Anchor,
20206 cx: &mut App,
20207 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20208 Some(self.update(cx, |project, cx| {
20209 project.document_highlights(buffer, position, cx)
20210 }))
20211 }
20212
20213 fn definitions(
20214 &self,
20215 buffer: &Entity<Buffer>,
20216 position: text::Anchor,
20217 kind: GotoDefinitionKind,
20218 cx: &mut App,
20219 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20220 Some(self.update(cx, |project, cx| match kind {
20221 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20222 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20223 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20224 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20225 }))
20226 }
20227
20228 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20229 // TODO: make this work for remote projects
20230 self.update(cx, |project, cx| {
20231 if project
20232 .active_debug_session(cx)
20233 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20234 {
20235 return true;
20236 }
20237
20238 buffer.update(cx, |buffer, cx| {
20239 project.any_language_server_supports_inlay_hints(buffer, cx)
20240 })
20241 })
20242 }
20243
20244 fn inline_values(
20245 &self,
20246 buffer_handle: Entity<Buffer>,
20247
20248 range: Range<text::Anchor>,
20249 cx: &mut App,
20250 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20251 self.update(cx, |project, cx| {
20252 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20253
20254 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20255 })
20256 }
20257
20258 fn inlay_hints(
20259 &self,
20260 buffer_handle: Entity<Buffer>,
20261 range: Range<text::Anchor>,
20262 cx: &mut App,
20263 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20264 Some(self.update(cx, |project, cx| {
20265 project.inlay_hints(buffer_handle, range, cx)
20266 }))
20267 }
20268
20269 fn resolve_inlay_hint(
20270 &self,
20271 hint: InlayHint,
20272 buffer_handle: Entity<Buffer>,
20273 server_id: LanguageServerId,
20274 cx: &mut App,
20275 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20276 Some(self.update(cx, |project, cx| {
20277 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20278 }))
20279 }
20280
20281 fn range_for_rename(
20282 &self,
20283 buffer: &Entity<Buffer>,
20284 position: text::Anchor,
20285 cx: &mut App,
20286 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20287 Some(self.update(cx, |project, cx| {
20288 let buffer = buffer.clone();
20289 let task = project.prepare_rename(buffer.clone(), position, cx);
20290 cx.spawn(async move |_, cx| {
20291 Ok(match task.await? {
20292 PrepareRenameResponse::Success(range) => Some(range),
20293 PrepareRenameResponse::InvalidPosition => None,
20294 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20295 // Fallback on using TreeSitter info to determine identifier range
20296 buffer.update(cx, |buffer, _| {
20297 let snapshot = buffer.snapshot();
20298 let (range, kind) = snapshot.surrounding_word(position);
20299 if kind != Some(CharKind::Word) {
20300 return None;
20301 }
20302 Some(
20303 snapshot.anchor_before(range.start)
20304 ..snapshot.anchor_after(range.end),
20305 )
20306 })?
20307 }
20308 })
20309 })
20310 }))
20311 }
20312
20313 fn perform_rename(
20314 &self,
20315 buffer: &Entity<Buffer>,
20316 position: text::Anchor,
20317 new_name: String,
20318 cx: &mut App,
20319 ) -> Option<Task<Result<ProjectTransaction>>> {
20320 Some(self.update(cx, |project, cx| {
20321 project.perform_rename(buffer.clone(), position, new_name, cx)
20322 }))
20323 }
20324}
20325
20326fn inlay_hint_settings(
20327 location: Anchor,
20328 snapshot: &MultiBufferSnapshot,
20329 cx: &mut Context<Editor>,
20330) -> InlayHintSettings {
20331 let file = snapshot.file_at(location);
20332 let language = snapshot.language_at(location).map(|l| l.name());
20333 language_settings(language, file, cx).inlay_hints
20334}
20335
20336fn consume_contiguous_rows(
20337 contiguous_row_selections: &mut Vec<Selection<Point>>,
20338 selection: &Selection<Point>,
20339 display_map: &DisplaySnapshot,
20340 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20341) -> (MultiBufferRow, MultiBufferRow) {
20342 contiguous_row_selections.push(selection.clone());
20343 let start_row = MultiBufferRow(selection.start.row);
20344 let mut end_row = ending_row(selection, display_map);
20345
20346 while let Some(next_selection) = selections.peek() {
20347 if next_selection.start.row <= end_row.0 {
20348 end_row = ending_row(next_selection, display_map);
20349 contiguous_row_selections.push(selections.next().unwrap().clone());
20350 } else {
20351 break;
20352 }
20353 }
20354 (start_row, end_row)
20355}
20356
20357fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20358 if next_selection.end.column > 0 || next_selection.is_empty() {
20359 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20360 } else {
20361 MultiBufferRow(next_selection.end.row)
20362 }
20363}
20364
20365impl EditorSnapshot {
20366 pub fn remote_selections_in_range<'a>(
20367 &'a self,
20368 range: &'a Range<Anchor>,
20369 collaboration_hub: &dyn CollaborationHub,
20370 cx: &'a App,
20371 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20372 let participant_names = collaboration_hub.user_names(cx);
20373 let participant_indices = collaboration_hub.user_participant_indices(cx);
20374 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20375 let collaborators_by_replica_id = collaborators_by_peer_id
20376 .values()
20377 .map(|collaborator| (collaborator.replica_id, collaborator))
20378 .collect::<HashMap<_, _>>();
20379 self.buffer_snapshot
20380 .selections_in_range(range, false)
20381 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20382 if replica_id == AGENT_REPLICA_ID {
20383 Some(RemoteSelection {
20384 replica_id,
20385 selection,
20386 cursor_shape,
20387 line_mode,
20388 collaborator_id: CollaboratorId::Agent,
20389 user_name: Some("Agent".into()),
20390 color: cx.theme().players().agent(),
20391 })
20392 } else {
20393 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20394 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20395 let user_name = participant_names.get(&collaborator.user_id).cloned();
20396 Some(RemoteSelection {
20397 replica_id,
20398 selection,
20399 cursor_shape,
20400 line_mode,
20401 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20402 user_name,
20403 color: if let Some(index) = participant_index {
20404 cx.theme().players().color_for_participant(index.0)
20405 } else {
20406 cx.theme().players().absent()
20407 },
20408 })
20409 }
20410 })
20411 }
20412
20413 pub fn hunks_for_ranges(
20414 &self,
20415 ranges: impl IntoIterator<Item = Range<Point>>,
20416 ) -> Vec<MultiBufferDiffHunk> {
20417 let mut hunks = Vec::new();
20418 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20419 HashMap::default();
20420 for query_range in ranges {
20421 let query_rows =
20422 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20423 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20424 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20425 ) {
20426 // Include deleted hunks that are adjacent to the query range, because
20427 // otherwise they would be missed.
20428 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20429 if hunk.status().is_deleted() {
20430 intersects_range |= hunk.row_range.start == query_rows.end;
20431 intersects_range |= hunk.row_range.end == query_rows.start;
20432 }
20433 if intersects_range {
20434 if !processed_buffer_rows
20435 .entry(hunk.buffer_id)
20436 .or_default()
20437 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20438 {
20439 continue;
20440 }
20441 hunks.push(hunk);
20442 }
20443 }
20444 }
20445
20446 hunks
20447 }
20448
20449 fn display_diff_hunks_for_rows<'a>(
20450 &'a self,
20451 display_rows: Range<DisplayRow>,
20452 folded_buffers: &'a HashSet<BufferId>,
20453 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20454 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20455 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20456
20457 self.buffer_snapshot
20458 .diff_hunks_in_range(buffer_start..buffer_end)
20459 .filter_map(|hunk| {
20460 if folded_buffers.contains(&hunk.buffer_id) {
20461 return None;
20462 }
20463
20464 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20465 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20466
20467 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20468 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20469
20470 let display_hunk = if hunk_display_start.column() != 0 {
20471 DisplayDiffHunk::Folded {
20472 display_row: hunk_display_start.row(),
20473 }
20474 } else {
20475 let mut end_row = hunk_display_end.row();
20476 if hunk_display_end.column() > 0 {
20477 end_row.0 += 1;
20478 }
20479 let is_created_file = hunk.is_created_file();
20480 DisplayDiffHunk::Unfolded {
20481 status: hunk.status(),
20482 diff_base_byte_range: hunk.diff_base_byte_range,
20483 display_row_range: hunk_display_start.row()..end_row,
20484 multi_buffer_range: Anchor::range_in_buffer(
20485 hunk.excerpt_id,
20486 hunk.buffer_id,
20487 hunk.buffer_range,
20488 ),
20489 is_created_file,
20490 }
20491 };
20492
20493 Some(display_hunk)
20494 })
20495 }
20496
20497 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20498 self.display_snapshot.buffer_snapshot.language_at(position)
20499 }
20500
20501 pub fn is_focused(&self) -> bool {
20502 self.is_focused
20503 }
20504
20505 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20506 self.placeholder_text.as_ref()
20507 }
20508
20509 pub fn scroll_position(&self) -> gpui::Point<f32> {
20510 self.scroll_anchor.scroll_position(&self.display_snapshot)
20511 }
20512
20513 fn gutter_dimensions(
20514 &self,
20515 font_id: FontId,
20516 font_size: Pixels,
20517 max_line_number_width: Pixels,
20518 cx: &App,
20519 ) -> Option<GutterDimensions> {
20520 if !self.show_gutter {
20521 return None;
20522 }
20523
20524 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20525 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20526
20527 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20528 matches!(
20529 ProjectSettings::get_global(cx).git.git_gutter,
20530 Some(GitGutterSetting::TrackedFiles)
20531 )
20532 });
20533 let gutter_settings = EditorSettings::get_global(cx).gutter;
20534 let show_line_numbers = self
20535 .show_line_numbers
20536 .unwrap_or(gutter_settings.line_numbers);
20537 let line_gutter_width = if show_line_numbers {
20538 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20539 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20540 max_line_number_width.max(min_width_for_number_on_gutter)
20541 } else {
20542 0.0.into()
20543 };
20544
20545 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20546 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20547
20548 let git_blame_entries_width =
20549 self.git_blame_gutter_max_author_length
20550 .map(|max_author_length| {
20551 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20552 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20553
20554 /// The number of characters to dedicate to gaps and margins.
20555 const SPACING_WIDTH: usize = 4;
20556
20557 let max_char_count = max_author_length.min(renderer.max_author_length())
20558 + ::git::SHORT_SHA_LENGTH
20559 + MAX_RELATIVE_TIMESTAMP.len()
20560 + SPACING_WIDTH;
20561
20562 em_advance * max_char_count
20563 });
20564
20565 let is_singleton = self.buffer_snapshot.is_singleton();
20566
20567 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20568 left_padding += if !is_singleton {
20569 em_width * 4.0
20570 } else if show_runnables || show_breakpoints {
20571 em_width * 3.0
20572 } else if show_git_gutter && show_line_numbers {
20573 em_width * 2.0
20574 } else if show_git_gutter || show_line_numbers {
20575 em_width
20576 } else {
20577 px(0.)
20578 };
20579
20580 let shows_folds = is_singleton && gutter_settings.folds;
20581
20582 let right_padding = if shows_folds && show_line_numbers {
20583 em_width * 4.0
20584 } else if shows_folds || (!is_singleton && show_line_numbers) {
20585 em_width * 3.0
20586 } else if show_line_numbers {
20587 em_width
20588 } else {
20589 px(0.)
20590 };
20591
20592 Some(GutterDimensions {
20593 left_padding,
20594 right_padding,
20595 width: line_gutter_width + left_padding + right_padding,
20596 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20597 git_blame_entries_width,
20598 })
20599 }
20600
20601 pub fn render_crease_toggle(
20602 &self,
20603 buffer_row: MultiBufferRow,
20604 row_contains_cursor: bool,
20605 editor: Entity<Editor>,
20606 window: &mut Window,
20607 cx: &mut App,
20608 ) -> Option<AnyElement> {
20609 let folded = self.is_line_folded(buffer_row);
20610 let mut is_foldable = false;
20611
20612 if let Some(crease) = self
20613 .crease_snapshot
20614 .query_row(buffer_row, &self.buffer_snapshot)
20615 {
20616 is_foldable = true;
20617 match crease {
20618 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20619 if let Some(render_toggle) = render_toggle {
20620 let toggle_callback =
20621 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20622 if folded {
20623 editor.update(cx, |editor, cx| {
20624 editor.fold_at(buffer_row, window, cx)
20625 });
20626 } else {
20627 editor.update(cx, |editor, cx| {
20628 editor.unfold_at(buffer_row, window, cx)
20629 });
20630 }
20631 });
20632 return Some((render_toggle)(
20633 buffer_row,
20634 folded,
20635 toggle_callback,
20636 window,
20637 cx,
20638 ));
20639 }
20640 }
20641 }
20642 }
20643
20644 is_foldable |= self.starts_indent(buffer_row);
20645
20646 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20647 Some(
20648 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20649 .toggle_state(folded)
20650 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20651 if folded {
20652 this.unfold_at(buffer_row, window, cx);
20653 } else {
20654 this.fold_at(buffer_row, window, cx);
20655 }
20656 }))
20657 .into_any_element(),
20658 )
20659 } else {
20660 None
20661 }
20662 }
20663
20664 pub fn render_crease_trailer(
20665 &self,
20666 buffer_row: MultiBufferRow,
20667 window: &mut Window,
20668 cx: &mut App,
20669 ) -> Option<AnyElement> {
20670 let folded = self.is_line_folded(buffer_row);
20671 if let Crease::Inline { render_trailer, .. } = self
20672 .crease_snapshot
20673 .query_row(buffer_row, &self.buffer_snapshot)?
20674 {
20675 let render_trailer = render_trailer.as_ref()?;
20676 Some(render_trailer(buffer_row, folded, window, cx))
20677 } else {
20678 None
20679 }
20680 }
20681}
20682
20683impl Deref for EditorSnapshot {
20684 type Target = DisplaySnapshot;
20685
20686 fn deref(&self) -> &Self::Target {
20687 &self.display_snapshot
20688 }
20689}
20690
20691#[derive(Clone, Debug, PartialEq, Eq)]
20692pub enum EditorEvent {
20693 InputIgnored {
20694 text: Arc<str>,
20695 },
20696 InputHandled {
20697 utf16_range_to_replace: Option<Range<isize>>,
20698 text: Arc<str>,
20699 },
20700 ExcerptsAdded {
20701 buffer: Entity<Buffer>,
20702 predecessor: ExcerptId,
20703 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20704 },
20705 ExcerptsRemoved {
20706 ids: Vec<ExcerptId>,
20707 removed_buffer_ids: Vec<BufferId>,
20708 },
20709 BufferFoldToggled {
20710 ids: Vec<ExcerptId>,
20711 folded: bool,
20712 },
20713 ExcerptsEdited {
20714 ids: Vec<ExcerptId>,
20715 },
20716 ExcerptsExpanded {
20717 ids: Vec<ExcerptId>,
20718 },
20719 BufferEdited,
20720 Edited {
20721 transaction_id: clock::Lamport,
20722 },
20723 Reparsed(BufferId),
20724 Focused,
20725 FocusedIn,
20726 Blurred,
20727 DirtyChanged,
20728 Saved,
20729 TitleChanged,
20730 DiffBaseChanged,
20731 SelectionsChanged {
20732 local: bool,
20733 },
20734 ScrollPositionChanged {
20735 local: bool,
20736 autoscroll: bool,
20737 },
20738 Closed,
20739 TransactionUndone {
20740 transaction_id: clock::Lamport,
20741 },
20742 TransactionBegun {
20743 transaction_id: clock::Lamport,
20744 },
20745 Reloaded,
20746 CursorShapeChanged,
20747 PushedToNavHistory {
20748 anchor: Anchor,
20749 is_deactivate: bool,
20750 },
20751}
20752
20753impl EventEmitter<EditorEvent> for Editor {}
20754
20755impl Focusable for Editor {
20756 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20757 self.focus_handle.clone()
20758 }
20759}
20760
20761impl Render for Editor {
20762 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20763 let settings = ThemeSettings::get_global(cx);
20764
20765 let mut text_style = match self.mode {
20766 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20767 color: cx.theme().colors().editor_foreground,
20768 font_family: settings.ui_font.family.clone(),
20769 font_features: settings.ui_font.features.clone(),
20770 font_fallbacks: settings.ui_font.fallbacks.clone(),
20771 font_size: rems(0.875).into(),
20772 font_weight: settings.ui_font.weight,
20773 line_height: relative(settings.buffer_line_height.value()),
20774 ..Default::default()
20775 },
20776 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20777 color: cx.theme().colors().editor_foreground,
20778 font_family: settings.buffer_font.family.clone(),
20779 font_features: settings.buffer_font.features.clone(),
20780 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20781 font_size: settings.buffer_font_size(cx).into(),
20782 font_weight: settings.buffer_font.weight,
20783 line_height: relative(settings.buffer_line_height.value()),
20784 ..Default::default()
20785 },
20786 };
20787 if let Some(text_style_refinement) = &self.text_style_refinement {
20788 text_style.refine(text_style_refinement)
20789 }
20790
20791 let background = match self.mode {
20792 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20793 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20794 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20795 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20796 };
20797
20798 EditorElement::new(
20799 &cx.entity(),
20800 EditorStyle {
20801 background,
20802 local_player: cx.theme().players().local(),
20803 text: text_style,
20804 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20805 syntax: cx.theme().syntax().clone(),
20806 status: cx.theme().status().clone(),
20807 inlay_hints_style: make_inlay_hints_style(cx),
20808 inline_completion_styles: make_suggestion_styles(cx),
20809 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20810 show_underlines: !self.mode.is_minimap(),
20811 },
20812 )
20813 }
20814}
20815
20816impl EntityInputHandler for Editor {
20817 fn text_for_range(
20818 &mut self,
20819 range_utf16: Range<usize>,
20820 adjusted_range: &mut Option<Range<usize>>,
20821 _: &mut Window,
20822 cx: &mut Context<Self>,
20823 ) -> Option<String> {
20824 let snapshot = self.buffer.read(cx).read(cx);
20825 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20826 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20827 if (start.0..end.0) != range_utf16 {
20828 adjusted_range.replace(start.0..end.0);
20829 }
20830 Some(snapshot.text_for_range(start..end).collect())
20831 }
20832
20833 fn selected_text_range(
20834 &mut self,
20835 ignore_disabled_input: bool,
20836 _: &mut Window,
20837 cx: &mut Context<Self>,
20838 ) -> Option<UTF16Selection> {
20839 // Prevent the IME menu from appearing when holding down an alphabetic key
20840 // while input is disabled.
20841 if !ignore_disabled_input && !self.input_enabled {
20842 return None;
20843 }
20844
20845 let selection = self.selections.newest::<OffsetUtf16>(cx);
20846 let range = selection.range();
20847
20848 Some(UTF16Selection {
20849 range: range.start.0..range.end.0,
20850 reversed: selection.reversed,
20851 })
20852 }
20853
20854 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20855 let snapshot = self.buffer.read(cx).read(cx);
20856 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20857 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20858 }
20859
20860 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20861 self.clear_highlights::<InputComposition>(cx);
20862 self.ime_transaction.take();
20863 }
20864
20865 fn replace_text_in_range(
20866 &mut self,
20867 range_utf16: Option<Range<usize>>,
20868 text: &str,
20869 window: &mut Window,
20870 cx: &mut Context<Self>,
20871 ) {
20872 if !self.input_enabled {
20873 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20874 return;
20875 }
20876
20877 self.transact(window, cx, |this, window, cx| {
20878 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20879 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20880 Some(this.selection_replacement_ranges(range_utf16, cx))
20881 } else {
20882 this.marked_text_ranges(cx)
20883 };
20884
20885 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20886 let newest_selection_id = this.selections.newest_anchor().id;
20887 this.selections
20888 .all::<OffsetUtf16>(cx)
20889 .iter()
20890 .zip(ranges_to_replace.iter())
20891 .find_map(|(selection, range)| {
20892 if selection.id == newest_selection_id {
20893 Some(
20894 (range.start.0 as isize - selection.head().0 as isize)
20895 ..(range.end.0 as isize - selection.head().0 as isize),
20896 )
20897 } else {
20898 None
20899 }
20900 })
20901 });
20902
20903 cx.emit(EditorEvent::InputHandled {
20904 utf16_range_to_replace: range_to_replace,
20905 text: text.into(),
20906 });
20907
20908 if let Some(new_selected_ranges) = new_selected_ranges {
20909 this.change_selections(None, window, cx, |selections| {
20910 selections.select_ranges(new_selected_ranges)
20911 });
20912 this.backspace(&Default::default(), window, cx);
20913 }
20914
20915 this.handle_input(text, window, cx);
20916 });
20917
20918 if let Some(transaction) = self.ime_transaction {
20919 self.buffer.update(cx, |buffer, cx| {
20920 buffer.group_until_transaction(transaction, cx);
20921 });
20922 }
20923
20924 self.unmark_text(window, cx);
20925 }
20926
20927 fn replace_and_mark_text_in_range(
20928 &mut self,
20929 range_utf16: Option<Range<usize>>,
20930 text: &str,
20931 new_selected_range_utf16: Option<Range<usize>>,
20932 window: &mut Window,
20933 cx: &mut Context<Self>,
20934 ) {
20935 if !self.input_enabled {
20936 return;
20937 }
20938
20939 let transaction = self.transact(window, cx, |this, window, cx| {
20940 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20941 let snapshot = this.buffer.read(cx).read(cx);
20942 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20943 for marked_range in &mut marked_ranges {
20944 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20945 marked_range.start.0 += relative_range_utf16.start;
20946 marked_range.start =
20947 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20948 marked_range.end =
20949 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20950 }
20951 }
20952 Some(marked_ranges)
20953 } else if let Some(range_utf16) = range_utf16 {
20954 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20955 Some(this.selection_replacement_ranges(range_utf16, cx))
20956 } else {
20957 None
20958 };
20959
20960 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20961 let newest_selection_id = this.selections.newest_anchor().id;
20962 this.selections
20963 .all::<OffsetUtf16>(cx)
20964 .iter()
20965 .zip(ranges_to_replace.iter())
20966 .find_map(|(selection, range)| {
20967 if selection.id == newest_selection_id {
20968 Some(
20969 (range.start.0 as isize - selection.head().0 as isize)
20970 ..(range.end.0 as isize - selection.head().0 as isize),
20971 )
20972 } else {
20973 None
20974 }
20975 })
20976 });
20977
20978 cx.emit(EditorEvent::InputHandled {
20979 utf16_range_to_replace: range_to_replace,
20980 text: text.into(),
20981 });
20982
20983 if let Some(ranges) = ranges_to_replace {
20984 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20985 }
20986
20987 let marked_ranges = {
20988 let snapshot = this.buffer.read(cx).read(cx);
20989 this.selections
20990 .disjoint_anchors()
20991 .iter()
20992 .map(|selection| {
20993 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20994 })
20995 .collect::<Vec<_>>()
20996 };
20997
20998 if text.is_empty() {
20999 this.unmark_text(window, cx);
21000 } else {
21001 this.highlight_text::<InputComposition>(
21002 marked_ranges.clone(),
21003 HighlightStyle {
21004 underline: Some(UnderlineStyle {
21005 thickness: px(1.),
21006 color: None,
21007 wavy: false,
21008 }),
21009 ..Default::default()
21010 },
21011 cx,
21012 );
21013 }
21014
21015 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21016 let use_autoclose = this.use_autoclose;
21017 let use_auto_surround = this.use_auto_surround;
21018 this.set_use_autoclose(false);
21019 this.set_use_auto_surround(false);
21020 this.handle_input(text, window, cx);
21021 this.set_use_autoclose(use_autoclose);
21022 this.set_use_auto_surround(use_auto_surround);
21023
21024 if let Some(new_selected_range) = new_selected_range_utf16 {
21025 let snapshot = this.buffer.read(cx).read(cx);
21026 let new_selected_ranges = marked_ranges
21027 .into_iter()
21028 .map(|marked_range| {
21029 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21030 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21031 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21032 snapshot.clip_offset_utf16(new_start, Bias::Left)
21033 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21034 })
21035 .collect::<Vec<_>>();
21036
21037 drop(snapshot);
21038 this.change_selections(None, window, cx, |selections| {
21039 selections.select_ranges(new_selected_ranges)
21040 });
21041 }
21042 });
21043
21044 self.ime_transaction = self.ime_transaction.or(transaction);
21045 if let Some(transaction) = self.ime_transaction {
21046 self.buffer.update(cx, |buffer, cx| {
21047 buffer.group_until_transaction(transaction, cx);
21048 });
21049 }
21050
21051 if self.text_highlights::<InputComposition>(cx).is_none() {
21052 self.ime_transaction.take();
21053 }
21054 }
21055
21056 fn bounds_for_range(
21057 &mut self,
21058 range_utf16: Range<usize>,
21059 element_bounds: gpui::Bounds<Pixels>,
21060 window: &mut Window,
21061 cx: &mut Context<Self>,
21062 ) -> Option<gpui::Bounds<Pixels>> {
21063 let text_layout_details = self.text_layout_details(window);
21064 let gpui::Size {
21065 width: em_width,
21066 height: line_height,
21067 } = self.character_size(window);
21068
21069 let snapshot = self.snapshot(window, cx);
21070 let scroll_position = snapshot.scroll_position();
21071 let scroll_left = scroll_position.x * em_width;
21072
21073 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21074 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21075 + self.gutter_dimensions.width
21076 + self.gutter_dimensions.margin;
21077 let y = line_height * (start.row().as_f32() - scroll_position.y);
21078
21079 Some(Bounds {
21080 origin: element_bounds.origin + point(x, y),
21081 size: size(em_width, line_height),
21082 })
21083 }
21084
21085 fn character_index_for_point(
21086 &mut self,
21087 point: gpui::Point<Pixels>,
21088 _window: &mut Window,
21089 _cx: &mut Context<Self>,
21090 ) -> Option<usize> {
21091 let position_map = self.last_position_map.as_ref()?;
21092 if !position_map.text_hitbox.contains(&point) {
21093 return None;
21094 }
21095 let display_point = position_map.point_for_position(point).previous_valid;
21096 let anchor = position_map
21097 .snapshot
21098 .display_point_to_anchor(display_point, Bias::Left);
21099 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21100 Some(utf16_offset.0)
21101 }
21102}
21103
21104trait SelectionExt {
21105 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21106 fn spanned_rows(
21107 &self,
21108 include_end_if_at_line_start: bool,
21109 map: &DisplaySnapshot,
21110 ) -> Range<MultiBufferRow>;
21111}
21112
21113impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21114 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21115 let start = self
21116 .start
21117 .to_point(&map.buffer_snapshot)
21118 .to_display_point(map);
21119 let end = self
21120 .end
21121 .to_point(&map.buffer_snapshot)
21122 .to_display_point(map);
21123 if self.reversed {
21124 end..start
21125 } else {
21126 start..end
21127 }
21128 }
21129
21130 fn spanned_rows(
21131 &self,
21132 include_end_if_at_line_start: bool,
21133 map: &DisplaySnapshot,
21134 ) -> Range<MultiBufferRow> {
21135 let start = self.start.to_point(&map.buffer_snapshot);
21136 let mut end = self.end.to_point(&map.buffer_snapshot);
21137 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21138 end.row -= 1;
21139 }
21140
21141 let buffer_start = map.prev_line_boundary(start).0;
21142 let buffer_end = map.next_line_boundary(end).0;
21143 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21144 }
21145}
21146
21147impl<T: InvalidationRegion> InvalidationStack<T> {
21148 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21149 where
21150 S: Clone + ToOffset,
21151 {
21152 while let Some(region) = self.last() {
21153 let all_selections_inside_invalidation_ranges =
21154 if selections.len() == region.ranges().len() {
21155 selections
21156 .iter()
21157 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21158 .all(|(selection, invalidation_range)| {
21159 let head = selection.head().to_offset(buffer);
21160 invalidation_range.start <= head && invalidation_range.end >= head
21161 })
21162 } else {
21163 false
21164 };
21165
21166 if all_selections_inside_invalidation_ranges {
21167 break;
21168 } else {
21169 self.pop();
21170 }
21171 }
21172 }
21173}
21174
21175impl<T> Default for InvalidationStack<T> {
21176 fn default() -> Self {
21177 Self(Default::default())
21178 }
21179}
21180
21181impl<T> Deref for InvalidationStack<T> {
21182 type Target = Vec<T>;
21183
21184 fn deref(&self) -> &Self::Target {
21185 &self.0
21186 }
21187}
21188
21189impl<T> DerefMut for InvalidationStack<T> {
21190 fn deref_mut(&mut self) -> &mut Self::Target {
21191 &mut self.0
21192 }
21193}
21194
21195impl InvalidationRegion for SnippetState {
21196 fn ranges(&self) -> &[Range<Anchor>] {
21197 &self.ranges[self.active_index]
21198 }
21199}
21200
21201fn inline_completion_edit_text(
21202 current_snapshot: &BufferSnapshot,
21203 edits: &[(Range<Anchor>, String)],
21204 edit_preview: &EditPreview,
21205 include_deletions: bool,
21206 cx: &App,
21207) -> HighlightedText {
21208 let edits = edits
21209 .iter()
21210 .map(|(anchor, text)| {
21211 (
21212 anchor.start.text_anchor..anchor.end.text_anchor,
21213 text.clone(),
21214 )
21215 })
21216 .collect::<Vec<_>>();
21217
21218 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21219}
21220
21221pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21222 match severity {
21223 lsp::DiagnosticSeverity::ERROR => colors.error,
21224 lsp::DiagnosticSeverity::WARNING => colors.warning,
21225 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21226 lsp::DiagnosticSeverity::HINT => colors.info,
21227 _ => colors.ignored,
21228 }
21229}
21230
21231pub fn styled_runs_for_code_label<'a>(
21232 label: &'a CodeLabel,
21233 syntax_theme: &'a theme::SyntaxTheme,
21234) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21235 let fade_out = HighlightStyle {
21236 fade_out: Some(0.35),
21237 ..Default::default()
21238 };
21239
21240 let mut prev_end = label.filter_range.end;
21241 label
21242 .runs
21243 .iter()
21244 .enumerate()
21245 .flat_map(move |(ix, (range, highlight_id))| {
21246 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21247 style
21248 } else {
21249 return Default::default();
21250 };
21251 let mut muted_style = style;
21252 muted_style.highlight(fade_out);
21253
21254 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21255 if range.start >= label.filter_range.end {
21256 if range.start > prev_end {
21257 runs.push((prev_end..range.start, fade_out));
21258 }
21259 runs.push((range.clone(), muted_style));
21260 } else if range.end <= label.filter_range.end {
21261 runs.push((range.clone(), style));
21262 } else {
21263 runs.push((range.start..label.filter_range.end, style));
21264 runs.push((label.filter_range.end..range.end, muted_style));
21265 }
21266 prev_end = cmp::max(prev_end, range.end);
21267
21268 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21269 runs.push((prev_end..label.text.len(), fade_out));
21270 }
21271
21272 runs
21273 })
21274}
21275
21276pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21277 let mut prev_index = 0;
21278 let mut prev_codepoint: Option<char> = None;
21279 text.char_indices()
21280 .chain([(text.len(), '\0')])
21281 .filter_map(move |(index, codepoint)| {
21282 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21283 let is_boundary = index == text.len()
21284 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21285 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21286 if is_boundary {
21287 let chunk = &text[prev_index..index];
21288 prev_index = index;
21289 Some(chunk)
21290 } else {
21291 None
21292 }
21293 })
21294}
21295
21296pub trait RangeToAnchorExt: Sized {
21297 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21298
21299 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21300 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21301 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21302 }
21303}
21304
21305impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21306 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21307 let start_offset = self.start.to_offset(snapshot);
21308 let end_offset = self.end.to_offset(snapshot);
21309 if start_offset == end_offset {
21310 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21311 } else {
21312 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21313 }
21314 }
21315}
21316
21317pub trait RowExt {
21318 fn as_f32(&self) -> f32;
21319
21320 fn next_row(&self) -> Self;
21321
21322 fn previous_row(&self) -> Self;
21323
21324 fn minus(&self, other: Self) -> u32;
21325}
21326
21327impl RowExt for DisplayRow {
21328 fn as_f32(&self) -> f32 {
21329 self.0 as f32
21330 }
21331
21332 fn next_row(&self) -> Self {
21333 Self(self.0 + 1)
21334 }
21335
21336 fn previous_row(&self) -> Self {
21337 Self(self.0.saturating_sub(1))
21338 }
21339
21340 fn minus(&self, other: Self) -> u32 {
21341 self.0 - other.0
21342 }
21343}
21344
21345impl RowExt for MultiBufferRow {
21346 fn as_f32(&self) -> f32 {
21347 self.0 as f32
21348 }
21349
21350 fn next_row(&self) -> Self {
21351 Self(self.0 + 1)
21352 }
21353
21354 fn previous_row(&self) -> Self {
21355 Self(self.0.saturating_sub(1))
21356 }
21357
21358 fn minus(&self, other: Self) -> u32 {
21359 self.0 - other.0
21360 }
21361}
21362
21363trait RowRangeExt {
21364 type Row;
21365
21366 fn len(&self) -> usize;
21367
21368 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21369}
21370
21371impl RowRangeExt for Range<MultiBufferRow> {
21372 type Row = MultiBufferRow;
21373
21374 fn len(&self) -> usize {
21375 (self.end.0 - self.start.0) as usize
21376 }
21377
21378 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21379 (self.start.0..self.end.0).map(MultiBufferRow)
21380 }
21381}
21382
21383impl RowRangeExt for Range<DisplayRow> {
21384 type Row = DisplayRow;
21385
21386 fn len(&self) -> usize {
21387 (self.end.0 - self.start.0) as usize
21388 }
21389
21390 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21391 (self.start.0..self.end.0).map(DisplayRow)
21392 }
21393}
21394
21395/// If select range has more than one line, we
21396/// just point the cursor to range.start.
21397fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21398 if range.start.row == range.end.row {
21399 range
21400 } else {
21401 range.start..range.start
21402 }
21403}
21404pub struct KillRing(ClipboardItem);
21405impl Global for KillRing {}
21406
21407const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21408
21409enum BreakpointPromptEditAction {
21410 Log,
21411 Condition,
21412 HitCondition,
21413}
21414
21415struct BreakpointPromptEditor {
21416 pub(crate) prompt: Entity<Editor>,
21417 editor: WeakEntity<Editor>,
21418 breakpoint_anchor: Anchor,
21419 breakpoint: Breakpoint,
21420 edit_action: BreakpointPromptEditAction,
21421 block_ids: HashSet<CustomBlockId>,
21422 editor_margins: Arc<Mutex<EditorMargins>>,
21423 _subscriptions: Vec<Subscription>,
21424}
21425
21426impl BreakpointPromptEditor {
21427 const MAX_LINES: u8 = 4;
21428
21429 fn new(
21430 editor: WeakEntity<Editor>,
21431 breakpoint_anchor: Anchor,
21432 breakpoint: Breakpoint,
21433 edit_action: BreakpointPromptEditAction,
21434 window: &mut Window,
21435 cx: &mut Context<Self>,
21436 ) -> Self {
21437 let base_text = match edit_action {
21438 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21439 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21440 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21441 }
21442 .map(|msg| msg.to_string())
21443 .unwrap_or_default();
21444
21445 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21446 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21447
21448 let prompt = cx.new(|cx| {
21449 let mut prompt = Editor::new(
21450 EditorMode::AutoHeight {
21451 max_lines: Self::MAX_LINES as usize,
21452 },
21453 buffer,
21454 None,
21455 window,
21456 cx,
21457 );
21458 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21459 prompt.set_show_cursor_when_unfocused(false, cx);
21460 prompt.set_placeholder_text(
21461 match edit_action {
21462 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21463 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21464 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21465 },
21466 cx,
21467 );
21468
21469 prompt
21470 });
21471
21472 Self {
21473 prompt,
21474 editor,
21475 breakpoint_anchor,
21476 breakpoint,
21477 edit_action,
21478 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21479 block_ids: Default::default(),
21480 _subscriptions: vec![],
21481 }
21482 }
21483
21484 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21485 self.block_ids.extend(block_ids)
21486 }
21487
21488 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21489 if let Some(editor) = self.editor.upgrade() {
21490 let message = self
21491 .prompt
21492 .read(cx)
21493 .buffer
21494 .read(cx)
21495 .as_singleton()
21496 .expect("A multi buffer in breakpoint prompt isn't possible")
21497 .read(cx)
21498 .as_rope()
21499 .to_string();
21500
21501 editor.update(cx, |editor, cx| {
21502 editor.edit_breakpoint_at_anchor(
21503 self.breakpoint_anchor,
21504 self.breakpoint.clone(),
21505 match self.edit_action {
21506 BreakpointPromptEditAction::Log => {
21507 BreakpointEditAction::EditLogMessage(message.into())
21508 }
21509 BreakpointPromptEditAction::Condition => {
21510 BreakpointEditAction::EditCondition(message.into())
21511 }
21512 BreakpointPromptEditAction::HitCondition => {
21513 BreakpointEditAction::EditHitCondition(message.into())
21514 }
21515 },
21516 cx,
21517 );
21518
21519 editor.remove_blocks(self.block_ids.clone(), None, cx);
21520 cx.focus_self(window);
21521 });
21522 }
21523 }
21524
21525 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21526 self.editor
21527 .update(cx, |editor, cx| {
21528 editor.remove_blocks(self.block_ids.clone(), None, cx);
21529 window.focus(&editor.focus_handle);
21530 })
21531 .log_err();
21532 }
21533
21534 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21535 let settings = ThemeSettings::get_global(cx);
21536 let text_style = TextStyle {
21537 color: if self.prompt.read(cx).read_only(cx) {
21538 cx.theme().colors().text_disabled
21539 } else {
21540 cx.theme().colors().text
21541 },
21542 font_family: settings.buffer_font.family.clone(),
21543 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21544 font_size: settings.buffer_font_size(cx).into(),
21545 font_weight: settings.buffer_font.weight,
21546 line_height: relative(settings.buffer_line_height.value()),
21547 ..Default::default()
21548 };
21549 EditorElement::new(
21550 &self.prompt,
21551 EditorStyle {
21552 background: cx.theme().colors().editor_background,
21553 local_player: cx.theme().players().local(),
21554 text: text_style,
21555 ..Default::default()
21556 },
21557 )
21558 }
21559}
21560
21561impl Render for BreakpointPromptEditor {
21562 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21563 let editor_margins = *self.editor_margins.lock();
21564 let gutter_dimensions = editor_margins.gutter;
21565 h_flex()
21566 .key_context("Editor")
21567 .bg(cx.theme().colors().editor_background)
21568 .border_y_1()
21569 .border_color(cx.theme().status().info_border)
21570 .size_full()
21571 .py(window.line_height() / 2.5)
21572 .on_action(cx.listener(Self::confirm))
21573 .on_action(cx.listener(Self::cancel))
21574 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21575 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21576 }
21577}
21578
21579impl Focusable for BreakpointPromptEditor {
21580 fn focus_handle(&self, cx: &App) -> FocusHandle {
21581 self.prompt.focus_handle(cx)
21582 }
21583}
21584
21585fn all_edits_insertions_or_deletions(
21586 edits: &Vec<(Range<Anchor>, String)>,
21587 snapshot: &MultiBufferSnapshot,
21588) -> bool {
21589 let mut all_insertions = true;
21590 let mut all_deletions = true;
21591
21592 for (range, new_text) in edits.iter() {
21593 let range_is_empty = range.to_offset(&snapshot).is_empty();
21594 let text_is_empty = new_text.is_empty();
21595
21596 if range_is_empty != text_is_empty {
21597 if range_is_empty {
21598 all_deletions = false;
21599 } else {
21600 all_insertions = false;
21601 }
21602 } else {
21603 return false;
21604 }
21605
21606 if !all_insertions && !all_deletions {
21607 return false;
21608 }
21609 }
21610 all_insertions || all_deletions
21611}
21612
21613struct MissingEditPredictionKeybindingTooltip;
21614
21615impl Render for MissingEditPredictionKeybindingTooltip {
21616 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21617 ui::tooltip_container(window, cx, |container, _, cx| {
21618 container
21619 .flex_shrink_0()
21620 .max_w_80()
21621 .min_h(rems_from_px(124.))
21622 .justify_between()
21623 .child(
21624 v_flex()
21625 .flex_1()
21626 .text_ui_sm(cx)
21627 .child(Label::new("Conflict with Accept Keybinding"))
21628 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21629 )
21630 .child(
21631 h_flex()
21632 .pb_1()
21633 .gap_1()
21634 .items_end()
21635 .w_full()
21636 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21637 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21638 }))
21639 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21640 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21641 })),
21642 )
21643 })
21644 }
21645}
21646
21647#[derive(Debug, Clone, Copy, PartialEq)]
21648pub struct LineHighlight {
21649 pub background: Background,
21650 pub border: Option<gpui::Hsla>,
21651 pub include_gutter: bool,
21652 pub type_id: Option<TypeId>,
21653}
21654
21655fn render_diff_hunk_controls(
21656 row: u32,
21657 status: &DiffHunkStatus,
21658 hunk_range: Range<Anchor>,
21659 is_created_file: bool,
21660 line_height: Pixels,
21661 editor: &Entity<Editor>,
21662 _window: &mut Window,
21663 cx: &mut App,
21664) -> AnyElement {
21665 h_flex()
21666 .h(line_height)
21667 .mr_1()
21668 .gap_1()
21669 .px_0p5()
21670 .pb_1()
21671 .border_x_1()
21672 .border_b_1()
21673 .border_color(cx.theme().colors().border_variant)
21674 .rounded_b_lg()
21675 .bg(cx.theme().colors().editor_background)
21676 .gap_1()
21677 .occlude()
21678 .shadow_md()
21679 .child(if status.has_secondary_hunk() {
21680 Button::new(("stage", row as u64), "Stage")
21681 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21682 .tooltip({
21683 let focus_handle = editor.focus_handle(cx);
21684 move |window, cx| {
21685 Tooltip::for_action_in(
21686 "Stage Hunk",
21687 &::git::ToggleStaged,
21688 &focus_handle,
21689 window,
21690 cx,
21691 )
21692 }
21693 })
21694 .on_click({
21695 let editor = editor.clone();
21696 move |_event, _window, cx| {
21697 editor.update(cx, |editor, cx| {
21698 editor.stage_or_unstage_diff_hunks(
21699 true,
21700 vec![hunk_range.start..hunk_range.start],
21701 cx,
21702 );
21703 });
21704 }
21705 })
21706 } else {
21707 Button::new(("unstage", row as u64), "Unstage")
21708 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21709 .tooltip({
21710 let focus_handle = editor.focus_handle(cx);
21711 move |window, cx| {
21712 Tooltip::for_action_in(
21713 "Unstage Hunk",
21714 &::git::ToggleStaged,
21715 &focus_handle,
21716 window,
21717 cx,
21718 )
21719 }
21720 })
21721 .on_click({
21722 let editor = editor.clone();
21723 move |_event, _window, cx| {
21724 editor.update(cx, |editor, cx| {
21725 editor.stage_or_unstage_diff_hunks(
21726 false,
21727 vec![hunk_range.start..hunk_range.start],
21728 cx,
21729 );
21730 });
21731 }
21732 })
21733 })
21734 .child(
21735 Button::new(("restore", row as u64), "Restore")
21736 .tooltip({
21737 let focus_handle = editor.focus_handle(cx);
21738 move |window, cx| {
21739 Tooltip::for_action_in(
21740 "Restore Hunk",
21741 &::git::Restore,
21742 &focus_handle,
21743 window,
21744 cx,
21745 )
21746 }
21747 })
21748 .on_click({
21749 let editor = editor.clone();
21750 move |_event, window, cx| {
21751 editor.update(cx, |editor, cx| {
21752 let snapshot = editor.snapshot(window, cx);
21753 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21754 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21755 });
21756 }
21757 })
21758 .disabled(is_created_file),
21759 )
21760 .when(
21761 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21762 |el| {
21763 el.child(
21764 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21765 .shape(IconButtonShape::Square)
21766 .icon_size(IconSize::Small)
21767 // .disabled(!has_multiple_hunks)
21768 .tooltip({
21769 let focus_handle = editor.focus_handle(cx);
21770 move |window, cx| {
21771 Tooltip::for_action_in(
21772 "Next Hunk",
21773 &GoToHunk,
21774 &focus_handle,
21775 window,
21776 cx,
21777 )
21778 }
21779 })
21780 .on_click({
21781 let editor = editor.clone();
21782 move |_event, window, cx| {
21783 editor.update(cx, |editor, cx| {
21784 let snapshot = editor.snapshot(window, cx);
21785 let position =
21786 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21787 editor.go_to_hunk_before_or_after_position(
21788 &snapshot,
21789 position,
21790 Direction::Next,
21791 window,
21792 cx,
21793 );
21794 editor.expand_selected_diff_hunks(cx);
21795 });
21796 }
21797 }),
21798 )
21799 .child(
21800 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21801 .shape(IconButtonShape::Square)
21802 .icon_size(IconSize::Small)
21803 // .disabled(!has_multiple_hunks)
21804 .tooltip({
21805 let focus_handle = editor.focus_handle(cx);
21806 move |window, cx| {
21807 Tooltip::for_action_in(
21808 "Previous Hunk",
21809 &GoToPreviousHunk,
21810 &focus_handle,
21811 window,
21812 cx,
21813 )
21814 }
21815 })
21816 .on_click({
21817 let editor = editor.clone();
21818 move |_event, window, cx| {
21819 editor.update(cx, |editor, cx| {
21820 let snapshot = editor.snapshot(window, cx);
21821 let point =
21822 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21823 editor.go_to_hunk_before_or_after_position(
21824 &snapshot,
21825 point,
21826 Direction::Prev,
21827 window,
21828 cx,
21829 );
21830 editor.expand_selected_diff_hunks(cx);
21831 });
21832 }
21833 }),
21834 )
21835 },
21836 )
21837 .into_any_element()
21838}